Fix AutocompleteController issues, emit ItemListChanged with new count

--- components/autocomplete/autocompletecontroller.coffee
+++ components/autocomplete/autocompletecontroller.coffee
@@ -184,7 +184,7 @@
         @appendAutoCompletedItem()
       @addItemToSubmitQueue activeItem.item
       @rearrangeInputWidth()
-      @emit 'ItemListChanged'
+      @emit 'ItemListChanged', @selectedItemCounter
     else
       inputView.setValue ''
       @getSingleton("windowController").setKeyView null
@@ -225,7 +225,7 @@
       defaultValue  : value

   removeHiddenInputItem:(name)->
-    @hiddenInputs[name].remove()
+    delete @hiddenInputs[name]

   addSelectedItem:(name,data)->
     {selectedItemClass} = @getOptions()
@@ -296,7 +296,7 @@
       if item.getOptions().userInput is not ""
         @selectedItemCounter++
     else
-      @addHiddenInputItem path.join('.'),itemValue
+      @addHiddenInputItem path, itemValue

     @addSelectedItemData data
     @addSelectedItem itemName, data
@@ -316,11 +316,11 @@
           sibling.id isnt id
       JsPath.setAt form.getCustomData(), path, collection
     else
-      @removeHiddenInputItem path.join('.')
+      @removeHiddenInputItem path
     @removeSelectedItemData data
     @selectedItemCounter--
     item.destroy()
-    @emit 'ItemListChanged'
+    @emit 'ItemListChanged', @selectedItemCounter

   rearrangeInputWidth:()->
     # mainView = @getView()


Update script to leverage auth.coffee for restricting user access.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -7,7 +7,12 @@
 # Configuration:
 #   HUBOT_DEPLOY_CONFIG
 #
-#   CONFIG should be in JSON i.e. '{ "foo": {"job": "deploy-foo", "users": ["*"] } }'
+#   CONFIG should be in JSON i.e. '{ "foo": {"job": "deploy-foo", "role": "deploy" } }'
+#
+#   The "role" field uses the [auth.coffee][1] module for restricting access via user
+#   configurable roles. You can set "role" to "*" if you don't care about restricting access.
+#
+#   [1]: https://github.com/github/hubot-scripts/blob/master/src/scripts/auth.coffee
 #
 # Commands:
 #   hubot deploy <environment> <branch> - deploys the specified branch to the specified environment
@@ -19,6 +24,12 @@

 jenkinsDeploy = (msg, robot) ->
   CONFIG = JSON.parse process.env.HUBOT_DEPLOY_CONFIG
+
+  userHasRole = (user, role) ->
+    if role is "*"
+      return true
+
+    return robot.Auth.hasRole(user, role)

   if not robot.jenkins?.build?
     msg.send "Error: jenkins plugin not installed."
@@ -34,13 +45,13 @@
     return

   job = CONFIG[environment].job
-  users = CONFIG[environment].users
+  role = CONFIG[environment].role
   params = "BRANCH=#{branch}"

-  if user not in users and '*' not in users
-    msg.send "Access denied."
-    msg.send "Valid users are: #{(u for u in users)}"
-    return
+  if not userHasRole(user, role)
+     msg.send "Access denied."
+     msg.send "You must have this role to use this command: #{role}"
+     return

   # monkeypatch the msg.match object
   msg.match[1] = job


lol .. doesnt work

lol .. doesnt work

--- src/dt-dom.coffee
+++ src/dt-dom.coffee
@@ -57,12 +57,7 @@

     tpl.on 'text', (el, text) ->
         delay.call el, ->
-            if el._dom.text?
-                el._dom.text = text
-            else if el._dom.textContent?
-                el._dom.textContent = text
-            else if el._dom.innerHTML?
-                el._dom.innerHTML = text
+            el._dom.textContent = text

     tpl.on 'raw', (el, html) ->
         delay.call el, ->


Delete entry from expansionState.entries

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -229,8 +229,13 @@
     for name, entry of removedEntries
       entriesRemoved = true
       entry.destroy()
-      delete @entries[name]
-      delete @expansionState[name]
+
+      if @entries.hasOwnProperty(entry)
+        delete @entries[name]
+
+      if @expansionState.entries.hasOwnProperty(entry)
+        delete @expansionState.entries[name]
+
     @emitter.emit('did-remove-entries', removedEntries) if entriesRemoved

     if newEntries.length > 0


Move isSelected test to before possible blur/click.

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -176,13 +176,14 @@
       element.selectionStart? and element.selectionEnd? and element.selectionStart != element.selectionEnd

   simulateSelect: (element) ->
+    isSelected = @isSelected element
     # If element == document.activeElement, then we won't get a new focus event.  So, we pretend (to any
     # active modes which care, e.g. PostFindMode) that element has been clicked.
     if element == document.activeElement and DomUtils.isEditable document.activeElement
       handlerStack.bubbleEvent "click", target: element

     element.focus()
-    unless @isSelected element
+    unless isSelected
       # When focusing a textbox, put the selection caret at the end of the textbox's contents.
       # For some HTML5 input types (eg. date) we can't position the caret, so we wrap this with a try.
       try element.setSelectionRange(element.value.length, element.value.length)


Referencing Backbone in Events.

Referencing Backbone in Events.

--- src/events.coffee
+++ src/events.coffee
@@ -1,9 +1,13 @@
 #
 # A module to that can be mixed into any object, allowing the object to trigger
-# named events. Inspired by code from Backbone.js.
+# named events.
+#
+# Based on code from Backbone.js (http://documentcloud.github.com/backbone)
 #

+
 #= require wolf
+

 wolf.Events =


fix: instances of λ should be treated as functions

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -22,7 +22,7 @@
     opts.ctx ?= Object.create vocabulary
     for k, v of opts.ctx when not ast.vars[k]
       ast.vars[k] = varInfo = type: 'X', slot: ast.nSlots++, scopeDepth: ast.scopeDepth
-      if typeof v is 'function'
+      if typeof v is 'function' or v instanceof λ
         varInfo.type = 'F'
         if v.aplMetaInfo?.isAdverb      then varInfo.isAdverb      = true
         if v.aplMetaInfo?.isConjunction then varInfo.isConjunction = true


[cleanup] extraneous await

[cleanup] extraneous await

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -122,7 +122,7 @@
     callback null, result

   listsByState: (queue = "*", state, callback) ->
-    await @scanRedis "bull:#{queue}:#{state}", callback
+    @scanRedis "bull:#{queue}:#{state}", callback

   #Returns the job data from a list of job ids
   fullKeysForList: (list, callback) ->


Fixed issue with parsing old highlights in utils.annotationFromHighlight()

--- src/readmill/utils.coffee
+++ src/readmill/utils.coffee
@@ -135,7 +135,7 @@
   annotationFromHighlight: (highlight, client) ->
     # Try and extract annotation metadata from the locators object.
     meta = try JSON.parse(highlight.locators.annotator) catch e then null
-    meta = (try {range: JSON.parse(highlight.pre)} catch e then null) unless meta
+    meta = (try ranges: JSON.parse(highlight.pre) catch e then null) unless meta
     deferred = new jQuery.Deferred()

     if meta


Use source sep. only if list has same brackets, otherwise get default

--- lib/list-edit.coffee
+++ lib/list-edit.coffee
@@ -62,7 +62,7 @@
     #    if cut == newElts.length # newElts.length > 1, so newElts[n-1] exists, not first and no need to fix pre
     #      newElts[n-1].post = cutElts[m].post

-    @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, {sep: separator, elts: listElements}, [cutStart, cutEnd]) ->
+    @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, {openBracket: openBracket, sep: separator, elts: listElements}, [cutStart, cutEnd]) ->
       if listElements.length == 0
         atom.notifications.addWarning 'List cut in empty list.'
       else
@@ -77,7 +77,7 @@

           # copy selected elements to clipboard
           selectionText = bufferText.slice listElements[cutStart].eltStart, listElements[cutEnd-1].eltEnd
-          atom.clipboard.write selectionText, @mkListEditMeta(separator)
+          atom.clipboard.write selectionText, @mkListEditMeta(openBracket, separator)

           if cutStart == 0
             if cutEnd == listElements.length
@@ -107,7 +107,7 @@

   copyCmd: ->
     console.log 'Executing command list-edit-copy'
-    @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, {sep: separator, elts: listElements}, [selStart,selEnd]) ->
+    @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, {openBracket: openBracket, sep: separator, elts: listElements}, [selStart,selEnd]) ->
       if listElements.length == 0
         atom.notifications.addWarning 'List copy in empty list.'
       else
@@ -118,7 +118,7 @@
           selectionText = bufferText.slice listElements[selStart].eltStart, listElements[selEnd-1].eltEnd
           # Clip includes separators, which seems logical when we use it for a non-list paste
           #console.log "Copied: '#{selectionText}'"
-          atom.clipboard.write selectionText, @mkListEditMeta(separator)
+          atom.clipboard.write selectionText, @mkListEditMeta(openBracket, separator)
           editor.setSelectedBufferRange (TextManipulation.getRangeForIxRange textBuffer,
                                            [listElements[selStart].eltStart, listElements[selEnd-1].eltEnd])

@@ -132,10 +132,13 @@
     else
       # TODO: For now assume clipboard comes from the same list as paste target
       @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, elementList, [selStart,selEnd]) ->
-        {startIx: listStartIx, endIx: listEndIx, sep: separator, elts: listElements} = elementList
+        {startIx: listStartIx, endIx: listEndIx, openBracket: openBracket, sep: separator, elts: listElements} = elementList
         console.log "About to list-paste \"#{clip.text}\" at selection [#{selStart},#{selEnd}>"
-
-        separator ?=  clipMeta.sep ? '<defaultSeparator>'
+        console.log "Opening bracket: #{openBracket}, separator: #{separator}"
+        separator ?= if clipMeta.openBracket == openBracket and clipMeta.sep
+                       clipMeta.sep # Use separator from clipboard only if it comes from a list with equal brackets
+                     else
+                       TextManipulation.getDefaultSeparatorFor openBracket

         if selStart != selEnd
           # if an element or a range is selected, all surrounding whitespace can be left untouched
@@ -224,8 +227,8 @@
   # and column nr of opening bracket for handling empty/one elt insertions (column nr is only for multi line lists)

-  mkListEditMeta: (separator) ->
-    {id: 'list-edit-clip-meta', sep: separator}
+  mkListEditMeta: (openBracket, separator) ->
+    {id: 'list-edit-clip-meta', openBracket: openBracket, sep: separator}

   getListEditMeta: (clip) ->
     clip.metadata if clip?.metadata?.id == 'list-edit-clip-meta'


Syntax fix.

Syntax fix.

--- src/netrunner.coffee
+++ src/netrunner.coffee
@@ -53,7 +53,7 @@
   if card.type == "Agenda"
     text += '*Adv/Pts*: '+ card.advancementcost + '\/' + card.agendapoints +'\n'
   end
-  if card.type == "ICE" || card.type == "Upgrade || card.type == "Asset"
+  if card.type == "ICE" || card.type == "Upgrade" || card.type == "Asset"
     text += '*Rez Cost*: ' + card.cost + '\n'
     if card.type == "ICE"
       text += '*Strength*: ' + card.strength + '\n'


fixed github plugin

fixed github plugin

--- src/github.coffee
+++ src/github.coffee
@@ -1,30 +1,31 @@
 {Status, Timeline, extend, jsonp} = Chorus = @Chorus

 class GithubCommit extends Status
-    constructor: (id, username, avatar, date, text, raw, @url) ->
+    constructor: (id, username, avatar, date, text, raw) ->
         super id, username, avatar, date, text, raw

-    getUrl: -> "http://github.com/#{ @url }"
+    getUrl: -> @getStreamUrl()

-    getStreamUrl: ->
-        "http://github.com/#{ @url.replace /\/commit\/[^\/]+$/, ''}"
+    getStreamUrl: -> "https://github.com/#{@username}"

     @from = (json) ->
-        {author, url, id, authored_date, message} = json
-        {name, email} = author
-        new GithubCommit id, name, gravatar(email), authored_date, message, json, url
+        {url, commit, sha, author} = json
+        {message} = commit
+        {login} = author
+
+        new GithubCommit sha, login, author.avatar_url, commit.author.date, message, json

 class GithubCommits extends Timeline
     constructor: (@username, @repo, options) ->
-        @queryUrl = "http://github.com/api/v2/json/commits/list"
-        @queryUrl += "/#{@username}/#{@repo}/#{options?.branch ? 'master'}"
+        @queryUrl = "https://api.github.com/repos"
+        @queryUrl += "/#{@username}/#{@repo}/commits"
         super options

     fetch: -> jsonp
         url: @queryUrl
         callback: (data) => @update data

-    statusesFromData: (data) -> GithubCommit.from item for item in data.commits
+    statusesFromData: (data) -> GithubCommit.from item for item in data.data

 Timeline.shorthands.unshift
     pattern: /^GH:([a-z-_]+)\/([a-z-_]+)/i,
@@ -35,210 +36,4 @@
     fun: (_, name, project, branch) ->
         new GithubCommits name, project, branch: branch

-extend Chorus, {GithubCommit, GithubCommits}
-
-gravatar = (email) ->
-    "http://www.gravatar.com/avatar/#{ md5 trim(email).toLowerCase() }"
-
-trim = (str) -> str.replace(/^\s+|\s+$/g, "")
-
-md5 = `function (string) {
-
-	function RotateLeft(lValue, iShiftBits) {
-		return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
-	}
-
-	function AddUnsigned(lX,lY) {
-		var lX4,lY4,lX8,lY8,lResult;
-		lX8 = (lX & 0x80000000);
-		lY8 = (lY & 0x80000000);
-		lX4 = (lX & 0x40000000);
-		lY4 = (lY & 0x40000000);
-		lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
-		if (lX4 & lY4) {
-			return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
-		}
-		if (lX4 | lY4) {
-			if (lResult & 0x40000000) {
-				return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
-			} else {
-				return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
-			}
-		} else {
-			return (lResult ^ lX8 ^ lY8);
-		}
- 	}
-
- 	function F(x,y,z) { return (x & y) | ((~x) & z); }
- 	function G(x,y,z) { return (x & z) | (y & (~z)); }
- 	function H(x,y,z) { return (x ^ y ^ z); }
-	function I(x,y,z) { return (y ^ (x | (~z))); }
-
-	function FF(a,b,c,d,x,s,ac) {
-		a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
-		return AddUnsigned(RotateLeft(a, s), b);
-	};
-
-	function GG(a,b,c,d,x,s,ac) {
-		a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
-		return AddUnsigned(RotateLeft(a, s), b);
-	};
-
-	function HH(a,b,c,d,x,s,ac) {
-		a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
-		return AddUnsigned(RotateLeft(a, s), b);
-	};
-
-	function II(a,b,c,d,x,s,ac) {
-		a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
-		return AddUnsigned(RotateLeft(a, s), b);
-	};
-
-	function ConvertToWordArray(string) {
-		var lWordCount;
-		var lMessageLength = string.length;
-		var lNumberOfWords_temp1=lMessageLength + 8;
-		var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
-		var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
-		var lWordArray=Array(lNumberOfWords-1);
-		var lBytePosition = 0;
-		var lByteCount = 0;
-		while ( lByteCount < lMessageLength ) {
-			lWordCount = (lByteCount-(lByteCount % 4))/4;
-			lBytePosition = (lByteCount % 4)*8;
-			lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
-			lByteCount++;
-		}
-		lWordCount = (lByteCount-(lByteCount % 4))/4;
-		lBytePosition = (lByteCount % 4)*8;
-		lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
-		lWordArray[lNumberOfWords-2] = lMessageLength<<3;
-		lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
-		return lWordArray;
-	};
-
-	function WordToHex(lValue) {
-		var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
-		for (lCount = 0;lCount<=3;lCount++) {
-			lByte = (lValue>>>(lCount*8)) & 255;
-			WordToHexValue_temp = "0" + lByte.toString(16);
-			WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
-		}
-		return WordToHexValue;
-	};
-
-	function Utf8Encode(string) {
-		string = string.replace(/\r\n/g,"\n");
-		var utftext = "";
-
-		for (var n = 0; n < string.length; n++) {
-
-			var c = string.charCodeAt(n);
-
-			if (c < 128) {
-				utftext += String.fromCharCode(c);
-			}
-			else if((c > 127) && (c < 2048)) {
-				utftext += String.fromCharCode((c >> 6) | 192);
-				utftext += String.fromCharCode((c & 63) | 128);
-			}
-			else {
-				utftext += String.fromCharCode((c >> 12) | 224);
-				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
-				utftext += String.fromCharCode((c & 63) | 128);
-			}
-
-		}
-
-		return utftext;
-	};
-
-	var x=Array();
-	var k,AA,BB,CC,DD,a,b,c,d;
-	var S11=7, S12=12, S13=17, S14=22;
-	var S21=5, S22=9 , S23=14, S24=20;
-	var S31=4, S32=11, S33=16, S34=23;
-	var S41=6, S42=10, S43=15, S44=21;
-
-	string = Utf8Encode(string);
-
-	x = ConvertToWordArray(string);
-
-	a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
-
-	for (k=0;k<x.length;k+=16) {
-		AA=a; BB=b; CC=c; DD=d;
-		a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
-		d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
-		c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
-		b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
-		a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
-		d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
-		c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
-		b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
-		a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
-		d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
-		c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
-		b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
-		a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
-		d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
-		c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
-		b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
-		a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
-		d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
-		c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
-		b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
-		a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
-		d=GG(d,a,b,c,x[k+10],S22,0x2441453);
-		c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
-		b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
-		a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
-		d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
-		c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
-		b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
-		a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
-		d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
-		c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
-		b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
-		a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
-		d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
-		c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
-		b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
-		a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
-		d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
-		c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
-		b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
-		a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
-		d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
-		c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
-		b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
-		a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
-		d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
-		c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
-		b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
-		a=II(a,b,c,d,x[k+0], S41,0xF4292244);
-		d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
-		c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
-		b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
-		a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
-		d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
-		c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
-		b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
-		a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
-		d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
-		c=II(c,d,a,b,x[k+6], S43,0xA3014314);
-		b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
-		a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
-		d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
-		c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
-		b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
-		a=AddUnsigned(a,AA);
-		b=AddUnsigned(b,BB);
-		c=AddUnsigned(c,CC);
-		d=AddUnsigned(d,DD);
-	}
-
-	var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
-
-	return temp.toLowerCase();
-}`
+extend Chorus, {GithubCommit, GithubCommits}


fix require

fix require

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -1,4 +1,4 @@
-realize = require './core'
+realize = require './realize'
 util = require './util'
 factories = require './factory'


needs for a tag

needs for a tag

--- lib/supporter.coffee
+++ lib/supporter.coffee
@@ -8,7 +8,6 @@
     score = baseScore / Math.pow(ageInHours + 2, f) * 100
     console.log [stars, baseScore, ageInHours, score]
     score
-

   sendSubscription: (userId, needs) ->
     user = Meteor.users.findOne(userId)
@@ -26,3 +25,7 @@
       subject: 'Recently added needs'

     Meteor.users.update {_id : userId}, {$set: { "profile.subscriptionEmailSentAt": new Date() } }
+
+  needsToSend: (tagList) ->
+    Needs.find ({tags: { $in: tagList }, $where: "this.subscriptionEmailSentAt.getTime() < this.createdAt.getTime()"})
+


do not resize for non-local terms

do not resize for non-local terms

--- lib/term-view.coffee
+++ lib/term-view.coffee
@@ -48,7 +48,7 @@
         @term.write data
     catch error
       console.error error
-    @resizeToPane()
+    @resizeToPane_()
     @focusTerm()

   attached: () ->
@@ -105,7 +105,7 @@
     term.focus()
     @applyStyle()
     @attachEvents()
-    @resizeToPane
+    @resizeToPane_

   resize: (cols, rows) ->
     return unless @term
@@ -158,10 +158,10 @@
     ) + "px"

   attachEvents: ->
-    @resizeToPane = @resizeToPane.bind this
+    @resizeToPane_ = @resizeToPane_.bind this
     @on 'focus', @focus
-    $(window).on 'resize', => @resizeToPane()
-    @disposable = atom.workspace.getActivePane().observeFlexScale => setTimeout (=> @resizeToPane()), 300
+    $(window).on 'resize', => @resizeToPane_()
+    @disposable = atom.workspace.getActivePane().observeFlexScale => setTimeout (=> @resizeToPane_()), 300
     atom.commands.add "atom-workspace", "term2:paste", => @paste()
     atom.commands.add "atom-workspace", "term2:copy", => @copy()

@@ -183,13 +183,14 @@
     @input atom.clipboard.read()

   focus: ->
-    @resizeToPane()
+    @resizeToPane_()
     @focusTerm()

   focusTerm: ->
     @term.focus()

-  resizeToPane: ->
+  resizeToPane_: ->
+    return unless @ptyProcess
     {cols, rows} = @getDimensions_()
     @resize cols, rows

@@ -217,7 +218,7 @@

   destroy: ->
     @off 'focus', @focus
-    $(window).off 'resize', @resizeToPane
+    $(window).off 'resize', @resizeToPane_
     if @ptyProcess
       @ptyProcess.terminate()
       @ptyProcess = null


Added docs for binding events

Added docs for binding events

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -61,6 +61,24 @@
 # the previous keystrokes are replayed. If there is ambiguity again during the
 # replay, the next longest bindings are disabled and the keystrokes are replayed
 # again.
+#
+# ## Events
+#
+# * `matched` -
+#      Emitted when keystrokes match a binding.
+#      * usedBinding - The {KeyBinding} that was used
+#      * unusedBindings - Other {KeyBinding}s that matched, but weren't used
+#
+# * `matched-partially` -
+#      Emitted when keystrokes partially match one or more bindings.
+#      * keystrokes - The keystroke {String} that triggered the partial
+#        binding match
+#      * keyBindings - The {KeyBinding}s that partially matched
+#
+# * `match-failed` -
+#      Emitted when keystrokes don't match any bindings.
+#      * keystrokes - The keystroke {String} that matched no bindings
+
 module.exports =
 class Keymap
   Emitter.includeInto(this)


fix for bug in @remove

fix for bug in @remove

Remove is using byId when I it should be using vmId

--- lib/viewmodel.coffee
+++ lib/viewmodel.coffee
@@ -58,7 +58,7 @@
     delete ViewModel.byId[viewmodel.vmId]
     templateName = ViewModel.templateName(viewmodel.templateInstance)
     if templateName
-      delete ViewModel.byTemplate[templateName][viewmodel.byId]
+      delete ViewModel.byTemplate[templateName][viewmodel.vmId]

   @find = (templateNameOrPredicate, predicateOrNothing) ->
     templateName = _.isString(templateNameOrPredicate) and templateNameOrPredicate
@@ -739,4 +739,4 @@
   @mixin = (obj) ->
     for key, value of obj
       ViewModel.mixins[key] = value
-    return+    return


Corrects text replace in atom snippet

Corrects text replace in atom snippet

--- ide-snippets/atom/StructureJS.Atom.cson
+++ ide-snippets/atom/StructureJS.Atom.cson
@@ -311,7 +311,7 @@
      * TODO: YUIDoc_comment
      *
      * @class ${1:Class}
-     * @extends ${1}
+     * @extends BaseEvent
      * @constructor
      **/
     var ${1:Class} = (function () {
@@ -326,7 +326,7 @@
          */
         ${1:Class}.EXAMPLE = '${1:Class}.example';

-      var _super = Extend(${1:Class}, ${1});
+      var _super = Extend(${1:Class}, BaseEvent);

           function ${1:Class}() {
               _super.call(this);


Include the user name for `me`

Include the user name for `me`

--- src/lieber.coffee
+++ src/lieber.coffee
@@ -21,6 +21,6 @@
     name = msg.match[1].trim()
     switch name
       when 'me'
-        msg.reply ", you are #{msg.random phrases}"
+        msg.reply "#{msg.message.user.name}, you are #{msg.random phrases}"
       else
         msg.send "#{name} is #{msg.random phrases}"


logger: Fix bug when not logging to terminal

--- src/logger.coffee
+++ src/logger.coffee
@@ -13,7 +13,7 @@
 [ INFO, WARN, ERROR, VERBOSE ] = TAGS = [ "INFO", "WARN", "ERROR", "VERBS" ]
 LEFT_MARGIN = _.maxBy(TAGS, _.size).length + 1
 LEFT_PADSTRING_LONG_LINES = _.repeat(" ", LEFT_MARGIN + TAG_INDICATOR.length)
-MAX_COLS_PER_LINE = stdout.columns - 1 # HACK: Assumes application won't last long
+MAX_COLS_PER_LINE = (stdout.columns ? Number.MAX_VALUE) - 1 # HACK: Assumes application won't last long

 # Private methods


fix: Make sure 'info' and 'debug' logging levels have different colors

--- src/logger.coffee
+++ src/logger.coffee
@@ -23,9 +23,9 @@
     warn: 1
     error: 0
   colors:
-    silly: 'magenta'
-    debug: 'blue'
-    verbose: 'cyan'
+    silly: 'gray'
+    debug: 'cyan'
+    verbose: 'magenta'
     info: 'blue'
     test: 'yellow'
     pass: 'green'


Adjust title screen.

Adjust title screen.

--- src/state/title.coffee
+++ src/state/title.coffee
@@ -29,16 +29,20 @@

 state.create = ->
     game.add.image 0, 0, 'background'
-    for c in colors
-        x = game.rnd.integerInRange(0, W)
-        y = game.rnd.integerInRange(0, H)
-        gem = game.add.image(x, y, "#{c}_square")
-        gem.angle = game.rnd.integerInRange(-45, 45)
-        tween = game.tweens.create(gem).to({angle: gem.angle + 10}, 300,
-            Phaser.Easing.Sinusoidal.InOut, true, game.rnd.integerInRange(0, 2000),
-            -1, true)
-        tween.repeatDelay(game.rnd.integerInRange(500, 1500))
-        tween.start()
+    for i in [1..3]
+        for c in colors
+            x = game.rnd.integerInRange(0, W)
+            y = game.rnd.integerInRange(0, H)
+            gem = game.add.image(x, y, "#{c}_square")
+            gem.angle = game.rnd.integerInRange(-45, 45)
+            angleChange = game.rnd.integerInRange(-30, 30)
+            newAnchor = game.rnd.frac()
+            gem.anchor.set newAnchor
+            tween = game.tweens.create(gem).to({angle: gem.angle + angleChange}, 300,
+                Phaser.Easing.Sinusoidal.InOut, true, game.rnd.integerInRange(0, 2000),
+                -1, true)
+            tween.repeatDelay(game.rnd.integerInRange(500, 1500))
+            tween.start()
     text = game.add.text W / 4, H / 4, "1gam", textStyle
     text.anchor.set 0.5
     text.angle = -15
@@ -61,12 +65,14 @@
     button2Text = game.add.text W / 2, H / 5 * 4, "Free Play", buttonStyle
     button2Text.anchor.set 0.5

-    fsb = game.add.button 0, H, 'timer_bar', ->
+    fsb = game.add.button 86, H, 'timer_bar', ->
         game.scale.startFullScreen()
-    fsb.width = 20
-    fsb.height = 20
+    fsb.width = 80
+    fsb.height = 18
     fsb.anchor.y = 1
-    fst = game.add.text 0, H + 5, "FS", {font: '12pt sans-serif'}
+    fsb.anchor.x = 0.5
+    fst = game.add.text 86, H + 3, "Go Fullscreen", {font: '8pt sans-serif'}
+    fst.anchor.x = 0.5
     fst.anchor.y = 1

 module.exports = state


Group prefix and suffix code

Group prefix and suffix code

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -372,10 +372,11 @@
     lines.push(newText[previousIndex..]) if newText.length > previousIndex

     # Update first and last line so replacement preserves existing prefix and suffix of oldRange
+    prefix = @lineForRow(startRow)[0...oldRange.start.column]
+    lines[0] = prefix + lines[0]
+
+    suffix = @lineForRow(endRow)[oldRange.end.column...]
     lastIndex = lines.length - 1
-    prefix = @lineForRow(startRow)[0...oldRange.start.column]
-    suffix = @lineForRow(endRow)[oldRange.end.column...]
-    lines[0] = prefix + lines[0]
     lines[lastIndex] += suffix
     lastLineEnding = @lineEndingForRow(endRow)
     lastLineEnding = normalizedEnding if lastLineEnding isnt '' and normalizedEnding?


Use correct path underscore-extensions

Use correct path underscore-extensions

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1,4 +1,4 @@
-_ = require './underscore–extensions'
+_ = require './underscore-extensions'
 telepath = require 'telepath'
 {Point, Range} = telepath
 fsUtils = require './fs-utils'


Update function documentation for scrollToScreenPosition

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -3037,7 +3037,7 @@

   # Essential: Scrolls the editor to the given screen position.
   #
-  # * `screenPosition` An object that represents a buffer position. It can be either
+  # * `screenPosition` An object that represents a screen position. It can be either
   #    an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
   # * `options` (optional) {Object}
   #   * `center` Center the editor around the position if possible. (default: false)


Update text-editor.coffee

Update text-editor.coffee

Fix a type in `src/text-editor.coffee` (`adter` => `after`).

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -325,7 +325,7 @@
   onWillInsertText: (callback) ->
     @emitter.on 'will-insert-text', callback

-  # Extended: Calls your `callback` adter text has been inserted.
+  # Extended: Calls your `callback` after text has been inserted.
   #
   # * `callback` {Function}
   #   * `event` event {Object}


cleanup and check string memory retain-release leaks

--- tds.coffee
+++ tds.coffee
@@ -32,17 +32,14 @@
 STRING_ID = 0

 INT_SIZE = 16
-MAX_SIZE = 1<<26
 MAX_BYTES = 1<<29
 MAX_BYTES_POW = 29

 Strings =
- INVALID_STRING_REF: "Invalid String reference"
  NO_PROPERTIES: (name) ->
   "No properties in the struct #{name}"
- INVALID_STRING: "Invalid String"
-
-#TODO cleanup method to garbage collect
+
+#TODO cleanup() method to garbage collect
 StringAlloc = ->
  chars = []
  charLens = []
@@ -91,9 +88,10 @@
    if i_lCharPos is lastCharLen
     addChar()
    lastChar[i_lCharPos++] = str.charCodeAt i
-  view = i_lView
+  view = i_lView #these two variables are used outside after calling create()
   viewPos = i_lViewPos
   i_lViewPos++
+  return

  addView = ->
   size = lastViewLen
@@ -141,15 +139,14 @@
     create str
     @x = view
     @y = viewPos
-   else
-    ###
-    if x < 0 or x > i_lView or y < 0 or y >= viewLens[x] or
-    (x is i_lView and y >= i_lViewPos)
-     throw new Error Strings.INVALID_STRING_REF
-    ###
+   else if not x?
+    @x = @y = 0
+    retain 0, 0
+   else
     @x = x
     @y = y
     retain x, y
+   return

   release: ->
    release @x, @y
@@ -251,8 +248,6 @@
 p = new Person()
 console.log p.get()
 ###
-
-#TODO check memory leaks

 Struct = ->
  id = null
@@ -559,13 +554,13 @@

   add: (structIns) ->
    if i_lArrPos is size
-    @addArray()
+    @_addArray()

    @length++
    length++
    return lastArr.get i_lArrPos++, structIns

-  addArray: ->
+  _addArray: ->
    if size < max_size
     views = lastArr.getViews()
     lastArr = TDS.Array struct, size<<1
@@ -587,6 +582,7 @@

  new ArrayListClass

+#TODO remove() method to remove a key-val pair
 HashtableBase = (size, val_type) ->
  ListTerminal = Struct "__ListTerminal__",
   {property: 'start', type: Types.Float64}
@@ -598,8 +594,8 @@
   {property: 'val', type: val_type}
   {property: 'next', type: Types.Float64}

- lists = new ArrayList ListTerminal, size, (Math.min size, 1<<20)
- items = new ArrayList ItemType, null, 1<<20
+ lists = new ArrayList ListTerminal, size
+ items = new ArrayList ItemType, null
  li = ii = null
  class HashtableBaseClass
   constructor: ->


Respond to greetings too

Respond to greetings too

--- src/scripts/polite.coffee
+++ src/scripts/polite.coffee
@@ -23,3 +23,6 @@

   robot.respond /(ty|thx)/i, (msg) ->
     msg.send msg.random shortResponses
+
+  robot.respond /(hello|hi|sup|howdy|good (morning|evening|afternoon))/i, (msg) ->
+    msg.send "#{robot.name} at your service!"


Formatting issues

Formatting issues

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -7,12 +7,10 @@
   robot.hear /sifters/, (msg)->
     token = process.env.HUBOT_SIFTER_TOKEN
     company = process.env.HUBOT_SIFTER_COMPANY
-    projects = process.env.HUBOT_SIFTER_PROJECTS
-  msg
-     .http("https://#{company}.sifterapp.com/api/projects/")
+    project = process.env.HUBOT_SIFTER_PROJECT
+    msg
+     .http("https://#{company}.sifterapp.com/api/projects/#{project}/issues")
       .header('X-Sifter-Token', token)
       .header(Accept: 'application/json')
       .get() (err, res, body) ->
-        issues = JSON.parse(body)
-        console.log(issues)


hubot should only yell on broken builds

hubot should only yell on broken builds

and be more informative in messages send. Like offering some links to gh and travis

--- src/scripts/travis.coffee
+++ src/scripts/travis.coffee
@@ -3,7 +3,7 @@
 #   Can also notify about builds, just enable the webhook notification on travis http://about.travis-ci.org/docs/user/build-configuration/ -> 'Webhook notification'
 #
 # Dependencies:
-#   None
+#   "gitio": "1.0.1"
 #
 # Configuration:
 #   None
@@ -20,6 +20,7 @@

 url = require('url')
 querystring = require('querystring')
+gitio = require('gitio')

 module.exports = (robot) ->

@@ -43,4 +44,6 @@

     payload = JSON.parse req.body.payload

-    robot.send user, "#{payload.author_name} triggered build of #{payload.repository.name} and it #{payload.status_message}!"+    if payload.status isnt 0
+        gitio payload.compare_url, (err, data) ->
+            robot.send user, "#{payload.author_name} broke the build (#{payload.build_url}) of #{payload.repository.name} with commit (#{if err then payload.compare_url else data})!"


Fix issue in files.coffee

Fix issue in files.coffee

--- src/utils/files.coffee
+++ src/utils/files.coffee
@@ -534,13 +534,13 @@

             # Ensures that the index match the extension if provided.
             if ext isnt "*"
-              if p.match ///\.#{ext}$/// then return p else p = undefined
+              if p?.match ///\.#{ext}$/// then return p else p = undefined
             else
               return p
           # The path is a file, it'll ensure that the path match
           # the extension if provided.
           else if ext isnt "*"
-            if p.match ///\.#{ext}$/// then return p else p = undefined
+            if p?.match ///\.#{ext}$/// then return p else p = undefined
           else
             return p
   # Nothing was found, it returns undefined.


Fixed RefreshTimer's requirements

Fixed RefreshTimer's requirements

--- muleview/coffee/app/RefreshTimer.coffee
+++ muleview/coffee/app/RefreshTimer.coffee
@@ -1,5 +1,6 @@
 Ext.define "Muleview.RefreshTimer",
   requires: [
+    "Muleview.Events"
     "Muleview.Settings"
   ]
   singleton: true


Remove 'Experimental' from alternate scoring definition

--- lib/command-palette-view.coffee
+++ lib/command-palette-view.coffee
@@ -10,7 +10,7 @@
     useAlternateScoring:
       type: 'boolean'
       default: true
-      description: 'Use an alternative scoring approach which prefers run of consecutive characters, acronyms and start of words. (Experimental)'
+      description: 'Use an alternative scoring approach which prefers run of consecutive characters, acronyms and start of words.'

   @activate: ->
     view = new CommandPaletteView


avatar initials for those w/o a picture

avatar initials for those w/o a picture

Former-commit-id: c36f8dc1ecf56e2df7f209d6b152cfb8fb71dae2 [formerly ea982ce1087bfcdc943dc97d93baff0f2fe2384a] [formerly 5fdcd28254676e26dcd63052aa45ee7a9da1d7b6 [formerly 80cd40d3f5cf3e05f4783851e32bdc70ce4a586d]] [formerly 678c20887ed9655619a91d04e5993995c42b30e1 [formerly ae000e1c52bd74b2a7971a6f58d6fd89e682796c] [formerly 958523b8a8d63017452d8a2a472cd1f263447491 [formerly 958523b8a8d63017452d8a2a472cd1f263447491 [formerly ca37b3d39572471c9e32e8c3a3e56a7ffbffff3a]]]]
Former-commit-id: 98fc8bfcf423d2a0548858e1ce907cb7ff441309 [formerly d7ecd067bd00e0aa2559afa99ac71022f5f2f890] [formerly d7e08fbb8dccdc9fad04c904eea1be7145517988 [formerly 952f1a934bef83494c4fe809e530f4127130c206]]
Former-commit-id: 34b92b6ee1636000909693eb7bf22fd3f48c7914 [formerly 47fe92badb4ed0d16c0f29c9219522144cbea799]
Former-commit-id: eecad4019063d9ef8cbe4fca56da906c209f1056
Former-commit-id: 755b973310ed949c57e36b604bd9e247b447c5fd

--- @client/avatar.coffee
+++ @client/avatar.coffee
@@ -81,7 +81,12 @@
     # isn't set to transparent because a transparent icon in many cases
     # will reveal content behind it that is undesirable to show.
     style.backgroundColor = 'white' if show_avatar
-
+
+    add_initials = !@props.anonymous && !user.avatar_file_name
+
+    if add_initials
+      style.textAlign = 'center'
+
     attrs =
       className: "avatar #{@props.className or ''}"
       id: id
@@ -103,9 +108,30 @@
           save tooltip

     # IE9 gets confused if there is an image without a src
-    tag = if !thumbnail? && browser.is_ie9 && img_size == 'thumb' then SPAN else IMG
+    tag = if !thumbnail? && browser.is_ie9 && img_size == 'thumb' || add_initials then SPAN else IMG

-    @transferPropsTo tag attrs
+
+    @transferPropsTo tag attrs,
+      if add_initials
+        name = (user.name.trim() or 'Anonymous').split(' ')
+        fontsize = style.width / 2
+        ff = 'monaco,Consolas,"Lucida Console",monospace'
+        if name.length == 2
+          name = "#{name[0][0]}#{name[1][0]}"
+        else
+          name = "#{name[0][0]}"
+        SPAN
+          style:
+            color: 'white'
+            pointerEvents: 'none'
+            fontSize: fontsize
+            display: 'block'
+            position: 'relative'
+            fontFamily: ff
+            top: style.height / 2 - heightWhenRendered(name, {fontSize: fontsize, fontFamily: ff}) / 2
+
+          name
+

 styles += """
 .avatar {


preload images

preload images

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -38,10 +38,14 @@

     cubeFragment = document.createDocumentFragment()

-    for key, set  of @sets
+    for key, set of @sets
       @_cubes[key] = @_createCube key
       @_setContent @_cubes[key].front, set[0]
       cubeFragment.appendChild @_cubes[key].el
+      for val in set
+        if @_urlRx.test val
+          image = new Image
+          image.src = val

     @el.classList.add @className
     @el.appendChild cubeFragment


:art: pass softWrap state as arg

:art: pass softWrap state as arg

--- lib/cursor-style-manager.coffee
+++ lib/cursor-style-manager.coffee
@@ -11,22 +11,22 @@

 # Return cursor style offset(top, left)
 # ---------------------------------------
-getOffset = (submode, cursor) ->
+getOffset = (submode, cursor, isSoftWrapped) ->
   {selection, editor} = cursor
   traversal = new Point(0, 0)
   switch submode
     when 'characterwise', 'blockwise'
-      if (not selection.isReversed()) and (not cursor.isAtBeginningOfLine())
+      if not selection.isReversed() and not cursor.isAtBeginningOfLine()
         traversal.column -= 1
     when 'linewise'
       bufferPoint = swrap(selection).getCharacterwiseHeadPosition()

-      traversal = if editor.isSoftWrapped()
+      traversal = if isSoftWrapped
         screenPoint = editor.screenPositionForBufferPosition(bufferPoint)
         screenPoint.traversalFrom(cursor.getScreenPosition())
       else
         bufferPoint.traversalFrom(cursor.getBufferPosition())
-  if (not selection.isReversed()) and cursor.isAtBeginningOfLine()
+  if not selection.isReversed() and cursor.isAtBeginningOfLine()
     traversal.row = -1
   traversal

@@ -76,8 +76,8 @@

     # [FIXME] In spec mode, we skip here since not all spec have dom attached.
     return if isSpecMode
-
+    isSoftWrapped = @editor.isSoftWrapped()
     for cursor in cursorsToShow when cursorNode = getCursorNode(@editorElement, cursor)
-      @subscriptions.add setStyle(cursorNode.style, getOffset(submode, cursor))
+      @subscriptions.add setStyle(cursorNode.style, getOffset(submode, cursor, isSoftWrapped))

 module.exports = CursorStyleManager


Debounce method to update deprecated calls

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -39,6 +39,8 @@
     @subscriptions.add atom.styles.onDidUpdateStyleElement(stylesChanged)
     @subscriptions.add atom.styles.onDidAddStyleElement(stylesChanged)

+    @debouncedUpdateCalls = _.debounce(@updateCalls, 1000)
+
   attached: ->
     @updateCalls()
     @updateSelectors()
@@ -84,7 +86,7 @@
     uri: @getURI()

   handleGrimUpdated: =>
-    @updateCalls()
+    @debouncedUpdateCalls()

   getURI: ->
     @uri


Use skinny arrow

Use skinny arrow

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -97,7 +97,7 @@
       $.ajax "#{url}?q=#{encodeURI(query)}&sort=created",
         accept: 'application/vnd.github.v3+json'
         contentType: 'application/json'
-        success: (data) =>
+        success: (data) ->
           if data.items?
             issues = {}
             for issue in data.items


Change caret icon due state closes #71

Change caret icon due state closes #71

--- wzk/ui/dropup/Dropup.coffee
+++ wzk/ui/dropup/Dropup.coffee
@@ -9,6 +9,7 @@
     BUTTON_TAG: 'li',
     BUTTON_CLASS: 'dropup-button',
     TARGET_ATTRIBUTE: 'target',
+    CARET: 'caret-marker', # do not set to 'caret', as it is would add another arrow
     ARROW_UP: 'fa-angle-up',
     ARROW_LEFT: 'fa-angle-left'

@@ -19,7 +20,7 @@
     DURATION: 500

   ###*
-    @param {wzk.dom.Dom}
+    @param {wzk.dom.Dom} dom
   ###
   constructor: (@dom)->

@@ -28,7 +29,7 @@
   ###
   decorate: (@dropupButton) ->
     # arrow span, allowing change of icon according to state
-    @arrowSpan = @dropupButton.querySelector 'span.fa'
+    @caretElement = @dropupButton.querySelector ".#{wzk.ui.dropup.Dropup.CSS.CARET}"

     # id of tag to dropup
     dropupId = goog.dom.dataset.get @dropupButton, wzk.ui.dropup.Dropup.CSS.TARGET_ATTRIBUTE
@@ -93,12 +94,13 @@
     @protected
   ###
   swapClasses: ->
-    if goog.dom.classes.has @arrowSpan, wzk.ui.dropup.Dropup.CSS.ARROW_LEFT
-      goog.dom.classes.add @arrowSpan, wzk.ui.dropup.Dropup.CSS.ARROW_UP
-      goog.dom.classes.remove @arrowSpan, wzk.ui.dropup.Dropup.CSS.ARROW_LEFT
-    else
-      goog.dom.classes.add @arrowSpan, wzk.ui.dropup.Dropup.CSS.ARROW_LEFT
-      goog.dom.classes.remove @arrowSpan, wzk.ui.dropup.Dropup.CSS.ARROW_UP
+    C = wzk.ui.dropup.Dropup.CSS
+
+    if @caretElement?
+      if goog.dom.classes.has @caretElement, C.ARROW_LEFT
+        goog.dom.classes.addRemove @caretElement, C.ARROW_LEFT, C.ARROW_UP
+      else
+        goog.dom.classes.addRemove @caretElement, C.ARROW_UP, C.ARROW_LEFT

   ###*
     Gets animation object and set's starting height and ending height


recharging now works

recharging now works

--- src/recharge.iced
+++ src/recharge.iced
@@ -38,11 +38,12 @@

   is_good_input_tx : (tx) ->
     amt = tx.amount * SATOSHI_PER_BTC
-    diff = amt - @tx_rough_total
-    if (diff > 0) and (tx.account is account())
+    diff = amt - @tx_rough_total()
+    if (diff > 0) and (tx.account is @account()) and (tx.confirmations >= @min_confirmations())
       ret = -diff # We want the transaction with the most wiggle room, not the least...
     else
       ret = null
+    return ret

   #-----------------------------------

@@ -60,7 +61,7 @@

   check_args : (cb) ->
     err = null
-    if not @to_address()? then err = new Error "no to address to work with (specify with -a)"
+    if not @to_address()? then err = new Error "no to address to work with (specify with -t)"
     else if not @account()? then err = new Error "need to specify an 'account' with -A"
     else if not @num_outputs()? then err = new Error "need to specify # of outputs with -o"
     cb err
@@ -81,12 +82,11 @@

   make_transaction : (cb) ->
     err = null
-    @data_to_address = (new btcjs.Address @to_address(), 0).toBase58Check()
     tx = new btcjs.Transaction
     tx.addInput @input_tx.txid, @input_tx.vout
     num = @num_outputs()
     for i in [0...num]
-      tx.addOutput @data_to_address, @min_amount()
+      tx.addOutput @to_address(), @min_amount()
     skey = btcjs.ECKey.fromWIF @priv_key

     # Sign temporarily with a fictitious amount of change
@@ -95,7 +95,7 @@

     fee = btcjs.networks.bitcoin.estimateFee(tx)
     @change = @input_tx.amount * SATOSHI_PER_BTC - num*@min_amount() - fee
-    if change < 0
+    if @change < 0
       err = new Error "Cannot transfer a negative amount of change"
     else
       tx.outs[change_offset].value = @change
@@ -106,8 +106,9 @@
   #-----------------------------------

   submit_transaction : (cb) ->
-    #await @client.sendRawTransaction @out_tx.toHex(), defer err, @out_tx_id
-    console.log @out_tx.toHex()
+    err = null
+    await @client.sendRawTransaction @out_tx.toHex(), defer err, @out_tx_id
+    console.error "Raw transaction: " + @out_tx.toHex()
     cb err

   #-----------------------------------
@@ -115,7 +116,7 @@
   write_output : (cb) ->
     console.log JSON.stringify {
       @out_tx_id,
-      @data_to_address,
+      to_address : @to_address(),
       @change,
       @change_address,
       input_tx_id : @input_tx.txid
@@ -129,6 +130,7 @@
     await @init argv, esc defer()
     await @find_transaction esc defer()
     await @get_private_key esc defer()
+    await @make_change_address esc defer()
     await @make_transaction esc defer()
     await @submit_transaction esc defer()
     await @write_output esc defer()


:racehorse: Speed up `selectAll` on a buffer with many folds

--- src/selection.coffee
+++ src/selection.coffee
@@ -251,7 +251,8 @@

   # Public: Selects all the text in the buffer.
   selectAll: ->
-    @setBufferRange(@editor.buffer.getRange(), autoscroll: false)
+    @editor.unfoldAll()
+    @setBufferRange(@editor.buffer.getRange(), autoscroll: false, preserveFolds: true)

   # Public: Selects all the text from the current cursor position to the
   # beginning of the line.


render: fix DOM object mutation, addressing comments in #69

--- src/render.coffee
+++ src/render.coffee
@@ -59,38 +59,49 @@
     $head: $head
   }

+assert = (isTrue, message) ->
+  unless isTrue?
+    throw new Error message
+
 renderHead = ($head) ->
   head = flatten $head

-  unless head?.tagName is 'HEAD'
-    throw new Error 'Invalid HEAD base element, not type <head>'
+  assert head?.tagName is 'HEAD', 'Invalid HEAD base element, not type <head>'

   title = head.children?[0]?.children?[0]?.text

-  unless title?
-    throw new Error 'Invalid HEAD base element, missing title'
+  assert title?, 'Invalid HEAD base element, missing title'

-  document.title = title
+  if document.title isnt title
+    document.title = title

-  mutable = _.filter head.children, (node) ->
+  current = _.filter document.head.__lastTree.children, (node) ->
     node.tagName is 'META' or node.tagName is 'LINK'

-  current = _.filter document.head.children, (node) ->
+  $current = document.head.querySelectorAll 'meta,link'
+
+  next = _.filter head.children, (node) ->
     node.tagName is 'META' or node.tagName is 'LINK'

-  if _.isEmpty mutable
+  if _.isEmpty next
     return null

-  unless mutable.length is current.length
-    throw new Error 'Cannot mutate <head> element count dynamically'
+  assert $current.length is current.length, '<head> does not match virtual-dom'
+  assert current.length is next.length,
+    'Cannot mutate <head> element count dynamically'

-  _.map _.zip(current, mutable), ([current, mutable]) ->
-    if current.tagName isnt mutable.tagName
-      throw new Error 'Type mismatch when updating <head>'
+  _.map current, (currentNode, index) ->
+    $currentNode = $current[index]
+    nextNode = next[index]

-    _.map mutable.properties, (val, key) ->
-      if current[key] isnt val
-        current[key] = val
+    assert nextNode.tagName isnt currentNode.tagName,
+      'Type mismatch when updating <head>'
+
+    _.map nextNode.properties, (val, key) ->
+      if currentNode[key] isnt val
+        $currentNode[key] = val
+
+  document.head.__lastTree = head

 module.exports = render = ($$root, tree) ->
   if isComponent tree
@@ -106,6 +117,7 @@

       document.head.__disposable?.dispose()
       hasState = $head.component?.state?
+      document.head.__lastTree = parser document.head

       if hasState
         document.head.__disposable = $head.component.state.subscribe onchange


Define a default template delimiter (mustache-style template delimiters). [#181]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -598,6 +598,7 @@
 # overridden globally or local to a `Rivets.View` instance.
 Rivets.config =
   preloadData: true
+  templateDelimiters: ['{{', '}}']
   handler: (context, ev, binding) ->
     @call context, ev, binding.view.models


Only search for file extension in the basename (after last slash)

--- src/scorer.coffee
+++ src/scorer.coffee
@@ -498,12 +498,12 @@
   end = subject.length - 1
   end-- while subject[end] is PathSeparator

-  # Get a bonus for matching extension
-  extAdjust = 1.0 + getExtensionScore(subject_lw, prepQuery.ext)
-  fullPathScore *= extAdjust
-
   # Get position of basePath of subject.
   basePos = subject.lastIndexOf(PathSeparator, end)
+
+  # Get a bonus for matching extension
+  extAdjust = 1.0 + getExtensionScore(subject_lw, prepQuery.ext, basePos)
+  fullPathScore *= extAdjust

   # no basePath, nothing else to compute.
   return fullPathScore if (basePos is -1)
@@ -566,24 +566,29 @@
   if pos < 0 then ""  else  str.substr(pos + 1)

-getExtensionScore = (candidate, ext) ->
+getExtensionScore = (candidate, ext, basePos) ->
+  # basePos is the position of last slash of candidate, -1 if absent.
+
   return 0 unless ext.length
-  pos = candidate.lastIndexOf(".") + 1
-  return 0 unless pos > 1
-
+
+  # Check that (a) extension exist, (b) it is after the start of the basename
+  pos = candidate.lastIndexOf(".")
+  return 0 unless pos > basePos # (note that basePos >= -1)
+
+  pos++
   n = ext.length
   m = candidate.length - pos

-  #n contain the smallest of both extension length, m the largest.
+  # n contain the smallest of both extension length, m the largest.
   if( m < n)
     n = m
     m = ext.length

-  #count number of matching characters in extension
+  # count number of matching characters in extension
   matched = -1
-  while ++matched < n then break unless candidate[pos + matched] is ext[matched]
-
-  #cannot divide by zero because m is the largest and we return if either is 0
+  while ++matched < n then break if candidate[pos + matched] isnt ext[matched]
+
+  # cannot divide by zero because m is the largest extension length and we return if either is 0
   return  matched / m

 #


[Client][Activity] Remove unused functions

--- client/Social/Activity/activitylistcontroller.coffee
+++ client/Social/Activity/activitylistcontroller.coffee
@@ -66,11 +66,8 @@
     @hideLazyLoader()
     return  unless activities.length > 0
     activityIds = []
-    for activity in activities when activity
-      @addItem activity
-      activityIds.push activity._id
-
-    @checkIfLikedBefore activityIds
+    queue = []
+
     activities.forEach (activity)=>
       queue.push =>
         @addItem activity
@@ -97,19 +94,6 @@
         if likeView
           likeView.setClass "liked"
           likeView._currentState = yes
-
-  getLastItemTimeStamp: ->
-
-    if item = @hiddenItems.first
-      item.getData().createdAt or item.getData().createdAtTimestamps.last
-    else
-      @lastItemTimeStamp
-
-  followedActivityArrived: (activity) ->
-
-    if @_state is 'private'
-      view = @addHiddenItem activity, 0
-      @activityHeader?.newActivityArrived()

   logNewActivityArrived:(activity)->
     id = activity.getId?()


Fix: Line 257 contains inconsistent indentation.

--- src/uploader.coffee
+++ src/uploader.coffee
@@ -254,31 +254,31 @@
         ), @waitTime
       ], (err) =>

-        clearInterval checkPartsInterval
-
-        @emit 'finishing'
-
+      clearInterval checkPartsInterval
+
+      @emit 'finishing'
+
+      if err
+        return @emit 'failed', err
+
+      @getNewClient().completeMultipartUpload
+        UploadId: @uploadId
+        Bucket:   @objectParams.Bucket
+        Key:      @objectParams.Key
+        MultipartUpload: Parts: ({'ETag': etag, 'PartNumber': parseInt(partNumber, 10)} for partNumber, etag of @uploadedParts)
+      , (err, data) =>
+        @emit 'finished', data
         if err
+          @emit 'error', err
+          @failed = true
+
+        if @failed
+          @getNewClient().abortMultipartUpload
+            UploadId: @uploadId
+            Bucket:   @objectParams.Bucket
+            Key:      @objectParams.Key
           return @emit 'failed', err
-
-        @getNewClient().completeMultipartUpload
-          UploadId: @uploadId
-          Bucket:   @objectParams.Bucket
-          Key:      @objectParams.Key
-          MultipartUpload: Parts: ({'ETag': etag, 'PartNumber': parseInt(partNumber, 10)} for partNumber, etag of @uploadedParts)
-        , (err, data) =>
-          @emit 'finished', data
-          if err
-            @emit 'error', err
-            @failed = true
-
-          if @failed
-            @getNewClient().abortMultipartUpload
-              UploadId: @uploadId
-              Bucket:   @objectParams.Bucket
-              Key:      @objectParams.Key
-            return @emit 'failed', err
-          @emit 'completed', err, location: data.Location, bucket: data.Bucket, key: data.Key, etag: data.ETag, expiration: data.Expiration, versionId: data.VersionId
+        @emit 'completed', err, location: data.Location, bucket: data.Bucket, key: data.Key, etag: data.ETag, expiration: data.Expiration, versionId: data.VersionId

 module.exports =
   Uploader: Uploader


newrelic: Nice error if body.facets is empty

--- src/utils/newrelic.coffee
+++ src/utils/newrelic.coffee
@@ -11,6 +11,7 @@
   q = "SELECT average(duration), stddev(duration), percentile(duration, 90) FROM MsgfloJobCompleted FACET role SINCE #{options.since} ago WHERE outport != 'error' AND appName = '#{app}' LIMIT #{limit}"
   insights.query q, (err, body) ->
     return callback err if err
+    return callback new Error "newrelic: No facets was returned in query" if not body.facets?.length

     results = {}
     for facet in body.facets


set focus when search button clicked

set focus when search button clicked

--- src/scripts/views/header-view.coffee
+++ src/scripts/views/header-view.coffee
@@ -44,6 +44,9 @@

   _onClick: ->
     getEntryService().setSearchVisible not @props.visible
+    setTimeout =>
+      @refs.input.getDOMNode().focus() if @props.visible
+    , 100
     null

   _onKeyDown: (e) ->


Add new commands

Add new commands

--- src/worldcup.coffee
+++ src/worldcup.coffee
@@ -2,10 +2,11 @@
 #   A way to get basic info and updates on the 2014 World Cup
 #
 # Commands:
-#   hubot wc today                - Returns a list of World Cup matches today
-#   hubot wc tomorrow             - Returns a list of World Cup matches tomorrow
-#   hubot wc teams                - Returns a list of teams in the World Cup
-#   hubot wc more <team acronym>  - Returns a link to FIFA for the team to see news, rosters, etc.
+#   hubot wc today                  - Returns a list of World Cup matches today
+#   hubot wc tomorrow               - Returns a list of World Cup matches tomorrow
+#   hubot wc teams                  - Returns a list of teams in the World Cup
+#   hubot wc more <team acronym>    - Returns a link to FIFA to see news, rosters, etc. for a given team
+#   hubot wc <red or yellow> <name> - Give someone a red/yellow card

 module.exports = (robot) ->
   robot.respond /(worldcup|wc)( today)/i, (msg) ->


Prevent insert ObjectID as string; mongodb/node-mongodb-native#1115

--- coffee/collection.coffee
+++ coffee/collection.coffee
@@ -34,7 +34,7 @@

   ret = []
   for item, i in req.body
-    # @TODO item._id
+    item._id = ObjectID(item._id) if item._id # @TODO restrict this
     # @TODO item.opprettet
     # @TODO item.endret
     # @TODO item.tilbyder


Solved second problem

Solved second problem

--- coffescript/02/02.coffee
+++ coffescript/02/02.coffee
@@ -1,7 +1,8 @@
 fibonacci = (first=0, second=1, range=34) ->
   num = first
+  next = 0
   arr = []
-  while num <= range
+  while next <= range
     if num <= 1
       next = num
     else
@@ -9,7 +10,7 @@
       first = second
       second = next
     arr.push(next)
-    num = next
+    num++

   return arr


remove extra catches in test

remove extra catches in test

--- test/bitballoon.coffee
+++ test/bitballoon.coffee
@@ -14,12 +14,10 @@
         access_token: config.bitballoon.access_token

     project.deploy()
-      .catch (err) -> console.error(err); throw err
       .tap (res) ->
         node.call(request, res.url)
         .tap (r) -> r[0].body.should.match /bitballoon deployer working, yay!/
       .then -> project.deploy()
-      .catch (err) -> console.error(err); throw err
       .tap (res) -> res.destroy()
       .catch (err) -> console.error(err); throw err
       .should.be.fulfilled


GameObject.Effect

GameObject.Effect

--- src/fadeable.coffee
+++ src/fadeable.coffee
@@ -1,56 +0,0 @@
-###*
-The Fadeable module provides a method to fade a sprite to transparent.
-You may also provide a callback function that is executed when the sprite has finished fading out.
-
-@name Fadeable
-@module
-@constructor
-@param {Object} I Instance variables
-@param {Core} self Reference to including object
-###
-Fadeable = (I, self) ->
-  Object.reverseMerge I,
-    fadeDuration: 30
-    fadeCooldown: null
-    fadeCallback: null
-
-  self.bind "update", ->
-    if I.fadeCooldown?
-      I.fadeCooldown = I.fadeCooldown.approach(0, 1)
-      I.alpha = I.fadeCooldown / I.fadeDuration
-
-    if I.fadeCooldown == 0
-      I.fadeCooldown = null
-      I.fadeCallback?(self)
-
-  ###*
-  A convenient way to set the fade instance variables on a sprite. You can modify the
-  instance variables by hand but the suggested way to do it is through this method.
-
-      player = GameObject()
-
-      player.include(Fadeable)
-
-      fadedOut = false
-
-      # this will fade the player object out over the next 30 frames.
-      # once the player is faded out the fadedOut variable will be set to true.
-      player.fadeOut 30, (player) ->
-        fadedOut = true
-
-      30.times ->
-        player.update()
-
-      fadedOut
-      # => true
-
-  @name fadeOut
-  @methodOf Fadeable#
-  @param {Number} [duration=30] How long the effect lasts
-  @param {Function} [callback=null] The function to execute when the sprite has finished fading.
-  ###
-  fadeOut: (duration = 30, callback) ->
-    I.fadeDuration = duration
-    I.fadeCooldown = duration
-    I.fadeCallback = callback
-


add a comma

add a comma

--- src/webpack.config.coffee
+++ src/webpack.config.coffee
@@ -539,7 +539,7 @@
                     { loader: 'coffee-loader' }
                 ]
             },
-            { test: /\.(js|jsx)$/, loader: 'babel-loader' }
+            { test: /\.(js|jsx)$/, loader: 'babel-loader' },
             { test: /\.less$/,   use: ["style-loader", "css-loader", "less-loader?#{cssConfig}"] },
             { test: /\.scss$/,   use: ["style-loader", "css-loader", "sass-loader?#{cssConfig}"] },
             { test: /\.sass$/,   use: ["style-loader", "css-loader", "sass-loader?#{cssConfig}&indentedSyntax"] },


cleanup

cleanup

--- src/donations-form-model.coffee
+++ src/donations-form-model.coffee
@@ -8,22 +8,22 @@
     }, self.parseQueryString(document.URL.split("?")[1]))

     ko.validation.configure({
-      insertMessages: false,
+      insertMessages: false
     });
     ko.validation.rules['ccDate'] = {
-        validator: (val, otherVal) ->
-          return $.payment.validateCardExpiry(val.month, val.year);
-        , message: 'Invalid date'
+      validator: (val, otherVal) ->
+        return $.payment.validateCardExpiry(val.month, val.year);
+      , message: 'Invalid date'
     }
     ko.validation.rules['ccNum'] = {
-        validator: (val, otherVal) ->
-          return $.payment.validateCardNumber(val);
-        , message: 'Invalid credit card number'
+      validator: (val, otherVal) ->
+        return $.payment.validateCardNumber(val);
+      , message: 'Invalid credit card number'
     }
     ko.validation.rules['cvc'] = {
-        validator: (val, otherVal) ->
-          return $.payment.validateCardCVC(val);
-        , message: 'Invalid credit card number'
+      validator: (val, otherVal) ->
+        return $.payment.validateCardCVC(val);
+      , message: 'Invalid credit card number'
     }
     ko.validation.registerExtenders()


:fire: console.log

:fire: console.log

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -92,7 +92,6 @@
     notificationContainer = @element.querySelector('.message')

     if DOMPurify is null
-      console.log("creating DOMPurify")
       DOMPurify = createDOMPurify()
     notificationContainer.innerHTML = DOMPurify.sanitize(marked(@model.getMessage()))


Added an extra check to probably fix a bug when updating to latest version

--- lib/project-manager-view.coffee
+++ lib/project-manager-view.coffee
@@ -63,5 +63,4 @@

   sortBy: (arr, key) ->
     arr.sort (a, b) ->
-      a[key].toUpperCase() > b[key].toUpperCase()
-
+      (a[key] || '\uffff').toUpperCase() > (b[key] || '\uffff').toUpperCase()


small modifications to the routerTest script

--- test/routerTest.coffee
+++ test/routerTest.coffee
@@ -339,15 +339,15 @@
     			"routes": [
         					{
             					"name": "dummy-route"
-            					"request": { ... }
-            					"response": { ... }
+            					"request": {}
+            					"response": {}
         					}
     					]
     			"orchestrations": [
         							{
             							"name": "dummy-orchestration"
-            							"request": { ... }
-            							"response": { ... }
+            							"request": {}
+            							"response": {}
         							}
     							]
     			"properties": [


fix for tab styling on dark background

fix for tab styling on dark background

--- @client/banner.coffee
+++ @client/banner.coffee
@@ -985,10 +985,6 @@
           background-color: white;
           color: black;
         }
-        .dark .PhotoBanner #tabs > ul > li.selected > h4, .dark .PhotoBanner #tabs > ul > li.hovered > h4 {
-          background-color: black;
-          color: white;
-        }
         .PhotoBanner #tabs > ul > li.selected, .PhotoBanner #tabs > ul > li.hovered {
           background-color: #{tab_background_color};
         }


fixed bug where substitutions were not matching within i18n-values handler

--- src/lib/i18n.coffee
+++ src/lib/i18n.coffee
@@ -35,23 +35,23 @@
   # Replace the value of the properties and/or attributes of `element` with the
   # messages looked up for their corresponding values.
   'i18n-values':  (element, value, map) ->
-    subs  = subst element, value, map
     parts = value.replace(/\s/g, '').split ';'
     for part in parts
       prop = part.match /^([^:]+):(.+)$/
       if prop
         propName = prop[1]
         propExpr = prop[2]
+        propSubs = subst element, propExpr, map
         if propName.indexOf('.') is 0
           path = propName.slice(1).split '.'
           obj  = element
           obj  = obj[path.shift()] while obj and path.length > 1
           if obj
             path = path[0]
-            obj[path] = i18n.get propExpr, subs
+            obj[path] = i18n.get propExpr, propSubs
             process element, map if path is 'innerHTML'
         else
-          element.setAttribute propName, i18n.get propExpr, subs
+          element.setAttribute propName, i18n.get propExpr, propSubs
 # List of internationalization attributes/handlers available.
 attributes = []
 attributes.push key for own key of handlers


Change confirmation message for deleting projects

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -507,6 +507,11 @@
     toggle_hide_project : ->
         @actions('projects').toggle_hide_project(@props.project.get('project_id'))

+    # account_id : String
+    # project    : immutable.Map
+    user_has_applied_upgrades : (account_id, project) ->
+         project.getIn(['users', account_id, 'upgrades']).some (val) => val > 0
+
     delete_message : ->
         if @props.project.get('deleted')
             <DeletedProjectWarning/>
@@ -543,13 +548,17 @@
         </Button>

     render_expanded_delete_info : ->
-        <Well>
-            <Alert bsStyle="info" style={padding:'8px'} >
+        has_upgrades = @user_has_applied_upgrades(salvus_client.account_id, @props.project)
+        <Well style={textAlign:'center'} >
+            {<Alert bsStyle="info" style={padding:'8px'} >
                 All of your upgrades from this project will be removed automatically.
                 Undeleting the project will not automatically restore them.
                 This will not affect upgrades other people have applied.
-            </Alert>
-            <ButtonToolbar style={textAlign:'center'} >
+            </Alert> if has_upgrades}
+            {<div style={marginBottom:'5px'} >
+                Are you sure you want to delete this project?
+            </div> if not has_upgrades}
+            <ButtonToolbar >
                 <Button bsStyle='danger' onClick={@toggle_delete_project}>
                     Delete Project
                 </Button>


add left and right margins of 2 em for public view of .md files

--- src/smc-webapp/public/editor_md.cjsx
+++ src/smc-webapp/public/editor_md.cjsx
@@ -45,7 +45,7 @@
         else if not @props.content?
             <Loading />
         else
-

+

                 <Markdown project_id={@props.project_id} file_path={@props.file_path} value={@props.content} />


you can only accept an invite if you don't have a role

--- controllers/login.coffee
+++ controllers/login.coffee
@@ -19,7 +19,7 @@
 app.get '/login/done', [ensureAuth, loadPerson, loadPersonTeam], (req, res, next) ->
   if !req.person
     return next 401
-  else if invite = req.session.invite
+  else if not req.user.role and (invite = req.session.invite)
     Team.findOne 'invites.code': invite, (err, team) ->
       return next err if err
       if team


Remove white space.

Remove white space.

--- js/src/service/caching-provider/CachingService.coffee
+++ js/src/service/caching-provider/CachingService.coffee
@@ -17,13 +17,13 @@
   #
   class CachingService

-    @USERS                 = 'users'
-    @UUIDS                 = 'uuids'
-    @SALTS                 = 'user_salts'
-    @OBJECTS               = 'objects'
-    @PUBLIC_KEYS           = 'public_keys'
-    @DEFAULT_GROUP         = 'default_group'
-    @CRYPTO_SERVICES       = 'object_crypto_services'
+    @USERS           = 'users'
+    @UUIDS           = 'uuids'
+    @SALTS           = 'user_salts'
+    @OBJECTS         = 'objects'
+    @PUBLIC_KEYS     = 'public_keys'
+    @DEFAULT_GROUP   = 'default_group'
+    @CRYPTO_SERVICES = 'object_crypto_services'

     @MASTER_AES_CRYPTO_SERVICE           = 'master_aes_crypto_service'
     @MASTER_AES_CRYPTO_SERVICE_ENCRYPTED = 'master_aes_crypto_service_encrypted'


Check for existence before copying

Check for existence before copying

--- tasks/task-helpers.coffee
+++ tasks/task-helpers.coffee
@@ -4,6 +4,9 @@

 module.exports = (grunt) ->
   cp: (source, destination, {filter}={}) ->
+    unless grunt.file.exists(source)
+      grunt.fatal("Cannot copy non-existent #{source.cyan} to #{destination.cyan}")
+
     try
       walkdir.sync source, (sourcePath, stats) ->
         return if filter?.test(sourcePath)


default start for opentsdb

default start for opentsdb

--- src/opentsdb.coffee
+++ src/opentsdb.coffee
@@ -36,6 +36,7 @@
         datapoints: _.sortBy points, ([v, t]) -> t

     data_url: ({time_series, start, end, aggregation, group}) ->
+      start ?= '1d-ago'
       m = _.map time_series, opentsdb.to_metric_string
       params = {start, end, m, ascii: true}
       base_url = settings.get 'base_url'


Added 1ms delay

Added 1ms delay

--- test/cwebp.coffee
+++ test/cwebp.coffee
@@ -48,7 +48,7 @@
         filename.should.not.be.empty
         fs.existsSync(filename).should.be.true
         promise
-      .then ->
+      .delay(1).then ->
         throw new Error 'Should not be fulfilled'
       , (err) ->
         err.should.be.Error


Disable logging in test again (oops)

Disable logging in test again (oops)

--- test/front.coffee
+++ test/front.coffee
@@ -2,7 +2,6 @@
 WebSocket = require('ws')
 WebSocketServer = require('ws').Server

-logger.set_levels 'development'
 describe 'Front', ->
   beforeEach ->
     @front = new Front


Added test for one read lock holder extending its lock while another read lock holder already has a much later expiring lock. This this case the new extender should not impact the lock document expire time.

--- test/index.coffee
+++ test/index.coffee
@@ -413,6 +413,7 @@

     describe 'renewLock', () ->
       lock1 = null
+      lock2 = null
       id = null

       before (done) ->
@@ -431,6 +432,17 @@
           assert expiresBefore <= ld.expires
           assert.equal ld.expires, lock1.heldLock.expires
           done()
+
+      it "shouldn't decrease the time to expire of a read lock when another lock holder already has a longer expiration", (done) ->
+        lock2 = Lock id, lockColl, { lockExpiration: 600 }
+        lock2.obtainReadLock().on 'locked', (ld) ->
+          assert ld?
+          expiresBefore = lock2.heldLock.expires
+          lock1.renewLock().on 'renewed', (ld) ->
+            assert.equal expiresBefore.getTime(), ld.expires.getTime()
+            assert.equal ld.expires.getTime(), lock1.heldLock.expires.getTime()
+            lock2.releaseLock().on 'released', (ld) ->
+              done()

       it "should fail to renew an unheld lock", (done) ->
         lock1.releaseLock().on 'released', (ld) ->


move base view constructor to top

move base view constructor to top

--- src/js/views/base.coffee
+++ src/js/views/base.coffee
@@ -1,4 +1,10 @@
 class BaseView extends Backbone.View
+
+  constructor: ->
+    @children = []
+    @contentChildren = []
+    super
+
   render: =>
     @preRender?()
     @viewAttrs ?= @model?.toJSON() ? {}
@@ -94,9 +100,4 @@
     index = _.indexOf(@children, view)
     @children.splice(index, 1) if index isnt -1

-  constructor: ->
-    @children = []
-    @contentChildren = []
-    super
-
 module.exports = BaseView


fix(): ensure `setLocale()` always returns a `Promise`

--- src/madlib-locale.coffee
+++ src/madlib-locale.coffee
@@ -49,108 +49,115 @@

         initialize: ( Handlebars, locale, localeLocation ) ->

-            if @initialized is false
+            if @initialized

-                @initialized    = true
+                error   = '[LocaleManager] Already initialized'

-                ##  Set location if given
-                ##
-                @localeLocation = localeLocation if localeLocation?
-
-                ##  Create our polyglot instance
-                ##  and load the default phrases
-                ##
-                @polyglot       =
-                    new Polyglot(
-                        locale:     objectUtils.getValue( 'name',    @locale, '??' )
-                        phrases:    objectUtils.getValue( 'phrases', @locale, {}   )
-                    )
-
-                ##  Register the handlebars helper(s)
-                ##
-                date            = _.bind( @date, @ )
-                money           = _.bind( @money, @ )
-                number          = _.bind( @number, @ )
-                translate       =
-
-                    ( key, args..., meta ) =>
-
-                        interpolation   = if args.length then args else meta.hash
-
-                        @translate( key, interpolation )
+                console.error( error )
+                return Q.reject( error )

-                Handlebars.registerHelper( '_translate',                translate   )
-                Handlebars.registerHelper( 't',                         translate   )
-                Handlebars.registerHelper( 'T', _.compose( capitalize,  translate ) )
+            @initialized    = true

-                Handlebars.registerHelper( '_date',                     date )
-                Handlebars.registerHelper( 'D',                         date )
+            ##  Set location if given
+            ##
+            @localeLocation = localeLocation if localeLocation?

-                Handlebars.registerHelper( '_money',                    money )
-                Handlebars.registerHelper( 'M',                         money )
+            ##  Create our polyglot instance
+            ##  and load the default phrases
+            ##
+            @polyglot       =
+                new Polyglot(
+                    locale:     objectUtils.getValue( 'name',    @locale, '??' )
+                    phrases:    objectUtils.getValue( 'phrases', @locale, {}   )
+                )
+
+            ##  Register the handlebars helper(s)
+            ##
+            date            = _.bind( @date, @ )
+            money           = _.bind( @money, @ )
+            number          = _.bind( @number, @ )
+            translate       =
+
+                ( key, args..., meta ) =>
+
+                    interpolation   = if args.length then args else meta.hash
+
+                    @translate( key, interpolation )

-                Handlebars.registerHelper( '_number',                   number )
-                Handlebars.registerHelper( 'N',                         number )
+            Handlebars.registerHelper( '_translate',                translate   )
+            Handlebars.registerHelper( 't',                         translate   )
+            Handlebars.registerHelper( 'T', _.compose( capitalize,  translate ) )

-                ##  Set the default locale and return promise
-                ##
-                return @setLocale( locale )
+            Handlebars.registerHelper( '_date',                     date )
+            Handlebars.registerHelper( 'D',                         date )

-            else
-                console.error( '[LocaleManager] Already initialized' )
+            Handlebars.registerHelper( '_money',                    money )
+            Handlebars.registerHelper( 'M',                         money )

-                return Q.reject( '[LocaleManager] Already initialized' )
+
+            Handlebars.registerHelper( '_number',                   number )
+            Handlebars.registerHelper( 'N',                         number )
+
+            ##  Set the default locale and return promise
+            ##
+            return @setLocale( locale )
+

         setLocale: ( locale ) ->

-            if @initialized is true
+            unless @initialized

-                deferred = Q.defer()
+                error   = '[LocaleManager] Tried to set locale before initializion.'

-                # Check if the locale is in the cache
+                console.error( error )
+                return Q.reject( error )
+
+
+            deferred = Q.defer()
+
+            # Check if the locale is in the cache
+            #
+            if @cache[ locale ]?
+                @locale = @cache[ locale ]
+                @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
+                @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )
+
+                deferred.resolve()
+            else
+                # Load the new locale phrases
                 #
-                if @cache[ locale ]?
-                    @locale = @cache[ locale ]
+                xhr = new XHR( settings )
+                xhr.call(
+                    url:    "#{@localeLocation}/#{locale}.json"
+                    type:   'json'
+                    method: 'GET'
+                )
+                .then( ( data ) =>
+                    # Set polyglot locale and phrases on success
+                    #
+                    @locale = data.response
+
                     @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
                     @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )

+                    # Add the default locale to the cache
+                    #
+                    @cache[ @locale.name ] = @locale
+
                     deferred.resolve()
-                else
-                    # Load the new locale phrases
-                    #
-                    xhr = new XHR( settings )
-                    xhr.call(
-                        url:    "#{@localeLocation}/#{locale}.json"
-                        type:   'json'
-                        method: 'GET'
-                    )
-                    .then( ( data ) =>
-                        # Set polyglot locale and phrases on success
-                        #
-                        @locale = data.response

-                        @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
-                        @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )
+                ,   ( error ) ->
+                    console.error( "[i18n] Failed to load locale #{locale}")
+                    deferred.reject( error )
+                )
+                .done()

-                        # Add the default locale to the cache
-                        #
-                        @cache[ @locale.name ] = @locale
+            return deferred.promise

-                        deferred.resolve()

-                    ,   ( error ) ->
-                        console.error( "[i18n] Failed to load locale #{locale}")
-                        deferred.reject( error )
-                    )
-                    .done()
-
-                return deferred.promise
-
-            else
-                console.error( '[LocaleManager] Tried to set locale before initializing' )

         getLocaleName: () ->
             return @locale.name


update gulp plumber error handle.

update gulp plumber error handle.

Signed-off-by: Bo-Yi Wu <f80eec1bf98b3feebacc247f35f2b506218004cb@gmail.com>

--- templates/gulpfile.coffee
+++ templates/gulpfile.coffee
@@ -56,14 +56,16 @@

 gulp.task 'styles', ->
   gulp.src <% if (includeCss) { %>paths.css + '/**/*.css'<% } else { %>paths.sass + '/**/*.scss'<% } %><% if (!includeCss) { %>
-    .pipe $.plumber()
+    .pipe $.if !production, $.plumber
+      errorHandler: (error) ->
+        $.util.log $.util.colors.red error.message
+        this.emit 'end'
     .pipe $.if !production, $.changed paths.css,
       extension: '.css'<% if (includeCompass) { %>
     .pipe $.compass
       css: paths.css
       sass: paths.sass
-      image: paths.image
-    .on('error', ->)<% } else { %>
+      image: paths.image<% } else { %>
     .pipe $.rubySass
       style: 'expanded'
       precision: 10<% } %><% } %>


init test

init test

--- example/coffee/test-main.coffee
+++ example/coffee/test-main.coffee
@@ -140,6 +140,8 @@
 			window.App.change('admin')
 		else
 			alert('バルス')
+
+	# Kai.init({'css':'hoge'})
 	console.log Kai.GET_CSS_PATH(Kai.RELATIVE)

 clickHandler =(event)->


Add test case for #37.

Add test case for #37.

--- test/tests.coffee
+++ test/tests.coffee
@@ -60,3 +60,21 @@
         assert _$jscoverage?, "Code should have been instrumented"
         assert ('a/foo.coffee' of _$jscoverage), "Should instrument a/foo.coffee"
         assert !('b/bar.coffee' of _$jscoverage), "Should not instrument b/bar.coffee"
+
+    it "should handle nested recursion correctly", ->
+        # From https://github.com/benbria/coffee-coverage/pull/37
+        instrumentor = new coffeeCoverage.CoverageInstrumentor()
+        source = """
+            z = 0
+            for i in [0...2]
+                for j in [0...5]
+                    z++
+
+            return z
+        """
+
+        code = instrumentor.instrumentCoffee("example.coffee", source).js
+
+        _$jscoverage = {"example.coffee": {}}
+        z = eval code
+        assert.equal z, 10


fix dashboard calculation display when submeasure calculations do not finish concurrently

--- app/assets/javascripts/dashboard.js.coffee
+++ app/assets/javascripts/dashboard.js.coffee
@@ -1,11 +1,13 @@
 @Dashboard = {
 	calculateMeasure: (response) ->
 		$.each response, (i, result) ->
-			row = Dashboard.measureRow(result.measure_id, result.sub_id)
-			row.fadeTo("fast", 1.0)
-			Render.percent row.find("div.measureProviderPopulationPercentage"), result
-			Render.barChart row.find("div.tableBar"), result
-			Render.fraction row.find("td.fraction"), result
+			Dashboard.calculateSingleMeasure(result)
+	calculateSingleMeasure: (result) ->
+		row = Dashboard.measureRow(result.measure_id, result.sub_id)
+		row.fadeTo("fast", 1.0)
+		Render.percent row.find("div.measureProviderPopulationPercentage"), result
+		Render.barChart row.find("div.tableBar"), result
+		Render.fraction row.find("td.fraction"), result
 	measureRow: (measure, sub_id) ->
 		selector = "tr.measure[data-measure='#{measure}']"
 		selector += "[data-measure-sub='#{sub_id || ''}']" if sub_id?
@@ -36,8 +38,6 @@
 		Page.onMeasureRemove = (measure) ->
 			Dashboard.fadeOut(measure)
 			measureRow = Dashboard.measureRow(measure).first()
-			console.log(measureRow.prevUntil(".headerRow", ":not([data-measure='#{measure}']):visible"))
-			console.log(measureRow.nextUntil(".headerRow", ":not([data-measure='#{measure}']):visible"))
 			Dashboard.measureRow(measure).prevAll(".headerRow").first().hide() if measureRow.prevUntil(".headerRow", ":not([data-measure='#{measure}']):visible").length == 0 && measureRow.nextUntil(".headerRow", ":not([data-measure='#{measure}']):visible").length == 0

 		Page.onReportComplete = (result, complete) ->
@@ -45,11 +45,14 @@
 				Dashboard.calculateMeasure(result)
 			else
 				$.each result, (i, data) ->
-					row = Dashboard.measureRow(data.job.measure_id, data.job.sub_id)
-					if (data.job.status != 'failed')
-						row.find('.jobLabel').text(data.job.status)
+					if data.job?
+						row = Dashboard.measureRow(data.job.measure_id, data.job.sub_id)
+						if (data.job.status != 'failed')
+							row.find('.jobLabel').text(data.job.status)
+						else
+							row.find('.jobLabel').parent().html(data.job.status)
 					else
-						row.find('.jobLabel').parent().html(data.job.status)
+						Dashboard.calculateSingleMeasure(data)

 		$('#btnExportReport').click(Dashboard.exportReport);
 		$('#btnExportReportSingle').click(Dashboard.doReportExport);


trigger an ajaxSuccess event on the form element on ajax success

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -97,6 +97,7 @@
       #   log "Sending these data to the server: #{JSON.stringify data}"
       success: (serverData) =>
         this.handleServerData serverData
+        @form.trigger 'ajaxSuccess', serverData
         # log "Successful form POST; got #{JSON.stringify(serverData)}"
       error: (event, status, errorThrown) =>
         log "Error response: #{event.status} #{status} - #{errorThrown} - #{event.responseText}"


metadata key is "metadata", not "meta"

metadata key is "metadata", not "meta"

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -234,4 +234,4 @@
   window.hatTrick = {} unless window.hatTrick?
   unless window.hatTrick.wizard?
     console.log "Creating new HatTrickWizard instance"
-    window.hatTrick.wizard = new HatTrickWizard($form, hatTrick.meta)
+    window.hatTrick.wizard = new HatTrickWizard($form, hatTrick.metadata)


OX-162 changed zooming speed to nonlinear

--- app/assets/javascripts/libs/flycam2.coffee
+++ app/assets/javascripts/libs/flycam2.coffee
@@ -35,14 +35,15 @@
   #  @zoomSteps=[1,1,1]

   zoomIn : (planeID) ->
-    @zoomSteps[planeID] -= ZOOM_DIFF
+    @zoomSteps[planeID] -= (1 + @zoomSteps[planeID]) / 1.5 * ZOOM_DIFF
     @hasChanged = true
     @buffer[planeID] = TEXTURE_WIDTH/2-@viewportWidth*@getTextureScalingFactor(planeID)/2

   zoomOut : (planeID) ->
     # Make sure the max. zoom Step will not be exceded
-    if @zoomSteps[planeID] < 3+@maxZoomStepDiff - ZOOM_DIFF
-      @zoomSteps[planeID] += ZOOM_DIFF
+    delta = (1 + @zoomSteps[planeID]) / 1.5 * ZOOM_DIFF
+    if @zoomSteps[planeID] < 3+@maxZoomStepDiff - delta
+      @zoomSteps[planeID] += delta
       @hasChanged = true
       @buffer[planeID] = TEXTURE_WIDTH/2-@viewportWidth*@getTextureScalingFactor(planeID)/2


Handle wider range of printable unicode characters

--- app/assets/javascripts/player/vt.js.coffee
+++ app/assets/javascripts/player/vt.js.coffee
@@ -41,7 +41,7 @@
       @handleControlCharacter(data[0])
       return 1

-    else if match = data.match(/^([\x20-\x7e]|[\xe2-\xe8]..|[\xa1-\xfe])+/)
+    else if match = data.match(/^([\x20-\x7e]|[\xe2-\xe8]..|[\xc3-\xc5].|[\xa1-\xfe])+/)
       @handlePrintableCharacters(match[0])
       return match[0].length


Tooltip alignment.

Tooltip alignment.

--- app/assets/javascripts/web/_site.js.coffee
+++ app/assets/javascripts/web/_site.js.coffee
@@ -2733,7 +2733,7 @@
         if bar.offsetLeft < parentWidth / 2
           tooltipTarget.removeClass("right")
           tooltipTarget.css
-            left: "#{bar.offsetLeft - 18}px"
+            left: "#{bar.offsetLeft - 14}px"
         else
           tooltipTarget.addClass("right")
           tooltipTarget.css


Fix offer created

Fix offer created

--- app/assets/javascripts/webrtc_rails.coffee
+++ app/assets/javascripts/webrtc_rails.coffee
@@ -72,6 +72,10 @@
     media:
       audio: true
       video: true
+    offer:
+      offerToReceiveAudio: 1,
+      offerToReceiveVideo: 1,
+      iceRestart: true

   constructor: (options)->
     @options = $.extend {}, @defaultOptions, WebRTC.defaultOptions, options
@@ -363,10 +367,10 @@

   sendOffer: ->
     @source = true
-    @connection.createOffer ((offer) =>
+    @connection.createOffer(@client.options.offer).then((offer) =>
       @connection.setLocalDescription offer
       @syncEngine.sendOffer @guid, offer
-    ), ->
+    ).catch (error)->

   handleOffer: (offer) ->
     @source = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -9,7 +9,10 @@
   obj = GameObject
     x: 50
     y: 50
+    width: 32
+    height: 32

+

 test "should remove objects outside of activeBounds", 2, ->
   obj = GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -19,6 +19,6 @@

   obj.update()

-  ok destroySpy, 'obj should be d'
+  ok destroySpy, 'obj should be destroyed whe '

 module()


不要なカッコを追加してしまったので削除

不要なカッコを追加してしまったので削除

--- test/calc-box.spec.coffee
+++ test/calc-box.spec.coffee
@@ -202,4 +202,3 @@
       it "MARK IV箱に入ること", ->
         box = new calcBox MARK_IV
         box.canContain(parcel).should.be.true
-)


Rename integration api routes, add apis remove, info and sample

--- packages/rocketchat-integrations/server/api/api.coffee
+++ packages/rocketchat-integrations/server/api/api.coffee
@@ -103,8 +103,11 @@
 				success: true

-Api.addRoute 'manageintegrations/:integrationId/:userId/:token', authRequired: true,
+Api.addRoute 'add/:integrationId/:userId/:token', authRequired: true,
 	post: ->
+		console.log 'Add integration'
+		console.log @bodyParams
+
 		if @bodyParams?.payload?
 			@bodyParams = JSON.parse @bodyParams.payload

@@ -118,12 +121,88 @@
 					success: false
 					error: 'Invalid integraiton id'

-		switch @bodyParams.action
-			when 'addOutgoingIntegration'
-				Meteor.runAsUser user._id, =>
-					Meteor.call 'addOutgoingIntegration', @bodyParams.data
-
-		return {} =
-			statusCode: 200
-			body:
-				success: true
+		Meteor.runAsUser user._id, =>
+			switch @bodyParams['event']
+				when 'newMessageOnChannel'
+					if @bodyParams.data.channel_name? and @bodyParams.data.channel_name.indexOf('#') is -1
+						@bodyParams.data.channel_name = '#' + @bodyParams.data.channel_name
+
+					Meteor.call 'addOutgoingIntegration',
+						username: 'rocket.cat'
+						urls: [@bodyParams.target_url]
+						name: @bodyParams.data.name
+						channel: @bodyParams.data.channel_name
+						triggerWords: @bodyParams.data.trigger_words
+
+				when 'newMessageToUser'
+					if @bodyParams.data.username.indexOf('@') is -1
+						@bodyParams.data.username = '@' + @bodyParams.data.username
+
+					Meteor.call 'addOutgoingIntegration',
+						username: 'rocket.cat'
+						urls: [@bodyParams.target_url]
+						name: @bodyParams.data.name
+						channel: @bodyParams.data.username
+						triggerWords: @bodyParams.data.trigger_words
+
+		return {} =
+			statusCode: 200
+			body:
+				success: true
+
+
+Api.addRoute 'remove/:integrationId/:userId/:token', authRequired: true,
+	post: ->
+		console.log 'Remove integration'
+		console.log @bodyParams
+
+		if @bodyParams?.payload?
+			@bodyParams = JSON.parse @bodyParams.payload
+
+		integration = RocketChat.models.Integrations.findOne(@urlParams.integrationId)
+		user = RocketChat.models.Users.findOne(@userId)
+
+		if not integration?
+			return {} =
+				statusCode: 400
+				body:
+					success: false
+					error: 'Invalid integraiton id'
+
+
+		integrationToRemove = RocketChat.models.Integrations.findOne urls: @bodyParams.target_url
+		Meteor.runAsUser user._id, =>
+			Meteor.call 'deleteOutgoingIntegration', integrationToRemove._id
+
+		return {} =
+			statusCode: 200
+			body:
+				success: true
+
+
+Api.addRoute 'sample/:integrationId/:userId/:token', authRequired: true,
+	get: ->
+		console.log 'Sample integration'
+
+		return {} =
+			statusCode: 200
+			body:
+				token: Random.id(24)
+				channel_id: Random.id()
+				channel_name: 'general'
+				timestamp: new Date
+				user_id: Random.id()
+				user_name: 'rocket.cat'
+				text: 'Sample text'
+				trigger_word: 'Sample'
+
+
+Api.addRoute 'info/:integrationId/:userId/:token', authRequired: true,
+	get: ->
+		console.log 'Info integration'
+
+		return {} =
+			statusCode: 200
+			body:
+				success: true
+


Groups: Fixes when there is no activity showing page not found error

--- server/lib/crawler/staticpages/feed.coffee
+++ server/lib/crawler/staticpages/feed.coffee
@@ -13,7 +13,6 @@
   if page > 0
     skip = (page - 1) * ITEMSPERPAGE

-
   options = {
     limit : ITEMSPERPAGE
     skip  : skip
@@ -30,10 +29,10 @@
   pageContent = ""
   model.count {}, (error, count)=>
     return callback error, null  if error
-    return callback null, null  if count is 0
+    return callback null, getEmptyPage contentType  if count is 0
     model.some {}, options, (err, contents)=>
       return callback err, null  if err
-      return callback null, null  if contents.length is 0
+      return callback null, getEmptyPage contentType  if count is 0
       queue = [0..contents.length - 1].map (index)=>=>
         queue.pageContent or= ""

@@ -208,6 +207,8 @@
       </div>
   </header>
   """
+getEmptyPage = (contentType) ->
+  putContentIntoFullPage "There is no activity yet", "", contentType

 putContentIntoFullPage = (content, pagination, contentType)->
   getGraphMeta  = require './graphmeta'


Remove `features.mendeley` and `features.zotero` from User model

--- services/web/app/coffee/models/User.coffee
+++ services/web/app/coffee/models/User.coffee
@@ -36,8 +36,6 @@
 							compileGroup:  { type:String,  default: Settings.defaultFeatures.compileGroup }
 							templates:     { type:Boolean, default: Settings.defaultFeatures.templates }
 							references:    { type:Boolean, default: Settings.defaultFeatures.references }
-							mendeley:    { type:Boolean, default: Settings.defaultFeatures.mendeley }
-							zotero:    { type:Boolean, default: Settings.defaultFeatures.zotero }
 						}
 	featureSwitches	  : {
 		pdfng: { type: Boolean }


syntax;

syntax;

--- test/wormhole_test.coffee
+++ test/wormhole_test.coffee
@@ -30,6 +30,6 @@
     it 'the peer receiving request document should respond wiht \'document\',
         the current document, and its clock', ->

-    it 'the peer should send '
+    it 'the peer should send ', ->

-  describe ''
+  describe '', ->


Use observable arrays when contents are arrays

--- js/inner.coffee
+++ js/inner.coffee
@@ -2,10 +2,16 @@

   constructor: (json, defaults = {}) ->
     for k,v of defaults
-      @[k] = ko.observable(v)
+      @[k] = @observable(v)

     for k,v of json
-      @[k] = ko.observable(v)
+      @[k] = @observable(v)
+
+  observable: (obj) ->
+    if $.isArray obj
+      ko.observableArray obj
+    else
+      ko.observable obj

   project_name: =>
     @vcs_url().substring(19)
@@ -17,8 +23,15 @@
     ko.computed args...

+
+class LogOutput extends Base
+  constructor: (json) ->
+    super json
+
+
 class ActionLog extends Base
   constructor: (json) ->
+    json.out = (new LogOutput(j) for j in json.out) if json.out
     super json,
       timedout: null
       exit_code: 0
@@ -46,23 +59,25 @@
       result

     @action_log_style = @komp =>
-      minimize: => @success
+      minimize: => @status == "success"

     @duration = @komp () =>
       Circle.time.pretty_duration_short(@run_time_millis())

+
 #TODO: next step is to add the vcs_url, which is why I was looking at the knockout.model and knockout.mapping plugin
 class Build extends Base
   constructor: (json) ->
     # make the actionlogs observable
-    json.action_logs = ko.observableArray( (new ActionLog(j) for j in json.action_logs) )
+    json.action_logs = (new ActionLog(j) for j in json.action_logs) if json.action_logs
     super json,
       committer_email: null,
       committer_name: null,
       body: null,
       subject: null
+      user: null

     @url = @komp =>
       "#{@project_path()}/#{@build_num()}"


Remove semi-colon

Remove semi-colon

--- js/inner.coffee
+++ js/inner.coffee
@@ -441,7 +441,7 @@
       eval code

   raiseIntercomDialog: () =>
-    $("#IntercomDefaultWidget").click();
+    $("#IntercomDefaultWidget").click()

   logout: (cx) =>
     # TODO: add CSRF protection


split out route hooking

split out route hooking

--- lib/CRUD.coffee
+++ lib/CRUD.coffee
@@ -37,6 +37,13 @@
     async.forEachSeries @_middleware, run, cb
     return

+  _hookRoute: (path, app, model, route) ->
+    handler = require("./util/handlers/#{route.meta.type}") route
+    p = (if path? then join(path,route.path) else route.path)
+    for method, fn of handler
+      app[method] p, @runMiddleware, model.runMiddleware, fn
+    return @
+
   hook: (path, app) ->
     if typeof path isnt 'string'
       app = path
@@ -44,10 +51,7 @@
     throw new Error "Missing httpServer" unless app?
     for name, model of @_models
       for route in model.routes
-        handler = require("./util/handlers/#{route.meta.type}") route
-        for method, fn of handler
-          p = (if path? then join(path,route.path) else route.path)
-          app[method] p, @runMiddleware, model.runMiddleware, fn
+        @_hookRoute path, app, model, route
     return @

 module.exports = CRUD


Refactor test code: use nested `describe` for readablity

--- tests/spec/invalid.coffee
+++ tests/spec/invalid.coffee
@@ -91,18 +91,20 @@
       ).done (-> done()), done

 describe "Print Java error message", ->
-  it "The message should be printed when `validate()` called", ->
-    vnu.validate("<html/>", ("ss": "64"))
-      .catch (err) ->
-        expect(err).to.be.an.instanceof(SyntaxError)
-        expect(err.message).to.match(
-          /^Unexpected token [\s\S]+Invalid thread stack size: -Xss64/
-        )
+  describe "When `validate()` is called", ->
+    it "The message should be printed", ->
+      vnu.validate("<html/>", ("ss": "64"))
+        .catch (err) ->
+          expect(err).to.be.an.instanceof(SyntaxError)
+          expect(err.message).to.match(
+            /^Unexpected token [\s\S]+Invalid thread stack size: -Xss64/
+          )

-  it "The message should be printed when `validateFiles()` called", ->
-    vnu.validateFiles(["./tests/data/invalid.html"], ("ss": "32"))
-      .catch (err) ->
-        expect(err).to.be.an.instanceof(SyntaxError)
-        expect(err.message).to.match(
-          /^Unexpected token [\s\S]+Invalid thread stack size: -Xss32/
-        )
+  describe "When `validateFiles()` is called", ->
+    it "The message should be printed", ->
+      vnu.validateFiles(["./tests/data/invalid.html"], ("ss": "32"))
+        .catch (err) ->
+          expect(err).to.be.an.instanceof(SyntaxError)
+          expect(err.message).to.match(
+            /^Unexpected token [\s\S]+Invalid thread stack size: -Xss32/
+          )


extend helper

extend helper

--- lib/ccss.coffee
+++ lib/ccss.coffee
@@ -1,4 +1,9 @@
 fs = require 'fs'
+
+extend = (object, properties) ->
+  for key, value of properties
+    object[key] = value
+  object

 @compile = (rules) ->
   css = ''
@@ -13,8 +18,7 @@
       unless mixin instanceof Array
         mixin = [mixin]
       for mix in mixin
-        for key, value of mix
-          pairs[key] = value
+        extend pairs, mix

     #a pair is either a css declaration, or a nested rule
     for key, value of pairs


Add text for extracting file with multibyte characters in path

--- test/api-spec.coffee
+++ test/api-spec.coffee
@@ -65,4 +65,10 @@
       done compFiles 'tmp/packthis-unicode-path.asar', 'test/expected/packthis-unicode-path.asar'
       return
     return
+  it 'should extract a text file from archive with multibyte characters in path', ->
+    actual = asar.extractFile('test/expected/packthis-unicode-path.asar', 'dir1/女の子.txt').toString 'utf8'
+    expected = fs.readFileSync 'test/input/packthis-unicode-path/dir1/女の子.txt', 'utf8'
+    # on windows replace crlf with lf
+    expected = expected.replace /\r\n/g, '\n' if os.platform() is 'win32'
+    assert.equal actual, expected
   return


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -44,7 +44,13 @@
   o.trigger "test"

 test "#trigger namespace", 1, ->
-
+  o.bind "test.TestNamespace", ->
+    ok true
+
+  o.trigger "test"
+
+  o.unbind "TestNamespace", ->
+  o.trigger "test"

 test "#unbind namespaced", 1, ->
   o = Object.extend({}, Bindable())