:white_check_mark: Added function tests

:white_check_mark: Added function tests

to prevent #6

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -14,109 +14,109 @@

   describe "when a regexp compile tokenizes", ->
     it "works with all bracket/seperator variations", ->
-      {tokens} = grammar.tokenizeLine("qr/text/egimosxradlupc;")
+      {tokens} = grammar.tokenizeLine("qr/text/acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "qr", scopes: ["source.perl", "string.regexp.compile.simple-delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.compile.simple-delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.compile.simple-delimiter.perl"]
       expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.compile.simple-delimiter.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("qr(text)egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("qr(text)acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "qr", scopes: ["source.perl", "string.regexp.compile.nested_parens.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "(", scopes: ["source.perl", "string.regexp.compile.nested_parens.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.compile.nested_parens.perl"]
       expect(tokens[3]).toEqual value: ")", scopes: ["source.perl", "string.regexp.compile.nested_parens.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("qr{text}egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("qr{text}acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "qr", scopes: ["source.perl", "string.regexp.compile.nested_braces.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "{", scopes: ["source.perl", "string.regexp.compile.nested_braces.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.compile.nested_braces.perl"]
       expect(tokens[3]).toEqual value: "}", scopes: ["source.perl", "string.regexp.compile.nested_braces.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("qr[text]egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("qr[text]acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "qr", scopes: ["source.perl", "string.regexp.compile.nested_brackets.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "[", scopes: ["source.perl", "string.regexp.compile.nested_brackets.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.compile.nested_brackets.perl"]
       expect(tokens[3]).toEqual value: "]", scopes: ["source.perl", "string.regexp.compile.nested_brackets.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("qr<text>egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("qr<text>acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "qr", scopes: ["source.perl", "string.regexp.compile.nested_ltgt.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "<", scopes: ["source.perl", "string.regexp.compile.nested_ltgt.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.compile.nested_ltgt.perl"]
       expect(tokens[3]).toEqual value: ">", scopes: ["source.perl", "string.regexp.compile.nested_ltgt.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.compile.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
       expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]

   describe "when a regexp find tokenizes", ->
     it "works with all bracket/seperator variations", ->
-      {tokens} = grammar.tokenizeLine("/text/egimosxradlupc;")
+      {tokens} = grammar.tokenizeLine("/text/acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
       expect(tokens[1]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find.perl"]
       expect(tokens[2]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
-      expect(tokens[3]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[3]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
       expect(tokens[4]).toEqual value: ";", scopes: ["source.perl"]

-      {tokens} = grammar.tokenizeLine("m/text/egimosxradlupc;")
+      {tokens} = grammar.tokenizeLine("m/text/acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "m", scopes: ["source.perl", "string.regexp.find-m.simple-delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find-m.simple-delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find-m.simple-delimiter.perl"]
       expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find-m.simple-delimiter.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("m(text)egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("m(text)acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "m", scopes: ["source.perl", "string.regexp.find-m.nested_parens.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "(", scopes: ["source.perl", "string.regexp.find-m.nested_parens.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find-m.nested_parens.perl"]
       expect(tokens[3]).toEqual value: ")", scopes: ["source.perl", "string.regexp.find-m.nested_parens.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("m{text}egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("m{text}acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "m", scopes: ["source.perl", "string.regexp.find-m.nested_braces.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "{", scopes: ["source.perl", "string.regexp.find-m.nested_braces.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find-m.nested_braces.perl"]
       expect(tokens[3]).toEqual value: "}", scopes: ["source.perl", "string.regexp.find-m.nested_braces.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("m[text]egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("m[text]acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "m", scopes: ["source.perl", "string.regexp.find-m.nested_brackets.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "[", scopes: ["source.perl", "string.regexp.find-m.nested_brackets.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find-m.nested_brackets.perl"]
       expect(tokens[3]).toEqual value: "]", scopes: ["source.perl", "string.regexp.find-m.nested_brackets.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
-
-      {tokens} = grammar.tokenizeLine("m<text>egimosxradlupc;")
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("m<text>acdegilmoprsux;")
       expect(tokens[0]).toEqual value: "m", scopes: ["source.perl", "string.regexp.find-m.nested_ltgt.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "<", scopes: ["source.perl", "string.regexp.find-m.nested_ltgt.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.find-m.nested_ltgt.perl"]
       expect(tokens[3]).toEqual value: ">", scopes: ["source.perl", "string.regexp.find-m.nested_ltgt.perl", "punctuation.definition.string.perl"]
-      expect(tokens[4]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[4]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.find-m.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
       expect(tokens[5]).toEqual value: ";", scopes: ["source.perl"]

   describe "when a regexp replace tokenizes", ->
     it "works with all bracket/seperator variations", ->
-      {tokens} = grammar.tokenizeLine("s/text/test/egimosxradlupc")
+      {tokens} = grammar.tokenizeLine("s/text/test/acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
       expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl"]
       expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]
-      expect(tokens[6]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-
-      {tokens} = grammar.tokenizeLine("s(text)(test)egimosxradlupc")
+      expect(tokens[6]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+
+      {tokens} = grammar.tokenizeLine("s(text)(test)acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.nested_parens.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "(", scopes: ["source.perl", "string.regexp.nested_parens.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.nested_parens.perl"]
@@ -124,9 +124,9 @@
       expect(tokens[4]).toEqual value: "(", scopes: ["source.perl", "string.regexp.format.nested_parens.perl", "punctuation.definition.string.perl"]
       expect(tokens[5]).toEqual value: "test", scopes: ["source.perl", "string.regexp.format.nested_parens.perl"]
       expect(tokens[6]).toEqual value: ")", scopes: ["source.perl", "string.regexp.format.nested_parens.perl", "punctuation.definition.string.perl"]
-      expect(tokens[7]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-
-      {tokens} = grammar.tokenizeLine("s{text}{test}egimosxradlupc")
+      expect(tokens[7]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+
+      {tokens} = grammar.tokenizeLine("s{text}{test}acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.nested_braces.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "{", scopes: ["source.perl", "string.regexp.nested_braces.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.nested_braces.perl"]
@@ -134,9 +134,9 @@
       expect(tokens[4]).toEqual value: "{", scopes: ["source.perl", "string.regexp.format.nested_braces.perl", "punctuation.definition.string.perl"]
       expect(tokens[5]).toEqual value: "test", scopes: ["source.perl", "string.regexp.format.nested_braces.perl"]
       expect(tokens[6]).toEqual value: "}", scopes: ["source.perl", "string.regexp.format.nested_braces.perl", "punctuation.definition.string.perl"]
-      expect(tokens[7]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-
-      {tokens} = grammar.tokenizeLine("s[text][test]egimosxradlupc")
+      expect(tokens[7]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+
+      {tokens} = grammar.tokenizeLine("s[text][test]acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.nested_brackets.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "[", scopes: ["source.perl", "string.regexp.nested_brackets.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.nested_brackets.perl"]
@@ -144,9 +144,9 @@
       expect(tokens[4]).toEqual value: "[", scopes: ["source.perl", "string.regexp.format.nested_brackets.perl", "punctuation.definition.string.perl"]
       expect(tokens[5]).toEqual value: "test", scopes: ["source.perl", "string.regexp.format.nested_brackets.perl"]
       expect(tokens[6]).toEqual value: "]", scopes: ["source.perl", "string.regexp.format.nested_brackets.perl", "punctuation.definition.string.perl"]
-      expect(tokens[7]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-
-      {tokens} = grammar.tokenizeLine("s<text><test>egimosxradlupc")
+      expect(tokens[7]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+
+      {tokens} = grammar.tokenizeLine("s<text><test>acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.nested_ltgt.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "<", scopes: ["source.perl", "string.regexp.nested_ltgt.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.nested_ltgt.perl"]
@@ -154,16 +154,16 @@
       expect(tokens[4]).toEqual value: "<", scopes: ["source.perl", "string.regexp.format.nested_ltgt.perl", "punctuation.definition.string.perl"]
       expect(tokens[5]).toEqual value: "test", scopes: ["source.perl", "string.regexp.format.nested_ltgt.perl"]
       expect(tokens[6]).toEqual value: ">", scopes: ["source.perl", "string.regexp.format.nested_ltgt.perl", "punctuation.definition.string.perl"]
-      expect(tokens[7]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
-
-      {tokens} = grammar.tokenizeLine("s_text_test_egimosxradlupc")
+      expect(tokens[7]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+
+      {tokens} = grammar.tokenizeLine("s_text_test_acdegilmoprsux")
       expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
       expect(tokens[1]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
       expect(tokens[3]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]
       expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl"]
       expect(tokens[5]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]
-      expect(tokens[6]).toEqual value: "egimosxradlupc", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[6]).toEqual value: "acdegilmoprsux", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]

   describe "tokenizes constant variables", ->
     it "highlights constants", ->
@@ -230,6 +230,20 @@
       {tokens} = grammar.tokenizeLine("$test->qx();")
       expect(tokens[2]).toEqual value: "->", scopes: ["source.perl", "keyword.operator.comparison.perl"]
       expect(tokens[3]).toEqual value: "qx();", scopes: ["source.perl"]
+
+  describe "when a function call tokenizes", ->
+    it "does not highlight calls which looks like a regexp", ->
+      {tokens} = grammar.tokenizeLine("s_ttest($key,\"t_storage\",$single_task);");
+      expect(tokens[0]).toEqual value: "s_ttest(", scopes: ["source.perl"]
+      expect(tokens[3]).toEqual value: ",", scopes: ["source.perl"]
+      expect(tokens[7]).toEqual value: ",", scopes: ["source.perl"]
+      expect(tokens[10]).toEqual value: ");", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("s__ttest($key,\"t_license\",$single_task);");
+      expect(tokens[0]).toEqual value: "s__ttest(", scopes: ["source.perl"]
+      expect(tokens[3]).toEqual value: ",", scopes: ["source.perl"]
+      expect(tokens[7]).toEqual value: ",", scopes: ["source.perl"]
+      expect(tokens[10]).toEqual value: ");", scopes: ["source.perl"]

   describe "tokenizes single quoting", ->
     it "does not escape characters in single-quote strings", ->


:bug: Fix error raised when opening a non existing file

Fixes #16

--- lib/minimap-git-diff.coffee
+++ lib/minimap-git-diff.coffee
@@ -64,7 +64,7 @@
   getRepositories: -> atom.project.getRepositories().filter (repo) -> repo?

   destroyBindings: =>
-    return unless @minimap? or @minimap.editorsMinimaps?
+    return unless @minimap? and @minimap.editorsMinimaps?
     @minimap.editorsMinimaps.forEach (minimap) =>
       @bindings.get(minimap)?.destroy()
       @bindings.delete(minimap)


Stringify json data contents.

Stringify json data contents.

--- web/coffee/problems.coffee
+++ web/coffee/problems.coffee
@@ -35,7 +35,7 @@

   console.log {feedback: feedback, pid: pid}

-  apiCall "POST", "/api/problems/feedback", {feedback: feedback, pid: pid}
+  apiCall "POST", "/api/problems/feedback", JSON.stringify({feedback: feedback, pid: pid})
   .done (data) ->
     apiNotify data


JSON workaround. Sending items as JSON strings.

--- web/coffee/problems.coffee
+++ web/coffee/problems.coffee
@@ -33,9 +33,9 @@

   pid = $(e.target).data("pid")

-  console.log {feedback: feedback, pid: pid}
+  postData = {feedback: JSON.stringify(feedback), pid: pid}

-  apiCall "POST", "/api/problems/feedback", JSON.stringify({feedback: feedback, pid: pid})
+  apiCall "POST", "/api/problems/feedback", postData
   .done (data) ->
     apiNotify data


Check for existence prior to remove on reset

--- source/server/module.coffee
+++ source/server/module.coffee
@@ -47,8 +47,8 @@
     @commitPublisher.startPublishing()

   onReset: ->
-    @injector.get('Space.eventSourcing.Commits').remove {}
-    @injector.get('Space.eventSourcing.Snapshots').remove {}
+    @injector.get('Space.eventSourcing.Commits')?.remove {}
+    @injector.get('Space.eventSourcing.Snapshots')?.remove {}

   onStop: ->
     @commitPublisher.stopPublishing()


Adapt map view to changed schema

Adapt map view to changed schema

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -60,10 +60,10 @@
           # FIXME temporarily disable clustering since it is not properly
           # reinitialized when the MapCtrl controller reloads
           # group: p.district
-          lat: p.latitude
-          lng: p.longitude
+          lat: p.location.coordinates[1]
+          lng: p.location.coordinates[0]
           message: "#{p.wpt_code}
" +
-            "Status: #{p.status}
" +
+            "Status: #{p.status_group}
" +
             "edit
" +
             "submit request"
       # This would keep loading further waterpoints as long as there are any.
@@ -76,10 +76,9 @@
       projection:
         _id: 1
         district: 1
-        latitude: 1
-        longitude: 1
+        location: 1
         wpt_code: 1
-        status: 1
+        status_group: 1
     , addMarkers
     return this


added missing label mousedown propagation #369

--- src/coffee/bootstrap-switch.coffee
+++ src/coffee/bootstrap-switch.coffee
@@ -376,7 +376,7 @@
               .not(@$element)
               .prop("checked", false)
               .trigger "change.bootstrapSwitch", true
-
+
           @$element.trigger "switchChange.bootstrapSwitch", [state]

         "focus.bootstrapSwitch": (e) =>
@@ -417,6 +417,7 @@
           return  if @_dragStart or @options.disabled or @options.readonly

           e.preventDefault()
+          e.stopPropagation()

           @_dragStart = (e.pageX or e.originalEvent.touches[0].pageX) - parseInt @$container.css("margin-left"), 10
           @$wrapper.removeClass "#{@options.baseClass}-animate"  if @options.animate


Fix an issue of klass.include() method.

Fix an issue of klass.include() method.

--- src/coffee/kopi/utils/klass.coffee
+++ src/coffee/kopi/utils/klass.coffee
@@ -35,7 +35,9 @@
   include = (klass, mixin) ->
     # extend class properties
     for own name, method of mixin
-      klass[name] = method
+      # Some version of Safari Mobile considers `prototype`
+      # as own property of `mixin`
+      klass[name] = method unless name == "prototype"
     # extend instance properties
     for own name, method of mixin.prototype
       klass.prototype[name] = method


Fixed after Halil's comments.

Fixed after Halil's comments.

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -404,20 +404,13 @@
       , (err, name)->
         if name
           slugInput.setValidationResult 'slug', "Slug is already being used.", yes
-          KD.remote.api.JGroup.suggestUniqueSlug name.name, (err, newSlug)->
-            unless err
-              slugInput.setTooltip
-                title     : "Suggestion: #{newSlug}"
-                placement : 'right'
-              slugInput.tooltip.show()
         else
           slugInput.setValidationResult 'slug', null
-          delete slugInput.tooltip

     makeSlug = =>
       titleInput = modal.modalTabs.forms["General Settings"].inputs.Title
       slugInput = modal.modalTabs.forms["General Settings"].inputs.Slug
-      KD.remote.api.JGroup.suggestUniqueSlug titleInput.getValue(), (err, newSlug)->
+      KD.remote.api.JGroup.suggestUniqueSlug titleInput.getValue(), 0, (err, newSlug)->
         if err then slugInput.setValue ''
         else
           slugInput.setValue newSlug


fix auto link checkbox

fix auto link checkbox

--- lib/insert-link-view.coffee
+++ lib/insert-link-view.coffee
@@ -73,12 +73,12 @@
     if utils.isInlineLink(selection)
       link = utils.parseInlineLink(selection)
       @setLink(link.text, link.url, link.title)
-      @saveCheckbox.prop("checked", true) unless @isChangedSavedLink(link)
+      @saveCheckbox.prop("checked", true) if @isInSavedLink(link)
     else if utils.isReferenceLink(selection)
       link = utils.parseReferenceLink(selection, @editor.getText())
       @referenceId = link.id
       @setLink(link.text, link.url, link.title)
-      @saveCheckbox.prop("checked", true) unless @isChangedSavedLink(link)
+      @saveCheckbox.prop("checked", true) if @isInSavedLink(link)
     else if @getSavedLink(selection)
       link = @getSavedLink(selection)
       @setLink(selection, link.url, link.title)
@@ -164,15 +164,15 @@
   getSavedLink: (text) ->
     @links?[text.toLowerCase()]

-  isChangedSavedLink: (link) ->
+  isInSavedLink: (link) ->
     savedLink = @getSavedLink(link.text)
-    savedLink and (savedLink.title != link.title or savedLink.url != link.url)
+    savedLink and savedLink.title == link.title and savedLink.url == link.url

   updateSavedLink: (text, title, url) ->
     try
       if @saveCheckbox.prop("checked")
         @links[text.toLowerCase()] = title: title, url: url if url
-      else if not @isChangedSavedLink(text: text, title: title, url: url)
+      else if @isInSavedLink(text: text, title: title, url: url)
         delete @links[text.toLowerCase()]
       CSON.writeFileSync(@getSavedLinksPath(), @links)
     catch error


Don't load user configuration in window specs (who knows what's in there?!)

--- spec/app/window-spec.coffee
+++ spec/app/window-spec.coffee
@@ -3,6 +3,7 @@

 describe "Window", ->
   beforeEach ->
+    spyOn(window, 'loadUserConfiguration')
     window.startup()

   afterEach ->


operator precedence issue is fixed.

operator precedence issue is fixed.

--- node_modules/bongo-client/src/bongo.coffee
+++ node_modules/bongo-client/src/bongo.coffee
@@ -141,7 +141,7 @@
           @update node
         else
           @update new konstructor(node)
-      else if 'string' is typeof node and hasEncoder
+      else if hasEncoder && 'string' is typeof node
         @update Encoder.XSSEncode(node)
       else
         @update node


syncstring: revert the wait-to-load on convert; it breaks in edge cases anyways

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -888,15 +888,9 @@
                 if err
                     cb(err)
                 else
-                    if not @_client.is_project() and @_patch_list.count() == 0
-                        @_patches_table.once 'change', =>
-                            @emit('change')
-                            @emit('connected')
-                            cb()
-                    else
-                        @emit('change')
-                        @emit('connected')
-                        cb()
+                    @emit('change')
+                    @emit('connected')
+                    cb()
             )

     # Delete the synchronized string and **all** patches from the database -- basically
@@ -1747,24 +1741,6 @@
     has_uncommitted_changes: () =>
         return @_patches_table?.has_uncommitted_changes()

-    ###
-    _test_random_edit: () =>
-        s = @to_str()
-        i = misc.randint(0, s.length-1)
-        if Math.random() <= .2
-            # delete text
-            s = s.slice(0,i) + s.slice(i-misc.randint(1,25))
-        else
-            # insert about 25 characters at random
-            s = s.slice(0,i) + Math.random().toString(36).slice(2) + s.slice(i)
-        @_doc = @_from_str(s)
-
-    test_random_edits: (opts) =>
-        opts = defaults opts,
-            number : 5
-            cb     : undefined
-    ###
-
 exports.SyncDoc = SyncDoc

 # Immutable string document that satisfies our spec.
@@ -1812,39 +1788,6 @@
             doctype           : {type:'string'}

 ###
-# A document that represents an arbitrary JSON-able Javascript object.
-class ObjectDocument
-    constructor: (@_value={}) ->
-    from_str: (value) ->
-        try
-            @_value = misc.from_json(value)
-        catch err
-            console.warn("error parsing JSON", err)
-            # leaves @_value unchanged, so JSON stays valid
-    to_str: =>
-        return misc.to_json(@_value)
-
-    # Underlying Javascript object -- safe to directly edit
-    to_obj: =>
-        return @_value
-
-    is_equal: (other) =>
-        return underscore.isEqual(@_value, other._value)
-
-class exports.SyncObject extends SyncDoc
-    constructor: (opts) ->
-        opts = defaults opts,
-            id      : required
-            client  : required
-            default : {}
-        super
-            string_id : opts.id
-            client    : opts.client
-            doc       : new ObjectDocument(opts.default)
-
-###
-
-###
 Used for testing
 ###
 synctable = require('./synctable')


set value to '' after commit

set value to '' after commit

--- src/smc-webapp/editor_git.cjsx
+++ src/smc-webapp/editor_git.cjsx
@@ -112,6 +112,7 @@
                 else
                     @setState(git_commit_return : output.stdout)
                     @setState(commit_message : '')
+                    @refs.commit_message.setValue('')
                     @update_status()
                     @update_diff()


Removed unused view

Removed unused view

--- core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
+++ core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
@@ -1,8 +1,3 @@
-class NDPFactRelationOrCommentAvatarView extends Backbone.Marionette.ItemView
-  className: 'ndp-evidence-heading'
-  template: 'evidence/ndp_fact_relation_or_comment_avatar'
-
-
 class NDPCommentView extends Backbone.Marionette.ItemView
   className: 'discussion-evidenceish-text'
   template: 'evidence/ndp_comment'


Add spec for building products without variantId.

--- src/spec/validator.spec.coffee
+++ src/spec/validator.spec.coffee
@@ -133,6 +133,24 @@
         expect(@validator.errors[0]).toBe '[row 2] We need a product before starting with a variant!'
         done()

+    it 'should build products without variantId', (done) ->
+      csv =
+        """
+        productType,sku
+        foo,1
+        foo,2
+        """
+      @validator.parse csv, (content) =>
+        @validator.buildProducts content
+        expect(@validator.errors.length).toBe 0
+        expect(@validator.rawProducts.length).toBe 2
+        expect(@validator.rawProducts[0].master).toEqual ['foo', '1']
+        expect(@validator.rawProducts[0].variants.length).toBe 0
+        expect(@validator.rawProducts[0].startRow).toBe 2
+        expect(@validator.rawProducts[1].master).toEqual ['foo', '2']
+        expect(@validator.rawProducts[1].variants.length).toBe 0
+        expect(@validator.rawProducts[1].startRow).toBe 3
+        done()

   describe '#valProduct', ->
     it 'should return no error', ->


Fix variable test

Fix variable test

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -48,8 +48,8 @@
   describe "start of variable", ->
     it "parses the dollar sign at the beginning of a variable separately", ->
       {tokens} = grammar.tokenizeLine("$var")
-      expect(tokens[0]).toEqual value: "$", scopes: ["source.powershell", "variable.other.powershell", "punctuation.variable.begin.powershell"]
-      expect(tokens[1]).toEqual value: "var", scopes: ["source.powershell", "variable.other.powershell"]
+      expect(tokens[0]).toEqual value: "$", scopes: ["source.powershell", "keyword.other.powershell"]
+      expect(tokens[1]).toEqual value: "var", scopes: ["source.powershell", "variable.other.readwrite.powershell"]

   describe "Double-quoted strings", ->
     describe "String with content", ->


Use path.join() instead of fsUtils.join()

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -3,6 +3,7 @@
 RootView = require 'root-view'
 StatusBar = require 'status-bar/lib/status-bar-view'
 fsUtils = require 'fs-utils'
+path = require 'path'
 {$$} = require 'space-pen'

 describe "StatusBar", ->
@@ -57,9 +58,9 @@

   describe "when the buffer content has changed from the content on disk", ->
     it "disables the buffer modified indicator on save", ->
-      path = "/tmp/atom-whitespace.txt"
-      fsUtils.write(path, "")
-      rootView.open(path)
+      filePath = "/tmp/atom-whitespace.txt"
+      fsUtils.write(filePath, "")
+      rootView.open(filePath)
       expect(statusBar.bufferModified.text()).toBe ''
       editor.insertText("\n")
       advanceClock(buffer.stoppedChangingDelay)
@@ -127,32 +128,32 @@
       expect(statusBar.branchLabel.text()).toBe ''

   describe "git status label", ->
-    [repo, path, originalPathText, newPath, ignoredPath] = []
+    [repo, filePath, originalPathText, newPath, ignoredPath] = []

     beforeEach ->
-      path = require.resolve('fixtures/git/working-dir/file.txt')
-      newPath = fsUtils.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'new.txt')
+      filePath = require.resolve('fixtures/git/working-dir/file.txt')
+      newPath = path.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'new.txt')
       fsUtils.write(newPath, "I'm new here")
-      ignoredPath = fsUtils.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'ignored.txt')
+      ignoredPath = path.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'ignored.txt')
       fsUtils.write(ignoredPath, 'ignored.txt')
-      git.getPathStatus(path)
+      git.getPathStatus(filePath)
       git.getPathStatus(newPath)
-      originalPathText = fsUtils.read(path)
+      originalPathText = fsUtils.read(filePath)
       rootView.attachToDom()

     afterEach ->
-      fsUtils.write(path, originalPathText)
+      fsUtils.write(filePath, originalPathText)
       fsUtils.remove(newPath) if fsUtils.exists(newPath)
       fsUtils.remove(ignoredPath) if fsUtils.exists(ignoredPath)

     it "displays the modified icon for a changed file", ->
-      fsUtils.write(path, "i've changed for the worse")
-      git.getPathStatus(path)
-      rootView.open(path)
+      fsUtils.write(filePath, "i've changed for the worse")
+      git.getPathStatus(filePath)
+      rootView.open(filePath)
       expect(statusBar.gitStatusIcon).toHaveClass('modified-status-icon')

     it "doesn't display the modified icon for an unchanged file", ->
-      rootView.open(path)
+      rootView.open(filePath)
       expect(statusBar.gitStatusIcon).toHaveText('')

     it "displays the new icon for a new file", ->
@@ -164,18 +165,18 @@
       expect(statusBar.gitStatusIcon).toHaveClass('ignored-status-icon')

     it "updates when a status-changed event occurs", ->
-      fsUtils.write(path, "i've changed for the worse")
-      git.getPathStatus(path)
-      rootView.open(path)
+      fsUtils.write(filePath, "i've changed for the worse")
+      git.getPathStatus(filePath)
+      rootView.open(filePath)
       expect(statusBar.gitStatusIcon).toHaveClass('modified-status-icon')
-      fsUtils.write(path, originalPathText)
-      git.getPathStatus(path)
+      fsUtils.write(filePath, originalPathText)
+      git.getPathStatus(filePath)
       expect(statusBar.gitStatusIcon).not.toHaveClass('modified-status-icon')

     it "displays the diff stat for modified files", ->
-      fsUtils.write(path, "i've changed for the worse")
-      git.getPathStatus(path)
-      rootView.open(path)
+      fsUtils.write(filePath, "i've changed for the worse")
+      git.getPathStatus(filePath)
+      rootView.open(filePath)
       expect(statusBar.gitStatusIcon).toHaveText('+1,-1')

     it "displays the diff stat for new files", ->


remove duplicate clearMarkers code

remove duplicate clearMarkers code

--- lib/line-diff-worker.coffee
+++ lib/line-diff-worker.coffee
@@ -7,8 +7,6 @@

     update: =>
         @clearMarkers()
-        markers = @editor.findMarkers({name: "line-diff"})
-        marker.destroy() for marker in markers
         editorView = atom.views.getView(@editor)
         gutter = $(editorView).find ".gutter"
         statusChangeSelector = ".git-line-modified, .git-line-removed, .git-line-added"


:bug: Fix looping over array while it's actually a Map

--- lib/minimap-git-diff.coffee
+++ lib/minimap-git-diff.coffee
@@ -65,7 +65,7 @@

   destroyBindings: =>
     return unless @minimap? or @minimap.editorsMinimaps?
-    for minimap in @minimap.editorsMinimaps
+    @minimap.editorsMinimaps.forEach (minimap) =>
       @bindings.get(minimap)?.destroy()
       @bindings.delete(minimap)


Bug fix: Retain scope on submitHandler (uses @settings), use preventDefault instead of return true/false, and only preventDefault if form is a formSelector match

--- src/jqt.coffee
+++ src/jqt.coffee
@@ -455,20 +455,19 @@
     setHash = (hash) =>
       location.hash = "#" + hash.replace(/^#/, "") if @settings.updateHash

-    submitHandler = (e, callback) ->
+    submitHandler = (e, callback) =>
       $(":focus").trigger "blur"
-      e.preventDefault()
+
       $form = (if (typeof (e) is "string") then $(e).eq(0) else ((if e.target then $(e.target) else $(e))))

       if $form.length and $form.is(@settings.formSelector) and $form.attr("action")
+        e.preventDefault()
+
         showPageByHref $form.attr("action"),
           data: $form.serialize()
           method: $form.attr("method") or "POST"
           animation: getAnimation($form)
           callback: callback
-
-        return false
-      true

     submitParentForm = ($el) ->
       $form = $el.closest("form")


Expand file tree on open

Expand file tree on open

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -4,14 +4,21 @@
 class SyncedFS
   constructor: (ws_url) ->
     ipc.send 'register-new-fs-connection', ws_url
-    ipc.on 'remote-open-event', (file) ->
+    ipc.on 'remote-open-event', (file) =>
       atom.workspace.open(file)
+      setTimeout =>
+        @expandTreeView()
+      , 0

     ipc.on 'connection-state', (state) =>
       @connectionState = state

     @handleEvents()
     @treeViewEventQueue = []
+
+  expandTreeView: ->
+    workspaceView = atom.views.getView(atom.workspace)
+    atom.commands.dispatch(workspaceView, 'tree-view:reveal-active-file')

   handleEvents: ->
     atom.workspace.observeTextEditors (editor) =>


insert clone after cloned product

insert clone after cloned product

--- app/assets/javascripts/admin/services/bulk_products.js.coffee
+++ app/assets/javascripts/admin/services/bulk_products.js.coffee
@@ -19,7 +19,7 @@
         # when a respond_overrride for the clone action is used.
         id = data.product.id
         dataFetcher("/api/products/" + id + "?template=bulk_show").then (newProduct) =>
-          @addProducts [newProduct]
+          @insertProductAfter(product, newProduct)

     updateVariantLists: (serverProducts, productsWithUnsavedVariants) ->
       for product in productsWithUnsavedVariants
@@ -38,6 +38,10 @@
       for product in products
         @unpackProduct product
         @products.push product
+
+    insertProductAfter: (product, newProduct) ->
+      index = @products.indexOf(product)
+      @products.splice(index + 1, 0, newProduct)

     unpackProduct: (product) ->
       #$scope.matchProducer product


bugfix: in-memory orderItems have to be deleted manually

--- app/assets/javascripts/views/ventas/venta_form_view.js.coffee
+++ app/assets/javascripts/views/ventas/venta_form_view.js.coffee
@@ -29,6 +29,9 @@
     @failureCallback response

   ventaSaved: (venta) ->
+    # delete orderItems instantiated and not persisted:
+    for orderItem in venta.get("orderItems.content").filterBy("id", null)
+      orderItem.deleteRecord()
     @get("controller").transitionToRoute "ventas.show", venta.get("id")

   saveVenta: ->


CNS-663: Implement CKEditor WYSIWYG

CNS-663: Implement CKEditor WYSIWYG

Removed coffeescript file for a dialog related to Redactor

--- app/coffeescripts/views/redactor/InsertUpdateImageView.coffee
+++ app/coffeescripts/views/redactor/InsertUpdateImageView.coffee
@@ -1,146 +0,0 @@
-define [
-  'i18n!editor'
-  'jquery'
-  'underscore'
-  'str/htmlEscape'
-  'compiled/fn/preventDefault'
-  'compiled/views/DialogBaseView'
-  'jst/tinymce/InsertUpdateImageView'
-], (I18n, $, _, h, preventDefault, DialogBaseView, template) ->
-
-  class InsertUpdateImageView extends DialogBaseView
-
-    template: template
-
-    events:
-      'change [name="image[width]"]' : 'constrainProportions'
-      'change [name="image[height]"]' : 'constrainProportions'
-      'click .flickrImageResult, .treeFile' : 'onFileLinkClick'
-      'change [name="image[src]"]' : 'onImageUrlChange'
-      'tabsshow .imageSourceTabs': 'onTabsshow'
-      'dblclick .flickrImageResult, .treeFile' : 'onFileLinkDblclick'
-
-    dialogOptions:
-      width: 625
-      title: I18n.t 'titles.insert_edit_image', 'Insert / Edit Image'
-
-    initialize: (@editor, selectedNode) ->
-      @$editor = @editor.$element;
-      @$selectedNode = $(selectedNode)
-      super
-      @render()
-      @show()
-      if @$selectedNode.prop('nodeName') is 'IMG'
-        @setSelectedImage
-          src: @$selectedNode.attr('src')
-          alt: @$selectedNode.attr('alt')
-          width: @$selectedNode.width()
-          height: @$selectedNode.height()
-
-    afterRender: ->
-      @$('.imageSourceTabs').tabs()
-
-    onTabsshow: (event, ui) ->
-      loadTab = (fn) =>
-        return if @["#{ui.panel.id}IsLoaded"]
-        @["#{ui.panel.id}IsLoaded"] = true
-        loadingDfd = $.Deferred()
-        $(ui.panel).disableWhileLoading loadingDfd
-        fn(loadingDfd.resolve)
-      switch ui.panel.id
-        when 'tabUploaded'
-          loadTab (done) =>
-            require [
-              'compiled/views/FileBrowserView'
-            ], (FileBrowserView) =>
-              new FileBrowserView(contentTypes: 'image').render().$el.appendTo(ui.panel)
-              done()
-        when 'tabFlickr'
-          loadTab (done) =>
-            require ['compiled/views/FindFlickrImageView'], (FindFlickrImageView) =>
-              new FindFlickrImageView().render().$el.appendTo(ui.panel)
-              done()
-
-    setAspectRatio: ->
-      width = Number @$("[name='image[width]']").val()
-      height = Number @$("[name='image[height]']").val()
-      if width && height
-        @aspectRatio = width / height
-      else
-        delete @aspectRatio
-
-    constrainProportions: (event) =>
-      val = Number $(event.target).val()
-      if @aspectRatio && (val or (val is 0))
-        if $(event.target).is('[name="image[height]"]')
-          @$('[name="image[width]"]').val Math.round(val * @aspectRatio)
-        else
-          @$('[name="image[height]"]').val Math.round(val / @aspectRatio)
-
-    setSelectedImage: (attributes = {}) ->
-      # set given attributes immediately; update width and height after image loads
-      @$("[name='image[#{key}]']").val(value) for key, value of attributes
-      dfd = $.Deferred()
-      onLoad = ({target: img}) =>
-        newAttributes = _.defaults attributes,
-          width: img.width
-          height: img.height
-        @$("[name='image[#{key}]']").val(value) for key, value of newAttributes
-        isValidImage = newAttributes.width && newAttributes.height
-        @setAspectRatio()
-        dfd.resolve newAttributes
-      onError = ({target: img}) =>
-        newAttributes =
-          width: ''
-          height: ''
-        @$("[name='image[#{key}]']").val(value) for key, value of newAttributes
-      @$img = $('', attributes).load(onLoad).error(onError)
-      dfd
-
-    getAttributes: ->
-      res = {}
-      for key in ['width', 'height']
-        val = Number @$("[name='image[#{key}]']").val()
-        res[key] = val if val && val > 0
-      for key in ['src',  'alt']
-        val = @$("[name='image[#{key}]']").val()
-        res[key] = val if val
-      res
-
-    onFileLinkClick: (event) ->
-      event.preventDefault()
-      @$('.active').removeClass('active').parent().removeAttr('aria-selected')
-      $a = $(event.currentTarget).addClass('active')
-      $a.parent().attr('aria-selected', true)
-      @flickr_link = $a.attr('data-linkto')
-      @setSelectedImage
-        src: $a.attr('data-fullsize')
-        alt: $a.attr('title')
-
-    onFileLinkDblclick: (event) =>
-      # click event is handled on the first click
-      @update()
-
-    onImageUrlChange: (event) ->
-      @flickr_link = null
-      @setSelectedImage src: $(event.currentTarget).val()
-
-    generateImageHtml: ->
-      img_tag = $("", @getAttributes())[0].outerHTML
-      if @flickr_link
-        img_tag = "#{img_tag}"
-      img_tag
-
-    update: =>
-      @editor.selectionRestore()
-      if @$selectedNode.prop('nodeName') is 'IMG'
-        new_attr = @getAttributes()
-        if @$selectedNode.parent().prop('nodeName') is 'A' &&
-           @$selectedNode.parent().attr('href') == this.$selectedNode.attr('src')
-          @$selectedNode.parent().attr('href', new_attr.src)
-        @$selectedNode.replaceWith($(@generateImageHtml()))
-        @$editor.editorBox 'sync'
-      else
-        @$editor.editorBox 'insert_code', @generateImageHtml()
-      @editor.focus()
-      @close()


When going back after reading docs, switch back to the inner app

--- js/outer.coffee
+++ js/outer.coffee
@@ -137,6 +137,10 @@
   @get "/about.*", (cx) -> about.display(cx)
   @get "/privacy.*", (cx) -> privacy.display(cx)
   @get "/", (cx) -> home.display(cx)
+  @get("/gh/.*", (cx) =>
+    @unload()
+    window.location = cx.path)
+

 # Global polyfills


improve confirm documentation

improve confirm documentation

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -821,6 +821,9 @@
     @emitter.emit 'did-beep'

   # Essential: A flexible way to open a dialog akin to an alert dialog.
+  #
+  # If the dialog is closed (via `Esc` key or `X` in the top corner) without selecting a button
+  # the first button will be clicked unless a "Cancel" or "No" button is provided.
   #
   # ## Examples
   #
@@ -839,7 +842,7 @@
   #   * `buttons` (optional) Either an array of strings or an object where keys are
   #     button names and the values are callbacks to invoke when clicked.
   #
-  # Returns the chosen button index {Number} if the buttons option was an array.
+  # Returns the chosen button index {Number} if the buttons option is an array or the return value of the callback if the buttons option is an object.
   confirm: (params={}) ->
     @applicationDelegate.confirm(params)


Remove atom environment state instance variable

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -125,7 +125,6 @@
     @document.addEventListener('mousedown', debouncedSaveState, true)
     @document.addEventListener('keydown', debouncedSaveState, true)

-    @state = {version: @constructor.version}

     @loadTime = null
     {devMode, safeMode, resourcePath} = @getLoadSettings()


uploadcsv: minor updates

uploadcsv: minor updates

--- client/home/lib/myteam/components/hometeamsendinvites/uploadcsvmodal.coffee
+++ client/home/lib/myteam/components/hometeamsendinvites/uploadcsvmodal.coffee
@@ -11,14 +11,13 @@
     { input: @input } = options

     @trailerContent = '''
-
Before starting to upload your file, please be sure that you have the right format.
-      The file should contain Email, First Name (optional), Last Name (optional), and Role fields in the right order.
-      Roles can be Admin or Member.
+
Before uploading your file, please make sure you have the right format.
+      The file should contain Email, First Name (optional), Last Name (optional), and Role fields in the right order.
+      Roles can be Admin or Member.

-      Here is an example:

-
+      Here is an example:

+

-        Email, First Name, Last Name, Role

         micheal@example.com, Micheal R., Crawley, Member

         clora@example.com, Clora J., Ochoa, Member

         randy@example.com, Randy S., Engel, Admin
@@ -26,11 +25,12 @@
     '''

     options = _.assign {}, options,
-      title: 'Upload CSV File'
       cssClass: 'content-modal csv-upload'
       width: 600

     super options
+
+    @setTitle 'Upload CSV File'

     @createTrailerStatePage()
     @createUploadingStatePage()
@@ -52,8 +52,6 @@
     @addSubView @uploadingPage = new kd.CustomHTMLView
       tagName: 'main'
       cssClass: 'main-container uploading-state hidden'
-
-    @uploadingPage.addSubView @loader = @getLoaderView()

   createErrorUploadingPage: ->
@@ -108,28 +106,28 @@

   createFormDataAndRequest : (event) ->

+    @setTitle 'Uploading'
     @trailerPage.hide()
     @uploadingPage.show()

-    fileNames = []
-
     @formData = new FormData()

-    for file in @input.files
-      fileNames.push file.name
-      @formData.append 'file', file, {
-        filename: file.name
-        contentType: 'multipart/form-data'
-      }
+    file = @input.files[0]
+    @formData.append 'file', file, {
+      filename: file.name
+      contentType: 'multipart/form-data'
+    }

     @errorUploading?.hide()

     @uploadingPage.updatePartial "

-       Selected File:

-        #{fileNames.join '
'}
+
Selected File:

+        #{file.name}

     "
+
+    @uploadingPage.addSubView @loader = @getLoaderView()

     @makeReq '/-/teams/invite-by-csv-analyze', 'successAnalyzeCSV', 'errorAnalyzeCSV'

@@ -156,6 +154,7 @@
   successAnalyzeCSV: (result) ->

     @loader.hide()
+    @setTitle 'Send Invitations'

     if typeof result is 'string'
       @sendData()
@@ -164,44 +163,72 @@
     @selectAndUpload.hide()
     @sendAllInvites.show()
     @uploadingPage.updatePartial '''
-      Would you like to send an invitation to all people below
-      except already invited members?

+

+        Would you like to send an invitation to all people below
+        except already invited members?
+

+

       In your files, we have found…
     '''
     { myself, admins, members, extras } = result
+
+    memberLabel = if members.length > 1 then 'Members' else 'Member'
+    adminLabel = if admins.length > 1 then 'Admins' else 'Admin'
+
+    @totalInvitation = admins + members
+
+    analyzeWrapperClassName = 'wrapper'
+    for ex in Object.keys(extras)
+      value = extras["#{ex}"]
+      if value
+        analyzeWrapperClassName = 'wrapper-with-extras'
+        @totalInvitation = @totalInvitation + value
+
+
+
     @successPage.addSubView analyzeCSV = new kd.CustomHTMLView
       cssClass: 'analyzeCSV'
       partial: """
-

+

-            #{admins+members}

+            #{@totalInvitation}

              Total

             #{members}

-             Members
+            #{memberLabel}

             #{admins}

-             Admins
+            #{adminLabel}

       """
+
     analyzeCSV.addSubView extraInfo = new kd.CustomHTMLView
+      cssClass: 'extras-wrapper'
     for ex in Object.keys(extras)
-      if extras["#{ex}"].count
-        extraInfo.setPartial """
+#{extras["#{ex}"].count} #{extras["#{ex}"].label}
"""
+      value = extras["#{ex}"]
+      if value
+        if ex is 'alreadyMembers'
+          label = if ex > 1 then 'Already Members' else 'Already Member'
+        else if ex is 'notValidInvites'
+          label = if ex > 1 then 'Invalid Invites' else 'Invalid Invite'
+        else if ex is 'alreadyInvited'
+          label = 'Already Invited'
+        extraInfo.setPartial """
+ #{value} #{label}
"""

   errorAnalyzeCSV: (err) ->

+    @setTitle 'Something Went Wrong'
     @loader.hide()
     @errorUploading.show()
     @errorUploading.addSubView showError = new kd.CustomHTMLView
       cssClass: 'show-error'
       partial: '''
-        It seems that the files you have selected not formatted right or empty or there are no valid data.
-        Please make sure that you have selected the right files

+        It seems that the file you have selected is not formatted right or has no valid data in it.
+

       '''

     showError.addSubView fileFormat = new kd.CustomHTMLView
@@ -221,5 +248,5 @@

   success: (result) ->

-    @getOptions().success()
+    @getOptions().success @totalInvitation
     @destroy()


Fix loadIfMoreItemsNecessary bug

Fix loadIfMoreItemsNecessary bug

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -111,7 +111,7 @@
       KDEventTypes  : "CommonInnerNavigationListItemReceivedClick"
       listener      : @
       callback      : (pubInst, data)=>
-        @filter data.type
+        @filter data.type, loadIfMoreItemsIsNecessary

   ownActivityArrived:(activity)->
     @activityListController.ownActivityArrived activity
@@ -210,13 +210,14 @@
       sort        :
         createdAt : -1

-    @fetchTeasers selector, options, (activities)=>
-      if activities
-        for activity in activities
-          @activityListController.addItem activity
-        callback? activities
-      else
-        callback?()
+    if not options.skip < options.limit
+      @fetchTeasers selector, options, (activities)=>
+        if activities
+          for activity in activities
+            @activityListController.addItem activity
+          callback? activities
+        else
+          callback?()

   loadSomeTeasersIn:(sourceIds, options, callback)->
     bongo.api.Relationship.within sourceIds, options, (err, rels)->
@@ -232,7 +233,7 @@
       controller._state = 'private'
       controller.itemsOrdered.forEach (item)=>
         item.hide() if not controller.isInFollowing(item.data)
-      return
+      return no

     else if show is 'public'
       controller._state = 'public'


change to bulk job csv output to show inputs given by user sheet

--- noddy/service/oabutton/api.coffee
+++ noddy/service/oabutton/api.coffee
@@ -348,6 +348,7 @@
     csv = '"MATCH","AVAILABLE","SOURCE","REQUEST","TITLE","DOI"'
     liborder = []
     sources = []
+    extras = []
     if res.length and res[0].args?
       jargs = JSON.parse res[0].args
       if jargs.libraries?
@@ -358,11 +359,24 @@
         sources = jargs.sources
         for s in sources
           csv += ',"' + s.toUpperCase() + '"'
+      for er in res
+        if er.args?
+          erargs = JSON.parse er.args
+          for k of erargs
+            extras.push(k) if k.toLowerCase() not in ['library','libraries','sources','plugin','all','titles'] and k not in extras
+      if extras.length
+        exhd = ''
+        exhd += '"' + ex + '",' for ex in extras
+        csv = exhd + csv

     for r in res
       row = if r.result.string then JSON.parse(r.result.string) else r.result['API.service.oab.find']
-      csv += '\n"'
-      csv += if row.match then row.match.replace('TITLE:','').replace(/"/g,'') + '","' else '","'
+      csv += '\n'
+      if r.args?
+        ea = JSON.parse r.args
+        for extra in extras
+          csv += '"' + (if ea[extra]? then ea[extra] else '') + '",'
+      csv += '"' + (if row.match then row.match.replace('TITLE:','').replace(/"/g,'') + '","' else '","')
       av = 'No'
       if row.availability?
         for a in row.availability


Add example to overview

Add example to overview

--- src/buffered-process.coffee
+++ src/buffered-process.coffee
@@ -1,38 +1,45 @@
 ChildProcess = require 'child_process'

-# Public: A wrapper which provides line buffering for Node's ChildProcess.
+# Public: A wrapper which provides standard error/output line buffering for
+# Node's ChildProcess.
 #
 # ## Requiring in packages
 #
 # ```coffee
-#   {BufferedProcess} = require 'atom'
+# {BufferedProcess} = require 'atom'
+#
+# command = 'ps'
+# args = ['-ef']
+# stdout = (output) -> console.log(output)
+# exit = (code) -> console.log("ps -ef exited with #{code}")
+# process = new BufferredProcess({command, args, stdout, exit})
 # ```
 module.exports =
 class BufferedProcess
   process: null
   killed: false

-  # Public: Executes the given executable.
+  # Public: Runs the given command by spawning a new child process.
   #
   # options - An {Object} with the following keys:
   #   :command - The {String} command to execute.
-  #   :args - The {String}} of arguments to pass to the script (optional).
+  #   :args - The {Array} of arguments to pass to the command (optional).
   #   :options - The options {Object} to pass to Node's `ChildProcess.spawn`
-  #              (optional).
-  #   :stdout - The callback that receives a single argument which contains the
-  #             standard output of the script. The callback is called as data is
-  #             received but it's buffered to ensure only complete lines are
-  #             passed until the source stream closes. After the source stream
-  #             has closed all remaining data is sent in a final call
-  #             (optional).
-  #   :stderr - The callback that receives a single argument which contains the
-  #             standard error of the script. The callback is called as data is
-  #             received but it's buffered to ensure only complete lines are
-  #             passed until the source stream closes. After the source stream
-  #             has closed all remaining data is sent in a final call
-  #             (optional).
-  #   :exit - The callback which receives a single argument containing the exit
-  #           status (optional).
+  #              method (optional).
+  #   :stdout - The callback {Function} that receives a single argument which
+  #             contains the standard output from the command. The callback is
+  #             called as data is received but it's buffered to ensure only
+  #             complete lines are passed until the source stream closes. After
+  #             the source stream has closed all remaining data is sent in a
+  #             final call (optional).
+  #   :stderr - The callback {Function} that receives a single argument which
+  #             contains the standard error output from the command. The
+  #             callback is called as data is received but it's buffered to
+  #             ensure only complete lines are passed until the source stream
+  #             closes. After the source stream has closed all remaining data
+  #             is sent in a final call (optional).
+  #   :exit - The callback {Function} which receives a single argument
+  #           containing the exit status (optional).
   constructor: ({command, args, options, stdout, stderr, exit}={}) ->
     options ?= {}
     @process = ChildProcess.spawn(command, args, options)
@@ -67,12 +74,9 @@

   # Helper method to pass data line by line.
   #
-  # * stream:
-  #   The Stream to read from.
-  # * onLines:
-  #   The callback to call with each line of data.
-  # * onDone:
-  #   The callback to call when the stream has closed.
+  # stream - The Stream to read from.
+  # onLines - The callback to call with each line of data.
+  # onDone - The callback to call when the stream has closed.
   bufferStream: (stream, onLines, onDone) ->
     stream.setEncoding('utf8')
     buffered = ''


Fix goal index position

Fix goal index position

--- lib/text-buffer/goal.coffee
+++ lib/text-buffer/goal.coffee
@@ -8,7 +8,7 @@
         # adjust the position, outlet not functioning
         element = $(@)[0]
         indexWidth = @index.toString().length
-        element.style.left = (- @editor.getDefaultCharWidth() * (indexWidth + 1)) + 'px'
+        element.style.left = (- @editor.getDefaultCharWidth() * (indexWidth + 2)) + 'px'
         element.style.top  = (- @editor.getLineHeightInPixels()) + 'px'

 class Goal


Fix undefined callback issue

Fix undefined callback issue

--- lib/token/token.spec.coffee
+++ lib/token/token.spec.coffee
@@ -86,7 +86,7 @@
 					(callback) ->
 						token.saveToken(johnDoeFixture.token, callback)

-					(savedToken, callback) ->
+					(callback) ->
 						token.getToken(callback)

 					(savedToken, callback) ->


Tracking quest 428

Tracking quest 428

Tracking quarterly quest "近海に侵入する敵潜を制圧せよ！"

--- assets/data/quest_goal.cson
+++ assets/data/quest_goal.cson
@@ -328,6 +328,25 @@
     mission: ['強行偵察任務']
     init: 0

+428: #近海に侵入する敵潜を制圧せよ！
+  type: 4
+  fuzzy: true
+  "mission_success@対潜警戒任務":
+    description: "4"
+    required: 2
+    mission: ['対潜警戒任務']
+    init: 0
+  "mission_success@海峡警備行動":
+    description: "A2"
+    required: 2
+    mission: ['海峡警備行動']
+    init: 0
+  "mission_success@長時間対潜警戒":
+    description: "A3"
+    required: 2
+    mission: ['長時間対潜警戒']
+    init: 0
+
 663: #新型艤装の継続研究
   type: 4
   "destory_item":


logout settings

logout settings

--- services/filestore/app/coffee/HealthCheckController.coffee
+++ services/filestore/app/coffee/HealthCheckController.coffee
@@ -15,6 +15,7 @@
 	req = {params:{}, query:{}, headers:{}}
 	req.params.project_id = settings.health_check.project_id
 	req.params.file_id = settings.health_check.file_id
+	console.log settings
 	myWritableStreamBuffer = new streamBuffers.WritableStreamBuffer(initialSize: 100)
 	res = {
 		send: (code) ->


New URLs require a trailing slash

New URLs require a trailing slash

--- lms/static/coffee/src/discussion/utils.coffee
+++ lms/static/coffee/src/discussion/utils.coffee
@@ -67,7 +67,7 @@
       permanent_link_comment  : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}"
       user_profile            : "/courses/#{$$course_id}/discussion/forum/users/#{param}"
       followed_threads       : "/courses/#{$$course_id}/discussion/forum/users/#{param}/followed"
-      threads                 : "/courses/#{$$course_id}/discussion/forum"
+      threads                 : "/courses/#{$$course_id}/discussion/forum/"
     }[name]

   @safeAjax: (params) ->


DE19697: fixing failing test that likes to be flaky

--- test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
+++ test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
@@ -62,7 +62,7 @@
   it 'should define height for planning board based on content window', ->
     Ext.DomHelper.append Ext.getBody(), '

'
     @createApp().then =>
-      expect(@planningBoard.getHeight()).toBe 580
+      expect(@app.getHeight()).toBe 580

   it 'should define height for grid board based on container height', ->
     @createApp(false, {height: 600}).then =>


small changes in list of general functions

--- grammars/lisp.cson
+++ grammars/lisp.cson
@@ -207,15 +207,14 @@
           (?xi)
               (?<=(\\s|\\()) # preceded by space or (
               ( append|apply|approximate|
-                construct-identifier|define-syntax|display|
-                for-each|force|cd|gen-counter|gen-loser|
-                generate-identifier|last-pair|length|let-syntax|
-                letrec-syntax|list|list-ref|list-tail|load|log|
+                construct-identifier|display|
+                funcall|force|cd|gen-counter|gen-loser|
+                generate-identifier|last-pair|length|
+                list|list-ref|list-tail|load|log|
                 macro|magnitude|map|map-streams|max|member|memq|
                 memv|min|newline|nil|not|peek-char|rationalize|
                 read|read-char|return|reverse|sequence|substring|
-                syntax|syntax-rules|transcript-off|transcript-on|
-                truncate|unwrap-syntax|values-list|write|write-char|
+                truncate|values-list|write|write-char|

                 # cons, car, cdr, etc
                 cons|c(a|d){1,4}r|
@@ -233,11 +232,7 @@
                 make-(?:polar|promise|rectangular|string|vector)

                 # string-«foo», vector-«foo»
-                string(?:-(?:append|copy|length|ref))?|
-                vector(?:-length|-ref)
-
-              	# additional keyword-like functions
-              	|for|printf|require
+                string(?:-(?:append|copy|length|ref))?
               )
               (?=(\\s|\\()) # followed by space or (
         '''


Improve recognition of extensionless DWB files


improved tikz

improved tikz

--- grammars/tikz.cson
+++ grammars/tikz.cson
@@ -10,6 +10,10 @@
   { include: '#coordinate' }
   { include: '#options' }
   { include: '#pathDescriptor' }
+  {
+    match: '\\$'
+    name: 'markup.other.calc.tikz'
+  }
   {
     comment: 'This prevents .tikz files being scoped as LaTeX & embedded with TikZ'
     match: '((\\\\)(begin|end))(\\{)(tikzpicture)(\\})'
@@ -29,13 +33,12 @@
   # General structures that apply anywhere
   coordinate: {
     comment: 'The contents is either a point, or name (which refers to a point).'
-    match: '(\\()(.*?)(\\))'
-    captures:
-      1: name: 'punctuation.definition.begin.coordinate.tikz'
-      2:
-        name: 'markup.other.coordinate.tikz'
-        patterns: [{ include: '$self' }]
-      3: name: 'punctuation.definition.end.coordinate.tikz'
+    begin: '\\('
+    end: '\\)|$'
+    beginCaptures: 0: name: 'punctuation.definition.begin.coordinate.tikz'
+    endCaptures: 0: name: 'punctuation.definition.end.coordinate.tikz'
+    contentName:'markup.other.coordinate.tikz'
+    patterns: [{ include: '$self' }]
   }
   options: {
     begin: '\\['
@@ -43,7 +46,7 @@
     end: '\\]'
     endCaptures: 0: name: 'punctuation.definition.argument.optional.end.tikz'
     contentName: 'variable.parameter.optional.tikz'
-    patterns: [{ include: '$self' }]
+    patterns: [{ include: '#text.tex.latex.tikz' }]
   }
   pathDescriptor: {
     comment: 'For the constructions like --, -|, |-, etc.'


Fix calling fs.open in fs.readFile wrapper

--- atom/common/lib/asar.coffee
+++ atom/common/lib/asar.coffee
@@ -129,6 +129,7 @@

   archive.stat(filePath) isnt false

+open = fs.open
 readFile = fs.readFile
 fs.readFile = (p, options, callback) ->
   [isAsar, asarPath, filePath] = splitPath p
@@ -155,12 +156,13 @@
   encoding = options.encoding

   buffer = new Buffer(info.size)
-  fs.open archive.path, flag, (error, fd) ->
+  open archive.path, flag, (error, fd) ->
     return callback error if error
     fs.read fd, buffer, 0, info.size, info.offset, (error) ->
       fs.close fd, ->
         callback error, if encoding then buffer.toString encoding else buffer

+openSync = fs.openSync
 readFileSync = fs.readFileSync
 fs.readFileSync = (p, options) ->
   [isAsar, asarPath, filePath] = splitPath p
@@ -183,7 +185,7 @@
   encoding = options.encoding

   buffer = new Buffer(info.size)
-  fd = fs.openSync archive.path, flag
+  fd = openSync archive.path, flag
   try
     fs.readSync fd, buffer, 0, info.size, info.offset
   catch e


fixed role jquery selector

fixed role jquery selector

--- app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
+++ app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
@@ -64,7 +64,7 @@
             return {
               team : $(element).val()
               role :
-                name: $(element).parent().parent().find("select   :selected").val()
+                name: $(element).parent().parent().parent().find("select   :selected").val()
             }
           )


Refactored ForgotPasswordCtrl

Refactored ForgotPasswordCtrl

--- app/assets/javascripts/sprangular/controllers/forgotPassword.coffee
+++ app/assets/javascripts/sprangular/controllers/forgotPassword.coffee
@@ -1,19 +1,21 @@
-Sprangular.controller 'ForgotPasswordCtrl', ($scope, $location, Account, Status) ->
+Sprangular.controller 'ForgotPasswordCtrl', ($scope, $location, Account, Flash, Status) ->
+  Status.title = 'Forgot Password'
+
   request = { email: '', errors: {} }

   $scope.request = request
-  $scope.requestSent = false

   $scope.submit = ->
     request.errors = {}
-    $scope.requestSent = false
-    Account.forgotPassword(request)
-      .then (content) ->
-        request.email = ''
-        $scope.requestSent = true
-      , (errors) ->
-        console.log errors
-        request.errors = errors
+
+    success = ->
+      $location.path '/'
+      Flash.success("We've sent you an email with a link to reset your password.")
+
+    error = (response) ->
+      request.errors = response.data.errors
+
+    Account.forgotPassword(request).then(success, error)

   $scope.cancel = ->
     $location.path '/'


fixed FillElement not correctly picking up on parent's color

--- src/art/engine/elements/shape_children/fill_element.coffee
+++ src/art/engine/elements/shape_children/fill_element.coffee
@@ -2,6 +2,7 @@
 Atomic = require 'art-atomic'
 Canvas = require 'art-canvas'
 FillableBase = require '../fillable_base'
+
 {log, createWithPostCreate} = Foundation
 {color, Color, point, Point, rect, Rectangle, matrix, Matrix, point0, point1} = Atomic
 {GradientFillStyle} = Canvas
@@ -14,8 +15,15 @@
   getBaseDrawArea:        -> @getParent().getBaseDrawArea()
   getPendingBaseDrawArea: -> @getPendingParent().getPendingBaseDrawArea()

+  ###
+  NOTE:
+
+  _prepareDrawOptions replaces values, even with null ones.
+  Hence, we prepare two separate draw optons and the merge them.
+  ###
+  _drawOptionsTemp = {}
   drawBasic: (target, elementToTargetMatrix, compositeMode, opacity) ->
     @_parent._prepareDrawOptions? @_drawOptions, compositeMode, opacity
-    @_prepareDrawOptions @_drawOptions, compositeMode, opacity
-
+    @_prepareDrawOptions _drawOptionsTemp, compositeMode, opacity
+    @_drawOptions[k] = v for k, v of _drawOptionsTemp when v
     @_parent.fillShape target, elementToTargetMatrix, @_drawOptions


Use my.command

Use my.command

--- src/layout/inspect_commands.coffee
+++ src/layout/inspect_commands.coffee
@@ -15,6 +15,6 @@
   command_buttons = make.buttons(
     'command',
     words,
-    my.button,
+    my.command,
     (button, args) -> button.send('command', button)
   )


messageeventmanager: listen pubnub channels for instance updates

--- client/Main/messageeventmanager.coffee
+++ client/Main/messageeventmanager.coffee
@@ -4,7 +4,15 @@

     super options, data

-    data
+    messageChannel = null
+
+    if KD.isPubnubEnabled()
+      eventType = "instance"
+      messageChannel = KD.singletons.realtime.subscribe {channelName: name, token: data.token, eventType}
+    else
+      messageChannel = data
+
+    messageChannel
       .on 'InteractionAdded', @bound 'addInteraction'
       .on 'InteractionRemoved', @bound 'removeInteraction'
       .on 'ReplyAdded', @bound 'addReply'


TerminalSession: sessionSelected event name is changed to CamelCase

--- client/Terminal/sessionitemview.coffee
+++ client/Terminal/sessionitemview.coffee
@@ -12,6 +12,6 @@
   click: ->

     {session, vm} = @getOptions()
-    @getDelegate().emit "sessionSelected", {vm, session}
+    @getDelegate().emit "SessionSelected", {vm, session}

     no


Client: Hide timer when not necessary.

Client: Hide timer when not necessary.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -952,8 +952,9 @@
     timeRemaining = @timers[index] - $.now() + @timerUpdatedAt[index]

     if !timeRemaining? || isNaN(timeRemaining)
-      $remainingTimer.text("??:??")
-    else
+      $remainingTimer.addClass('hidden')
+    else
+      $remainingTimer.removeClass('hidden')
       $remainingTimer.text PokeBattle.humanizeTime(timeRemaining)

     # Change timer class


Providers: AWS: config: remove key_pair from advanced fields for now

--- client/app/lib/providers/config.coffee
+++ client/app/lib/providers/config.coffee
@@ -31,7 +31,7 @@
     advancedFields         : [
                               'subnet', 'sg', 'vpc',
                               'ami','acl','cidr_block',
-                              'igw','key_pair','rtb'
+                              'igw','rtb'
                              ]
     credentialFields       :
       access_key           :


Do some CoffeeScript Cooking \o/

Do some CoffeeScript Cooking \o/

--- client/app/views/register/setup.coffee
+++ client/app/views/register/setup.coffee
@@ -64,12 +64,6 @@
         return ->

-# Returns the median value of arguments values
-getProgress = ->
-    sum = [].reduce.call arguments, ((memo, val) -> memo + val), 0
-    Math.floor sum / arguments.length
-
-
 ###
 Setup View
 ###
@@ -125,11 +119,15 @@
         # If there's imports, the `progress` property is a median of the timer
         # and imports progress ; otherwise it's just a reference to the timer
         if imports
+            # Returns the median value of arguments values
+            getProgress = ->
+                sum = [].reduce.call arguments, ((memo, val) -> memo + val), 0
+                Math.floor sum / arguments.length
+
             streams = imports.map (datasource) ->
                 fromSocket(datasource).toProperty()

-            args = [getProgress, timer()].concat streams
-            @progress = Bacon.combineWith.apply Bacon, args
+            @progress = Bacon.combineWith getProgress, timer(), streams...
             @progress.onError @onError
         else
             @progress = timer()


Make sure the active tab is always visible

We don't want to end up hiding the active tab in the dropdown. This
wouldn't give customers a clue as to what page they are on.

--- backend/app/assets/javascripts/spree/backend/components/tabs.coffee
+++ backend/app/assets/javascripts/spree/backend/components/tabs.coffee
@@ -46,8 +46,8 @@
     for tab in tabs
       # Bail if things are now fitting
       return if widthDifference <= 0
-      # Skip items already in the dropdown
-      continue if $(tab).hasClass("in-dropdown")
+      # Skip items already in the dropdown or active
+      continue if $(tab).hasClass("in-dropdown") or $(tab).hasClass("active")

       tabWidth = tab.offsetWidth
       @totalTabsWidth -= tabWidth


Don't trigger browsersync livereload on sourcemap files

--- gulp/config.coffee
+++ gulp/config.coffee
@@ -197,6 +197,7 @@
       views: "#{@GLOBALS.BUILD_DIR}"
       livereload: [
         "#{@GLOBALS.BUILD_DIR}/**/*"
+        "!#{@GLOBALS.BUILD_DIR}/**/*.map"
         "!#{@GLOBALS.BUILD_DIR}/config.xml"
       ]
       unnecessary_assets: [


Proper Dropzone reactivity

Proper Dropzone reactivity

--- client/services/dropzoneservice.coffee
+++ client/services/dropzoneservice.coffee
@@ -27,8 +27,11 @@
 dropzoneLogger = new Logger("dropzone")
 logger = new Logger("DropzoneService")

-logDropzone = (event, args...) =>
+handleDropzoneEvent = (event, dropzone, args...) =>
   dropzoneLogger.debug("#{event}:", args)
+  if event in ["addedfile", "addedfiles", "removedfile"]
+    dep = getDependency(dropzone)
+    dep.changed()

  b64ToBlob = (b64Data, contentType, sliceSize) ->
    sliceSize = sliceSize || 512
@@ -60,9 +63,7 @@
       !R.isEmpty(dropzone.files)

   clearDropzone: (dropzone) ->
-    dep = getDependency(dropzone)
     dropzone.removeAllFiles(true)
-    dep.changed()

   createDropzone: (cssId, forPictures, existingFiles) ->
     uploadFiles = (files, data) ->
@@ -232,7 +233,7 @@
       createImageThumbnails: forPictures,
     })
     for event in monitoredDropzoneEvents
-      dropzone.on(event, R.partial(logDropzone, event))
+      dropzone.on(event, R.partial(handleDropzoneEvent, event, dropzone))
     if existingFiles? && !R.isEmpty(existingFiles)
       description = if forPictures then "picture" else "file"
       picker = if forPictures then ((x) -> R.merge(x, {name: x.url})) else (


View: Added setAttribute, getAttribute and removeAttribute wrappers.

--- src/coffee/core/View.coffee
+++ src/coffee/core/View.coffee
@@ -142,6 +142,31 @@

   ###*
+    Native setAttribute wrapper.
+    @param {!string} name  Atrribute name.
+    @param {!string} value Attribute value.
+  ###
+  setAttribute: (name, value) ->
+    @getElement().setAttribute name, value
+
+
+  ###*
+    Native getAttribute wrapper.
+    @param {!string} name  Atrribute name.
+  ###
+  getAttribute: (name) ->
+    @getElement().getAttribute name
+
+
+  ###*
+    Native removeAttribute wrapper.
+    @param {!string} name  Atrribute name.
+  ###
+  removeAttribute: (name) ->
+    @getElement().removeAttribute name
+
+
+  ###*
     This method is responsible from binding DOM events to the element of this view.
     It uses native DOM event names which is all lowercase. If your options
     has an event name and a callback function in eventHandlers object


Create product if there is no id

Create product if there is no id

--- src/coffee/xmlimport.coffee
+++ src/coffee/xmlimport.coffee
@@ -35,33 +35,35 @@
 exports.XmlImport.prototype.createOrUpdate = (data, callback)->
   for p in data.message.products
     console.log "PRO %j", p
-    @rest.GET "/product-projections/#{p.id}", (error, response, body)=>
-      if response.statusCode is 404
-        console.log "CREATE %j", p.id
-        # create new product
-        @rest.POST "/products", JSON.stringify(p), (error, response, body)=>
-          if response.statusCode is 201
-            callback
-              message:
-                status: true
-          else
-            callback
-              message:
-                status: false
-                error: body
-      else
-        # update product
-        @sync.buildActions(p, old_prod).update (error, response, body)->
-          if response.statusCode is 200
-            console.log "SYNC %j", p.id
-            callback
-              message:
-                status: true
-          else
-            callback
-              message:
-                status: false
-                error: body
+    unless p.id
+      console.log "CREATE %j", p
+      # create new product
+      @rest.POST "/products", JSON.stringify(p), (error, response, body)=>
+        if response.statusCode is 201
+          console.log "Product created"
+          callback
+            message:
+              status: true
+        else
+          console.log "Could not create product"
+          console.log body
+          callback
+            message:
+              status: false
+              error: body
+    else
+      # update product
+      @sync.buildActions(p, old_prod).update (error, response, body)->
+        if response.statusCode is 200
+          console.log "SYNC %j", p.id
+          callback
+            message:
+              status: true
+        else
+          callback
+            message:
+              status: false
+              error: body

 exports.XmlImport.prototype.getAndFix = (raw)->
   #TODO: decode base64 - make configurable for testing


Workspace: Removed an used line.

Workspace: Removed an used line.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -284,7 +284,6 @@
     return unless options.title

     options.title     = options.title.replace "$0", KD.nick()
-    activePanel       = @getActivePanel()
     options.container = @getActivePanel()
     options.type      = "mini"
     options.cssClass  = KD.utils.curry "tw-broadcast", options.cssClass


Adding support for the most recent version of Font Awesome.

--- src/plugins/link.coffee
+++ src/plugins/link.coffee
@@ -77,7 +77,7 @@
         buttonHolder = jQuery ''
         buttonHolder.hallobutton
           label: 'Link'
-          icon: 'icon-link'
+          icon: 'fa fa-link'
           editable: @options.editable
           command: null
           queryState: false


Temporary fix for youTube iframe player issues

--- common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
+++ common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
@@ -34,17 +34,17 @@
       @volumeControl = new VideoVolumeControl el: @$('.secondary-controls')
     @speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
     @progressSlider = new VideoProgressSlider el: @$('.slider')
-    @player = new YT.Player @video.id,
-      playerVars:
-        controls: 0
-        wmode: 'transparent'
-        rel: 0
-        showinfo: 0
-        enablejsapi: 1
-      videoId: @video.youtubeId()
-      events:
-        onReady: @onReady
-        onStateChange: @onStateChange
+
+    params = { allowScriptAccess: "always" };
+    atts = { id: @videoid };
+    youtubeId = @video.youtubeId()
+
+    window.onYouTubePlayerReady = (e) =>
+        @player = document.getElementById("#{@video.id}")
+
+    swfobject.embedSWF("http://www.youtube.com/apiplayer?video_id=#{youtubeId}&enablejsapi=1&version=3&playerapiid=ytplayer&controls=0",
+                       @video.id, "425", "356", "9", null, null, params, atts )
+

   addToolTip: ->
     @$('.add-fullscreen, .hide-subtitles').qtip


Reworked wording of the "Reset to Defaults" button on Janko's request.

--- sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
@@ -36,7 +36,7 @@

   #Wire up reset to defaults
   $("#resetToDefaultsButton").click ->
-    if (confirm("Resetting to defaults will delete any existing role mappings and cannot be undone.  Are you sure you want to reset?"))
+    if (confirm("Resetting to default roles will remove any existing role mapping and will restore the default roles.  This operation cannot be undone.  Are you sure you want to reset?"))
       populateTable(default_roles)
       saveData(getJsonData())


US5810: When context right is the only right, there should not be error when deleting it.

--- sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
@@ -164,9 +164,7 @@
       rights = getRights(label.parents("tr"))
       # Disable the save button if the context right was deleted.
       right = input.text()
-      if (((rights.length == 2) && (right != "TEACHER_CONTEXT") && (right != "STAFF_CONTEXT") && \
-          ((rights.indexOf("TEACHER_CONTEXT") >= 0) || (rights.indexOf("STAFF_CONTEXT") >= 0))) ||
-          (rights.length == 1))
+      if ((containsLastNonContextRight(rights) || (rights.length == 1)) && notContextRight(right))
         return alert("Role group must contain at least one non-contextual right.")

@@ -185,6 +183,12 @@
   input.wrap("<" + type + "/>").parent().css("white-space", "nowrap")
   input.parent().append(div)
   return input.parent()
+
+containsLastNonContextRight = (rights) ->
+   return (rights.length == 2) && ((rights.indexOf("TEACHER_CONTEXT") >= 0) || (rights.indexOf("STAFF_CONTEXT") >= 0))
+
+notContextRight = (right) ->
+  return (right != "TEACHER_CONTEXT") && (right != "STAFF_CONTEXT")

 editRowStop = (tr) ->
   disableButton($(".rowEditToolSaveButton"))


Command setting

Command setting

--- lib/alchemide/wrapper.coffee
+++ lib/alchemide/wrapper.coffee
@@ -24,7 +24,8 @@

   array.push(p)
   setting = atom.config.get('autocomplete-elixir.elixirPath')
-  command = setting || "elixir"
+  setting = if setting == "elixir" then "" else setting
+  command = path.join (setting) , "elixir"

   ac = new Process({command: command, args: array.reverse(), stderr, exit})
   out = ac.process.stdout


fix(partner): fixes _ is not a function error (#7542)

--- src/desktop/apps/partner2/components/fixed_cells_count_carousel/view.coffee
+++ src/desktop/apps/partner2/components/fixed_cells_count_carousel/view.coffee
@@ -29,10 +29,10 @@
       _.map @fetchOptions, (options) =>
         @collection.fetch data: options, remove: false
     ).then (values) =>
-      objects = _(values).chain().pluck('value')
-      if objects.first().has('results').value() # For articles wrapped in results
-        objects = objects.pluck('results')
-      @collection.reset objects.flatten().value()
+      objects = _.pluck(values, 'value')
+      if _.has(objects[0], 'results') # For articles wrapped in results
+        objects = _.pluck(objects, 'results')
+      @collection.reset(_.flatten(objects))
       @collection

   # Only keep the first cellsCountPerPage * pagesCount items.


Put in place the edge port rather than replicating it

Replicated ArrayPorts don't work if you want to send packets to only a specific port within the ArrayPort. Since edge ports shouldn't be attached anyway, using the same port on both sides shouldn't be a problem to existing code.

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -63,65 +63,16 @@

   findEdgePorts: (name, process) ->
     for portName, port of process.component.inPorts
-      newPort = @isExported port, name, portName
-      continue if newPort is false
-      @inPorts[newPort] = @replicateInPort port, newPort
+      targetPortName = @isExported port, name, portName
+      continue if targetPortName is false
+      @inPorts[targetPortName] = port

     for portName, port of process.component.outPorts
-      newPort = @isExported port, name, portName
-      continue if newPort is false
-      @outPorts[newPort] = @replicateOutPort port, newPort
+      targetPortName = @isExported port, name, portName
+      continue if targetPortName is false
+      @outPorts[targetPortName] = port

     return true
-
-  replicatePort: (port) ->
-    return new noflo.ArrayPort() if port instanceof noflo.ArrayPort
-    return new noflo.Port() unless port instanceof noflo.ArrayPort
-
-  replicateInPort: (port, portName) ->
-    newPort = @replicatePort port
-    newPort.on "attach", (socket) ->
-      newSocket = noflo.internalSocket.createSocket()
-      port.attach newSocket
-    newPort.on "connect", ->
-      return unless port.isAttached()
-      port.connect()
-    newPort.on "begingroup", (group) ->
-      port.beginGroup group
-    newPort.on "data", (data) ->
-      port.send data
-    newPort.on "endgroup", ->
-      port.endGroup()
-    newPort.on "disconnect", ->
-      port.disconnect()
-    newPort.on "detach", (socket) ->
-      return unless newPort.isAttached()
-      port.detach()
-    newPort
-
-  replicateOutPort: (port, portName) ->
-    newPort = @replicatePort port
-    newPort.on "attach", (socket) ->
-      newSocket = noflo.internalSocket.createSocket()
-      port.attach newSocket
-    port.on "connect", ->
-      return unless newPort.isAttached()
-      newPort.connect()
-    port.on "begingroup", (group) ->
-      return unless newPort.isAttached()
-      newPort.beginGroup group
-    port.on "data", (data) ->
-      return unless newPort.isAttached()
-      newPort.send data
-    port.on "endgroup", ->
-      return unless newPort.isAttached()
-      newPort.endGroup()
-    port.on "disconnect", ->
-      newPort.disconnect()
-    newPort.on "detach", (socket) ->
-      return unless newPort.isAttached()
-      port.detach()
-    newPort

   isReady: ->
     @ready


Fixes #548: Return empty on heartbeat with no config response

--- src/api/mediators.coffee
+++ src/api/mediators.coffee
@@ -129,6 +129,7 @@
       yield Mediator.findByIdAndUpdate(mediator._id, update).exec()

     this.status = 200
+    this.body = ""
   catch err
     utils.logAndSetResponse this, 500, "Could not process mediator heartbeat (urn: #{urn}): #{err}", 'error'

@@ -187,4 +188,4 @@
     yield Mediator.findOneAndUpdate({ urn: urn }, { config: this.request.body, _configModifiedTS: new Date() }).exec()
     this.status = 200
   catch err
-    utils.logAndSetResponse this, 500, "Could not set mediator config (urn: #{urn}): #{err}", 'error'+    utils.logAndSetResponse this, 500, "Could not set mediator config (urn: #{urn}): #{err}", 'error'


Reduce achor navigation animation time

Reduce achor navigation animation time

--- source/assets/javascripts/components/_components.anchor-navigation.coffee
+++ source/assets/javascripts/components/_components.anchor-navigation.coffee
@@ -4,5 +4,5 @@
       target = $(@hash)
       target = if target.length then target else $('[name=' + @hash.slice(1) + ']')
       if target.length
-        $('html,body').animate { scrollTop: target.offset().top }, 1000
+        $('html,body').animate { scrollTop: target.offset().top }, 500
         return false


fix focus bug

fix focus bug

fix when same file and mutiple panes, unable to select suggestions.And focus jump to other panel.
resolved #67 #85

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -194,6 +194,7 @@
   #
   # e - The change {Event}
   onChanged: (e) =>
+    return unless @editorView.hasFocus()
     typedText = e.newText.trim()
     if typedText.length is 1 and atom.config.get "autocomplete-plus.enableAutoActivation"
       @contentsModified()


Add support for connecting to dev backend

--- src/Game.coffee
+++ src/Game.coffee
@@ -12,6 +12,8 @@

     if window.location.search.indexOf("local") != -1
         @socket = io.connect('http://localhost:8080', {secure: false})
+    else if window.location.search.indexOf("dev") != -1
+        @socket = io.connect('https://snap-backend-dev.herokuapp.com:443', {secure: true})
     else
         @socket = io.connect('https://snapgame.herokuapp.com:443', {secure: true})


Mount error display component

Mount error display component

Will be rendered as a BS modal if/when errors occur

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -1,10 +1,12 @@
 React = require 'react'
+
 classnames = require 'classnames'
 {SmartOverflow, SpyMode} = require 'openstax-react-components'

 {Task} = require '../task'
 navigation = {Navigation} = require '../navigation'
 CourseRegistration = require '../course/registration'
+ErrorNotification = require './error-notification'
 UserLogin = require '../user/login'
 UserProfile = require '../user/profile'

@@ -111,6 +113,8 @@
       loading: not (isLoggedIn or isLoaded)

+      <ErrorNotification />
+
       <SpyMode.Wrapper>
         <Navigation key='user-status' close={@props.close} course={course}/>


Fixes the one broken thread in prod; it had no lettters in its ID, so the id was being cast to a number.

--- lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -106,7 +106,7 @@
       for thread in @displayedCollection.models
         content = @renderThread(thread)
         rendered.append content
-        content.wrap("
")
+        content.wrap("
")

       @$(".post-list").html(rendered.html())
       @renderMorePages()


Randomize test order a little

Randomize test order a little

#8 QueryHandlers should also handle query transmission

--- grunt/jasmine_nodejs.coffee
+++ grunt/jasmine_nodejs.coffee
@@ -1,6 +1,10 @@
+seed = Math.floor(Math.random()*1000)
+console.log 'Seed for test order ', seed
 module.exports = {
   options:
     helperNameSuffix: '.js'
+    random: true
+    seed: seed
     specNameSuffix: 'spec.coffee'
     stopOnFailure: true
     useHelpers: true


Partially fix a bunch of loading logic.

Partially fix a bunch of loading logic.

--- src/bootstrap-elm.coffee
+++ src/bootstrap-elm.coffee
@@ -17,20 +17,22 @@
 # Looks up the doc and snapshot associated with the given docId,
 # writes the snapshot to the editor, and tells Elm about the new currentDocId
 loadDocId = (docId) ->
-  sync.getDoc(docId).then (doc) ->
-    app.ports.loadAsCurrentDoc.send doc
-
-    setUpEditor "edit-title", doc.title, (mutations, node) ->
-      sync.getDoc(docId).then (doc) ->
-        doc.title = node.textContent
-        sync.saveDoc(doc).then -> app.ports.loadAsCurrentDoc.send doc
-
-    setUpEditor "edit-description", doc.description, (mutations, node) ->
-      sync.getDoc(docId).then (doc) ->
-        doc.description = node.textContent
-        sync.saveDoc(doc).then -> app.ports.loadAsCurrentDoc.send doc
-
-    doc.chapters.forEach setUpChapter
+  sync.getDoc(docId).then loadAsCurrentDoc
+
+loadAsCurrentDoc = (doc) ->
+  app.ports.loadAsCurrentDoc.send doc
+
+  setUpEditor "edit-title", doc.title, (mutations, node) ->
+    sync.getDoc(doc.id).then (doc) ->
+      doc.title = node.textContent
+      sync.saveDoc(doc).then -> app.ports.loadAsCurrentDoc.send doc
+
+  setUpEditor "edit-description", doc.description, (mutations, node) ->
+    sync.getDoc(doc.id).then (doc) ->
+      doc.description = node.textContent
+      sync.saveDoc(doc).then -> app.ports.loadAsCurrentDoc.send doc
+
+  doc.chapters.forEach setUpChapter

 setUpChapter = (chapter) ->
   chapterId = chapter.id
@@ -38,9 +40,9 @@
   setUpEditor "edit-chapter-heading-#{chapterId}", chapter.heading, (mutations, node) ->
     sync.getCurrentDocId().then (currentDocId) ->
       sync.getDoc(currentDocId).then (doc) ->
-        for chapter in doc.chapters
-          if chapter.id == targetChapterId
-            chapter.heading = node.textContent
+        for currentChapter in doc.chapters
+          if currentChapter.id == chapterId
+            currentChapter.heading = node.textContent

         sync.saveDoc(doc).then -> app.ports.loadAsCurrentDoc.send doc

@@ -51,10 +53,8 @@
           sync.saveSnapshot({id: chapter.snapshotId, html: node.innerHTML})
             .then (->) # TODO send a word count update

-
 saveHtmlAndLoadDoc = (html) ->
-  sync.saveFreshDoc(DocImport.docFromHtml html)
-    .then app.ports.loadAsCurrentDoc.send
+  sync.saveFreshDoc(DocImport.docFromHtml html).then loadAsCurrentDoc

 showFileChooser = ->
   new Promise (resolve, reject) ->
@@ -119,7 +119,7 @@

 setUpEditor = (id, html, onMutate) ->
   whenPresent (-> document.getElementById id),
-    ((elem) -> getEditorFor(elem, onMutate).writeHtml html),
+    ((elem) -> getEditorFor(elem, onMutate).writeHtml html, true),
     ((err) -> console.error "Could not write after 10,000 attempts:", id, html),
     10000


clear buffer when disabled and flexible and stream is not paused

--- src/buffer-stream.coffee
+++ src/buffer-stream.coffee
@@ -117,6 +117,7 @@
                 return @clear()

             if @blocking
+                @clear() unless @enabled
                 return true # it's safe to immediately write again
             else
                 # prevent write calls from blocking the whole process


amqp: Implement removeQueue()

amqp: Implement removeQueue()

--- src/amqp.coffee
+++ src/amqp.coffee
@@ -49,8 +49,15 @@
         @channel.assertQueue queueName, options, (err) =>
           @channel.bindQueue exchangeName, queueName, '', {}, callback

-  removeQueue: (type, queueName, callback) -> # FIXME: do something here?
-    return callback null
+  removeQueue: (type, queueName, callback) ->
+    debug 'remove queue', type, queueName
+    if type == 'inqueue'
+      @channel.deleteQueue queueName, {}, callback
+    else
+      exchangeName = queueName
+      @channel.deleteExchange exchangeName, {}, (err) =>
+        return callback err if err
+        @channel.deleteQueue queueName, {}, callback

   ## Sending/Receiving messages
   sendToQueue: (exchangeName, message, callback) ->


optional destroy

optional destroy

--- src/ref.coffee
+++ src/ref.coffee
@@ -31,11 +31,11 @@
     @

   # Removes reference
-  destroy: ->
-    child.destroy() for id, child of @_children
+  destroy(removeEvents = false) ->
+    child.destroy(removeEvents) for id, child of @_children
     delete @_cache
     delete @_children
-    @off '*'
+    @off '*' if removeEvents

     if @parent
       delete @parent._children[@_id]