P1 DONE: operator-general-spec #270

P1 DONE: operator-general-spec #270

--- spec/operator-general-spec.coffee
+++ spec/operator-general-spec.coffee
@@ -1,3 +1,4 @@
+# SPEC_MIGRATION: P1 DONE #270
 {getVimState, dispatch, TextData} = require './spec-helper'
 settings = require '../lib/settings'

@@ -38,9 +39,9 @@
           ensure 'x', text: 'abc\n\n\nxyz'     , cursor: [1, 0], register: '"': text: '0'

         it "deletes multiple characters with a count", ->
-          ensure '2x', text: 'abc\n0123\n\nxyz', cursor: [1, 3], register: '"': text: '45'
+          ensure '2 x', text: 'abc\n0123\n\nxyz', cursor: [1, 3], register: '"': text: '45'
           set cursor: [0, 1]
-          ensure '3x',
+          ensure '3 x',
             text: 'a\n0123\n\nxyz'
             cursor: [0, 0]
             register: '"': text: 'bc'
@@ -71,10 +72,10 @@

         it "deletes multiple characters and newlines with a count", ->
           settings.set('wrapLeftRightMotion', true)
-          ensure '2x', text: 'abc\n0123\n\nxyz', cursor: [1, 3], register: '"': text: '45'
+          ensure '2 x', text: 'abc\n0123\n\nxyz', cursor: [1, 3], register: '"': text: '45'
           set cursor: [0, 1]
-          ensure '3x', text: 'a0123\n\nxyz', cursor: [0, 1], register: '"': text: 'bc\n'
-          ensure '7x', text: 'ayz', cursor: [0, 1], register: '"': text: '0123\n\nx'
+          ensure '3 x', text: 'a0123\n\nxyz', cursor: [0, 1], register: '"': text: 'bc\n'
+          ensure '7 x', text: 'ayz', cursor: [0, 1], register: '"': text: '0123\n\nx'

     describe "on an empty line", ->
       beforeEach ->
@@ -121,7 +122,7 @@
     describe "when followed by a d", ->
       it "deletes the current line and exits operator-pending mode", ->
         set text: "12345\nabcde\n\nABCDE", cursor: [1, 1]
-        ensure 'dd',
+        ensure 'd d',
           text: '12345\n\nABCDE'
           cursor: [1, 0]
           register: '"': text: 'abcde\n'
@@ -134,41 +135,41 @@
           ABCDE\n
           """
           , cursor: [2, 1]
-        ensure 'dd', text: "12345\nabcde\n", cursor: [1, 0]
+        ensure 'd d', text: "12345\nabcde\n", cursor: [1, 0]

       it "leaves the cursor on the first nonblank character", ->
         set text: '12345\n  abcde\n', cursor: [0, 4]
-        ensure 'dd', text: "  abcde\n", cursor: [0, 2]
+        ensure 'd d', text: "  abcde\n", cursor: [0, 2]

     describe "undo behavior", ->
       beforeEach ->
         set text: "12345\nabcde\nABCDE\nQWERT", cursor: [1, 1]

       it "undoes both lines", ->
-        ensure 'd2du', text: "12345\nabcde\nABCDE\nQWERT", selectedText: ''
+        ensure 'd 2 d u', text: "12345\nabcde\nABCDE\nQWERT", selectedText: ''

       describe "with multiple cursors", ->
         beforeEach ->
           set cursor: [[1, 1], [0, 0]]

         it "is undone as one operation", ->
-          ensure 'dlu',
+          ensure 'd l u',
             text: "12345\nabcde\nABCDE\nQWERT"
             selectedText: ['', '']

     describe "when followed by a w", ->
       it "deletes the next word until the end of the line and exits operator-pending mode", ->
         set text: 'abcd efg\nabc', cursor: [0, 5]
-        ensure 'dw',
+        ensure 'd w',
           text: "abcd \nabc"
           cursor: [0, 4]
           mode: 'normal'

       it "deletes to the beginning of the next word", ->
         set text: 'abcd efg', cursor: [0, 2]
-        ensure 'dw', text: 'abefg', cursor: [0, 2]
+        ensure 'd w', text: 'abefg', cursor: [0, 2]
         set text: 'one two three four', cursor: [0, 0]
-        ensure 'd3w', text: 'four', cursor: [0, 0]
+        ensure 'd 3 w', text: 'four', cursor: [0, 0]

     describe "when followed by an iw", ->
       it "deletes the containing word", ->
@@ -177,7 +178,7 @@
         ensure 'd',
           mode: 'operator-pending'

-        ensure 'iw',
+        ensure 'i w',
           text: "12345  ABCDE"
           cursor: [0, 6]
           register: '"': text: 'abcde'
@@ -196,12 +197,12 @@
       describe "on the beginning of the file", ->
         it "deletes the next two lines", ->
           set cursor: [0, 0]
-          ensure 'dj', text: 'ABCDE\n'
+          ensure 'd j', text: 'ABCDE\n'

       describe "on the middle of second line", ->
         it "deletes the last two lines", ->
           set cursor: [1, 2]
-          ensure 'dj', text: '12345\n'
+          ensure 'd j', text: '12345\n'

       describe "when cursor is on blank line", ->
         beforeEach ->
@@ -214,7 +215,7 @@
               """
             cursor: [1, 0]
         it "deletes both lines", ->
-          ensure 'dj', text: "a\nb\n", cursor: [1, 0]
+          ensure 'd j', text: "a\nb\n", cursor: [1, 0]

     describe "when followed by an k", ->
       originalText = """
@@ -229,17 +230,17 @@
       describe "on the end of the file", ->
         it "deletes the bottom two lines", ->
           set cursor: [2, 4]
-          ensure 'dk', text: '12345\n'
+          ensure 'd k', text: '12345\n'

       describe "on the beginning of the file", ->
         xit "deletes nothing", ->
           set cursor: [0, 0]
-          ensure 'dk', text: originalText
+          ensure 'd k', text: originalText

       describe "when on the middle of second line", ->
         it "deletes the first two lines", ->
           set cursor: [1, 2]
-          ensure 'dk', text: 'ABCDE'
+          ensure 'd k', text: 'ABCDE'

       describe "when cursor is on blank line", ->
         beforeEach ->
@@ -252,7 +253,7 @@
               """
             cursor: [2, 0]
         it "deletes both lines", ->
-          ensure 'dk', text: "a\nb\n", cursor: [1, 0]
+          ensure 'd k', text: "a\nb\n", cursor: [1, 0]

       # [TODO] write more generic operator test. #119
       # This is general behavior of all operator.
@@ -262,7 +263,7 @@
         cursorOriginal = [0, 0]
         it "deletes delete nothing", ->
           set text: textOriginal, cursor: cursorOriginal
-          ensure 'dk', text: textOriginal, cursor: cursorOriginal
+          ensure 'd k', text: textOriginal, cursor: cursorOriginal

     describe "when followed by a G", ->
       beforeEach ->
@@ -272,12 +273,12 @@
       describe "on the beginning of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 0]
-          ensure 'dG', text: '12345\n'
+          ensure 'd G', text: '12345\n'

       describe "on the middle of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 2]
-          ensure 'dG', text: '12345\n'
+          ensure 'd G', text: '12345\n'

     describe "when followed by a goto line G", ->
       beforeEach ->
@@ -287,12 +288,12 @@
       describe "on the beginning of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 0]
-          ensure 'd2G', text: '12345\nABCDE'
+          ensure 'd 2 G', text: '12345\nABCDE'

       describe "on the middle of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 2]
-          ensure 'd2G', text: '12345\nABCDE'
+          ensure 'd 2 G', text: '12345\nABCDE'

     describe "when followed by a t)", ->
       describe "with the entire line yanked before", ->
@@ -300,7 +301,7 @@
           set text: "test (xyz)", cursor: [0, 6]

         it "deletes until the closing parenthesis", ->
-          ensure ['yydt', char: ')'],
+          ensure ['y y d t', char: ')'],
             text: 'test ()'
             cursor: [0, 6]

@@ -314,7 +315,7 @@
             """
           cursorBuffer: [[0, 1], [1, 2], [2, 3]]

-        ensure 'de',
+        ensure 'd e',
           text: "a\n12\nABC\n"
           cursorBuffer: [[0, 0], [1, 1], [2, 2]]

@@ -323,7 +324,7 @@
           text: "abcd\nabc\nabd"
           cursorBuffer: [[0, 0], [1, 0], [2, 0]]

-        ensure ['dt', char: 'd'],
+        ensure ['d t', char: 'd'],
           text: "d\nabc\nd"
           cursorBuffer: [[0, 0], [1, 0], [2, 0]]

@@ -342,7 +343,7 @@

     describe "when selected lines in visual linewise mode", ->
       beforeEach ->
-        keystroke 'Vjy'
+        keystroke 'V j y'

       it "is in linewise motion", ->
         ensure register: '"': type: 'linewise'
@@ -355,7 +356,7 @@

     describe "when followed by a second y ", ->
       beforeEach ->
-        keystroke 'yy'
+        keystroke 'y y'

       it "saves the line to the default register", ->
         ensure register: '"': text: "012 345\n"
@@ -366,12 +367,12 @@
     describe "when useClipboardAsDefaultRegister enabled", ->
       it "writes to clipboard", ->
         settings.set 'useClipboardAsDefaultRegister', true
-        keystroke 'yy'
+        keystroke 'y y'
         expect(atom.clipboard.read()).toBe '012 345\n'

     describe "when followed with a repeated y", ->
       beforeEach ->
-        keystroke 'y2y'
+        keystroke 'y 2 y'

       it "copies n lines, starting from the current", ->
         ensure register: '"': text: "012 345\nabc\n"
@@ -381,18 +382,18 @@

     describe "with a register", ->
       beforeEach ->
-        keystroke ['"', char: 'a', 'yy']
+        keystroke ['"', char: 'a', 'y y']

       it "saves the line to the a register", ->
         ensure register: a: text: "012 345\n"

       it "appends the line to the A register", ->
-        ensure ['"', char: 'A', 'yy'],
+        ensure ['"', char: 'A', 'y y'],
           register: a: text: "012 345\n012 345\n"

     describe "with a forward motion", ->
       beforeEach ->
-        keystroke 'ye'
+        keystroke 'y e'

       it "saves the selected text to the default register", ->
         ensure register: '"': text: '345'
@@ -401,17 +402,17 @@
         ensure cursor: [0, 4]

       it "does not yank when motion fails", ->
-        ensure ['yt', char: 'x'],
+        ensure ['y t', char: 'x'],
           register: '"': text: '345'

     describe "with a text object", ->
       it "moves the cursor to the beginning of the text object", ->
         set cursorBuffer: [0, 5]
-        ensure 'yiw', cursorBuffer: [0, 4]
+        ensure 'y i w', cursorBuffer: [0, 4]

     describe "with a left motion", ->
       beforeEach ->
-        keystroke 'yh'
+        keystroke 'y h'

       it "saves the left letter to the default register", ->
         ensure register: '"': text: ' '
@@ -421,7 +422,7 @@

     describe "with a down motion", ->
       beforeEach ->
-        keystroke 'yj'
+        keystroke 'y j'

       it "saves both full lines to the default register", ->
         ensure register: '"': text: "012 345\nabc\n"
@@ -437,12 +438,12 @@
       describe "on the beginning of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 0]
-          ensure 'yGP', text: '12345\nabcde\nABCDE\nabcde\nABCDE'
+          ensure 'y G P', text: '12345\nabcde\nABCDE\nabcde\nABCDE'

       describe "on the middle of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 2]
-          ensure 'yGP', text: '12345\nabcde\nABCDE\nabcde\nABCDE'
+          ensure 'y G P', text: '12345\nabcde\nABCDE\nabcde\nABCDE'

     describe "when followed by a goto line G", ->
       beforeEach ->
@@ -452,19 +453,19 @@
       describe "on the beginning of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 0]
-          ensure 'y2GP', text: '12345\nabcde\nabcde\nABCDE'
+          ensure 'y 2 G P', text: '12345\nabcde\nabcde\nABCDE'

       describe "on the middle of the second line", ->
         it "deletes the bottom two lines", ->
           set cursor: [1, 2]
-          ensure 'y2GP', text: '12345\nabcde\nabcde\nABCDE'
+          ensure 'y 2 G P', text: '12345\nabcde\nabcde\nABCDE'

     describe "with multiple cursors", ->
       it "moves each cursor and copies the last selection's text", ->
         set
           text: "  abcd\n  1234"
           cursorBuffer: [[0, 0], [1, 5]]
-        ensure 'y^',
+        ensure 'y ^',
           register: '"': text: '123'
           cursorBuffer: [[0, 0], [1, 2]]

@@ -483,19 +484,19 @@
         set text: text.getRaw(), cursor: [1, 2]

       it "don't move cursor after yank from normal-mode", ->
-        ensure "yip", cursorBuffer: [1, 2], register: '"': text: text.getLines([0..2])
-        ensure "jyy", cursorBuffer: [2, 2], register: '"': text: text.getLines([2])
-        ensure "k.", cursorBuffer: [1, 2], register: '"': text: text.getLines([1])
+        ensure "y i p", cursorBuffer: [1, 2], register: '"': text: text.getLines([0..2])
+        ensure "j y y", cursorBuffer: [2, 2], register: '"': text: text.getLines([2])
+        ensure "k .", cursorBuffer: [1, 2], register: '"': text: text.getLines([1])

       it "don't move cursor after yank from visual-linewise", ->
-        ensure "Vy", cursorBuffer: [1, 2], register: '"': text: text.getLines([1])
-        ensure "Vjy", cursorBuffer: [2, 2], register: '"': text: text.getLines([1..2])
+        ensure "V y", cursorBuffer: [1, 2], register: '"': text: text.getLines([1])
+        ensure "V j y", cursorBuffer: [2, 2], register: '"': text: text.getLines([1..2])

       it "don't move cursor after yank from visual-characterwise", ->
-        ensure "vlly", cursorBuffer: [1, 4], register: '"': text: "234"
-        ensure "vhhy", cursorBuffer: [1, 2], register: '"': text: "234"
-        ensure "vjy", cursorBuffer: [2, 2], register: '"': text: "234567\n2_2"
-        ensure "v2ky", cursorBuffer: [0, 2], register: '"': text: "234567\n1_234567\n2_2"
+        ensure "v l l y", cursorBuffer: [1, 4], register: '"': text: "234"
+        ensure "v h h y", cursorBuffer: [1, 2], register: '"': text: "234"
+        ensure "v j y", cursorBuffer: [2, 2], register: '"': text: "234567\n2_2"
+        ensure "v 2 k y", cursorBuffer: [0, 2], register: '"': text: "234567\n1_234567\n2_2"

   describe "the yy keybinding", ->
     describe "on a single line file", ->
@@ -503,7 +504,7 @@
         set text: "exclamation!\n", cursor: [0, 0]

       it "copies the entire line and pastes it correctly", ->
-        ensure 'yyp',
+        ensure 'y y p',
           register: '"': text: "exclamation!\n"
           text: "exclamation!\nexclamation!\n"

@@ -512,12 +513,12 @@
         set text: "no newline!", cursor: [0, 0]

       it "copies the entire line and pastes it correctly", ->
-        ensure 'yyp',
+        ensure 'y y p',
           register: '"': text: "no newline!\n"
           text: "no newline!\nno newline!"

       it "copies the entire line and pastes it respecting count and new lines", ->
-        ensure 'yy2p',
+        ensure 'y y 2 p',
           register: '"': text: "no newline!\n"
           text: "no newline!\nno newline!\nno newline!"

@@ -565,7 +566,7 @@
       describe "at the end of a line", ->
         it "inserts before the current line's newline", ->
           set text: "abcde\none two three", cursor: [1, 4]
-          ensure 'd$k$p', text: "abcdetwo three\none "
+          ensure 'd $ k $ p', text: "abcdetwo three\none "

     describe "with linewise contents", ->
       describe "on a single line", ->
@@ -579,7 +580,7 @@
           ensure 'p', text: "012\n 345", cursor: [1, 1]

         it "replaces the current selection and put cursor to the first char of line", ->
-          ensure 'vp',
+          ensure 'v p',
             text: "0\n 345\n2"
             cursor: [1, 1]

@@ -615,7 +616,7 @@
           text: "12345\nabcde\nABCDE\nQWERT"
           cursor: [1, 1]
           register: '"': text: '123'
-        keystroke '2p'
+        keystroke '2 p'

       it "inserts the same line twice", ->
         ensure text: "12345\nab123123cde\nABCDE\nQWERT"
@@ -642,19 +643,19 @@
       describe "with characterwise selection", ->
         it "replaces selection with charwise content", ->
           set register: '"': text: "345"
-          ensure 'vp', text: "03452\n", cursor: [0, 3]
+          ensure 'v p', text: "03452\n", cursor: [0, 3]
         it "replaces selection with linewise content", ->
           set register: '"': text: "345\n"
-          ensure 'vp', text: "0\n345\n2\n", cursor: [1, 0]
+          ensure 'v p', text: "0\n345\n2\n", cursor: [1, 0]

       describe "with linewise selection", ->
         it "replaces selection with charwise content", ->
           set text: "012\nabc", cursor: [0, 1]
           set register: '"': text: "345"
-          ensure 'Vp', text: "345\nabc", cursor: [0, 0]
+          ensure 'V p', text: "345\nabc", cursor: [0, 0]
         it "replaces selection with linewise content", ->
           set register: '"': text: "345\n"
-          ensure 'Vp', text: "345\n", cursor: [0, 0]
+          ensure 'V p', text: "345\n", cursor: [0, 0]

   describe "the P keybinding", ->
     describe "with character contents", ->
@@ -686,33 +687,33 @@
         beforeEach ->
           set register: '"': text: "AAA\n"
         it "paste and select: [selection:linewise]", ->
-          ensure 'Vgp', text: "111\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
+          ensure 'V g p', text: "111\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
         it "paste and select: [selection:charwise, register:linewise]", ->
-          ensure 'vgP', text: "111\n\nAAA\n22\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
+          ensure 'v g P', text: "111\n\nAAA\n22\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']

       describe "characterwise register", ->
         beforeEach ->
           set register: '"': text: "AAA"
         it "paste and select: [selection:linewise, register:charwise]", ->
-          ensure 'Vgp', text: "111\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
+          ensure 'V g p', text: "111\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
         it "paste and select: [selection:charwise, register:charwise]", ->
-          ensure 'vgP', text: "111\nAAA22\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']
+          ensure 'v g P', text: "111\nAAA22\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']

     describe "in normal", ->
       describe "linewise register", ->
         beforeEach ->
           set register: '"': text: "AAA\n"
         it "putAfter and select", ->
-          ensure 'gp', text: "111\n222\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
+          ensure 'g p', text: "111\n222\nAAA\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
         it "putBefore and select", ->
-          ensure 'gP', text: "111\nAAA\n222\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
+          ensure 'g P', text: "111\nAAA\n222\n333\n", selectedText: "AAA\n", mode: ['visual', 'linewise']
       describe "characterwise register", ->
         beforeEach ->
           set register: '"': text: "AAA"
         it "putAfter and select", ->
-          ensure 'gp', text: "111\n2AAA22\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']
+          ensure 'g p', text: "111\n2AAA22\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']
         it "putAfter and select", ->
-          ensure 'gP', text: "111\nAAA222\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']
+          ensure 'g P', text: "111\nAAA222\n333\n", selectedText: "AAA", mode: ['visual', 'characterwise']

   describe "the J keybinding", ->
     beforeEach ->
@@ -729,7 +730,7 @@
         set
           text: "12345\nabcde\nABCDE\nQWERT"
           cursor: [1, 1]
-        keystroke '2J'
+        keystroke '2 J'

       describe "undo behavior", ->
         beforeEach -> keystroke 'u'
@@ -742,10 +743,10 @@
       set text: "12\n34\n56\n78", cursor: [0, 0]

     it "repeats the last operation", ->
-      ensure '2dd.', text: ""
+      ensure '2 d d .', text: ""

     it "composes with motions", ->
-      ensure 'dd2.', text: "78"
+      ensure 'd d 2 .', text: "78"

   describe "the r keybinding", ->
     beforeEach ->
@@ -765,7 +766,7 @@
         mode: 'normal'

     it "remain visual-mode when cancelled", ->
-      keystroke 'vr'
+      keystroke 'v r'
       vimState.input.cancel()
       ensure
         text: '12\n34\n\n'
@@ -780,18 +781,18 @@
         cursorBuffer: [[1, 0], [3, 0]]

     it "composes properly with motions", ->
-      ensure ['2r', char: 'x'], text: 'xx\nxx\n\n'
+      ensure ['2 r', char: 'x'], text: 'xx\nxx\n\n'

     it "does nothing on an empty line", ->
       set cursorBuffer: [2, 0]
       ensure ['r', char: 'x'], text: '12\n34\n\n'

     it "does nothing if asked to replace more characters than there are on a line", ->
-      ensure ['3r', char: 'x'], text: '12\n34\n\n'
+      ensure ['3 r', char: 'x'], text: '12\n34\n\n'

     describe "when in visual mode", ->
       beforeEach ->
-        keystroke 've'
+        keystroke 'v e'

       it "replaces the entire selection with the given character", ->
         ensure ['r', char: 'x'], text: 'xx\nxx\n\n'
@@ -817,7 +818,7 @@

       beforeEach ->
         set text: textOriginal, cursor: [1, 4]
-        ensure [{ctrl: 'v'}, 'l3j'],
+        ensure 'ctrl-v l 3 j',
           mode: ['visual', 'blockwise']
           selectedTextOrdered: ['11', '22', '33', '44'],

@@ -832,7 +833,7 @@
       set text: '12\n34\n56\n', cursorBuffer: [0, 1]

     it 'marks a position', ->
-      keystroke 'ma'
+      keystroke 'm a'
       expect(vimState.mark.get('a')).toEqual [0, 1]

   describe 'the R keybinding', ->
@@ -866,14 +867,14 @@
       editor.insertText "b"
       ensure text: "12fooab5\n67890"

-      ensure [raw: 'backspace'], text: "12fooa45\n67890"
+      ensure 'backspace', text: "12fooa45\n67890"
       editor.insertText "c"
       ensure text: "12fooac5\n67890"
-      ensure [{raw: 'backspace'}, {raw: 'backspace'}],
+      ensure 'backspace backspace',
         text: "12foo345\n67890"
         selectedText: ''

-      ensure [raw: 'backspace'],
+      ensure 'backspace',
         text: "12foo345\n67890"
         selectedText: ''

@@ -892,7 +893,7 @@
     it "repeats correctly when backspace was used in the text", ->
       keystroke 'R'
       editor.insertText "a"
-      keystroke [raw: 'backspace']
+      keystroke 'backspace'
       editor.insertText "b"
       keystroke 'escape'
       set cursorBuffer: [1, 2]
@@ -935,7 +936,7 @@
             56789
             """
           cursor: [2, 1]
-        ensure {raw: 'backspace'},
+        ensure 'backspace',
           text: """
             0a
             b
@@ -943,33 +944,33 @@
             56789
             """
           cursor: [2, 0]
-        ensure {raw: 'backspace'},
+        ensure 'backspace',
           text: """
             0a
             b34
             56789
             """
           cursor: [1, 1]
-        ensure {raw: 'backspace'},
+        ensure 'backspace',
           text: """
             0a
             234
             56789
             """
           cursor: [1, 0]
-        ensure {raw: 'backspace'},
+        ensure 'backspace',
           text: """
             0a234
             56789
             """
           cursor: [0, 2]
-        ensure {raw: 'backspace'},
+        ensure 'backspace',
           text: """
             01234
             56789
             """
           cursor: [0, 1]
-        ensure {raw: 'backspace'}, # do nothing
+        ensure 'backspace', # do nothing
           text: """
             01234
             56789
@@ -1002,7 +1003,7 @@
             """
           cursor: [1, 2]
           mode: 'normal'
-        ensure 'j.',
+        ensure 'j .',
           text: """
             abc
             def
@@ -1022,7 +1023,7 @@
             """
           cursor: [1, 1]
         ensure 'escape', cursor: [1, 0], mode: 'normal'
-        ensure 'j.',
+        ensure 'j .',
           text: """
           abc
           d4


[i18n] abort auto-translate.js if browser doesn't support it

--- src/coffee/auto-translate.coffee
+++ src/coffee/auto-translate.coffee
@@ -4,6 +4,9 @@
 # @license MIT

 ((module) ->
+    # abort if auto-translate isn't supported
+    return unless module.extension.getI18nMessage?
+
     # jQuery function to get all attributes of an element
     $.fn.attrs = ->
         return {} if @length is 0


This fixes list counts. Somehow. George, your code is magical.

--- app/index.coffee
+++ app/index.coffee
@@ -53,8 +53,8 @@
       el: @listTitle

     # Load data for localStorage
+    Task.fetch()
     List.fetch()
-    Task.fetch()

     # Create inbox list
     if not !!List.exists("inbox")


List last season progress bar last

List last season progress bar last

--- app/index.coffee
+++ app/index.coffee
@@ -67,6 +67,7 @@
       {season, id, name, total, complete}

     sortedSeasons.sort (a, b) ->
+      return 1 if a.season is '0'
       a.season > b.season

     seasons.push sortedSeasons...


reformat ajax request

reformat ajax request

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -541,13 +541,18 @@
           # console.log JSON.stringify(formData_formatted)
           formData_formatted_JSON = JSON.stringify(formData_formatted)

-          url = "http://p-u-p-i-l.com/order/form_handler"
-
+          url = "https://p-u-p-i-l.com/order/form_handler"
+
           $.ajax
-            type: 'POST'
+            async: true
             crossDomain: true
             url: url
-            dataType: "json"
+            type: "POST"
+            headers: {
+              "content-type": "application/json",
+              "cache-control": "no-cache"
+            }
+            processData: false
             data: formData_formatted_JSON
             error: (jqXHR, textStatus, errorThrown) ->
               # console.warn "AJAX Error: #{textStatus}"


Fix ModalBox opened twice

Fix ModalBox opened twice

--- app/assets/javascripts/easy_app_bootstrap.js.coffee
+++ app/assets/javascripts/easy_app_bootstrap.js.coffee
@@ -1,6 +1,7 @@
 root = exports ? this

-root.openModalBox = (element) ->
+root.openModalBox = (event, element) ->
+  event.preventDefault()
   draggable = $(element).data('draggable')
   size      = $(element).data('modal-size')
   $.get $(element).attr('href'), (data) ->
@@ -11,7 +12,6 @@
     if size
       $('.modal-dialog').addClass('modal-' + size)
   return false
-

 root.createBootstrapSwitch = (element) ->
   callback = $(element).data('switch-callback')


Update manuscript manager headers

Update manuscript manager headers

--- app/assets/javascripts/manuscript_manager.js.coffee
+++ app/assets/javascripts/manuscript_manager.js.coffee
@@ -74,6 +74,7 @@
           for flow, position in @state.flows
             Tahi.manuscriptManager.Column {
               addFunction: @addColumn,
+              updateName: @updateColumnName
               position: flow.position
               name: flow.name
               tasks: flow.tasks,
@@ -151,6 +152,30 @@
         flow.position += 1
       updatedFlows = head.concat newPhase, tail
       @setState flows: updatedFlows
+
+    findPhase: (phase_id)->
+      _(@state.flows).find (phase)-> phase.id == phase_id
+
+    updateColumnName: (phase_id)->
+      (e)=>
+        storedPhase = @findPhase(phase_id)
+        if e.target.innerText != storedPhase.name
+          $.ajax
+            url: "/phases"
+            method: 'PUT'
+            data:
+              id: phase_id
+              phase:
+                name: e.target.innerText
+            success: (data)=>
+              newFlows = @state.flows.slice(0)
+              i = newFlows.indexOf storedPhase
+              # data.phase.tasks are being sent from the server in a different
+              # order, so use the same unchanged tasks to preserve order
+              tasks = newFlows[i].tasks.slice(0)
+              newFlows.splice(i, 1, data.phase)
+              newFlows[i].tasks = tasks
+              @setState flows: newFlows

     addColumn: (position) ->
       newPosition = position + 1
@@ -170,7 +195,6 @@
             name: column.name
         success: (data) => @insertPhase(data.phase)

-
   Column: React.createClass
     displayName: "Column"
     render: ->
@@ -179,7 +203,7 @@
         Tahi.manuscriptManager.ColumnAppender {
           addFunction: @props.addFunction,
           position: @props.position}
-        (h2 {}, @props.name),
+        (h2 {onBlur: @props.updateName(@props.phase_id), contentEditable: "true"}, @props.name),
         (div {className: 'column-content'},
           (ul {className: 'cards'},
             @manuscriptCards()


The place string ("4/11") now looks even better in various sizes. At least in Chrome.

--- app/assets/javascripts/roofpig/DomHandler.js.coffee
+++ app/assets/javascripts/roofpig/DomHandler.js.coffee
@@ -9,7 +9,6 @@
     @div.append(renderer.domElement);

     @scale = @div.width()/400
-    console.log("scale:#{@scale}")

   has_focus: (has_it) ->
     color = if has_it then 'gray' else '#eee'


make tests run on travis again

make tests run on travis again

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -72,7 +72,7 @@
           ui: 'bdd'
           reporter: 'spec'
       travis:
-        src: ['test/server/**/*_test.coffee']
+        src: ['test/server/testSpec.coffee']
         options:
           globals: ['UNorm']
           timeout: 3000


#773 - $(Gruntfile.coffee) add new js files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,6 +31,9 @@
         './client/js/handler.base.js',
         './client/js/handler.form.js',
         './client/js/handler.xhr.js',
+        './client/js/ui.handler.events.js',
+        './client/js/ui.handler.click.drc.js',
+        './client/js/ui.handler.click.filename.js'
     ]

     # jQuery plugin modules


Disable Mangling

Disable Mangling

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -211,6 +211,8 @@
 						removeStyleLinkTypeAttributes:  true

 		uglify:
+			options:
+				mangle: false
 			my_target:
 				files:
 					'.temp/scripts/scripts.min.js': '.temp/scripts/scripts.js'


Add missing concat to Gruntfile

Add missing concat to Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -68,4 +68,4 @@

     clean: ['tmp']

-  grunt.registerTask 'build', ['commonjs', 'coffee', 'clean']+  grunt.registerTask 'build', ['commonjs', 'coffee', 'concat', 'clean']


* minor change

* minor change

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -52,7 +52,7 @@
           'src/**/*.js'
           'test/*.coffee'
         ]
-
+

   #grunt.loadTasks('tasks')


Add copy taks and removed "on watch" stuff

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -46,6 +46,17 @@
           'dist/test/**/*.js'
         ]

+    copy:
+      dist:
+        files: [
+          expand: true,
+          cwd: 'test',
+          dest: 'dist/test/',
+          src: [
+            '**/*.json'
+          ]
+        ]
+
     watch:
       options:
         spawn: false
@@ -61,24 +72,12 @@

     clean: ['dist/']

-  grunt.event.on 'watch', (action, files, target)->
-    grunt.log.writeln "#{target}: #{files} has #{action}"
-
-    # coffeelint
-    grunt.config ['coffeelint', target], src: files
-
-    # coffee
-    coffeeData = grunt.config ['coffee', target]
-    files = [files] if _.isString files
-    files = files.map (file)-> path.relative coffeeData.cwd, file
-    coffeeData.src = files
-
-    grunt.config ['coffee', target], coffeeData

   # tasks.
   grunt.registerTask 'compile', [
     'coffeelint'
     'coffee'
+    'copy'
   ]

   grunt.registerTask 'test', [


Improve Grunt File Watches

Improve Grunt File Watches

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -153,7 +153,7 @@
         livereload: true
       buildConfig:
         files: 'client/build.config.coffee'
-        tasks: ['copy:vendor']
+        tasks: ['copy:vendor', 'template:build']
       html:
         files: 'client/*.html'
         tasks: ['template:build']
@@ -161,16 +161,16 @@
         files: 'client/fonts/**/*'
         tasks: ['copy:fonts']
       js:
-        files: 'client/**/*.js'
+        files: ['client/app/**/*.js']
         tasks: ['copy:js', 'commonjs:modules', 'concat:precompile']
       coffee:
-        files: 'client/**/*.coffee'
+        files: 'client/app/**/*.coffee'
         tasks: ['coffee', 'commonjs:modules', 'concat:precompile']
       handlebars:
-        files: 'client/**/*.hbs'
+        files: 'client/app/**/*.hbs'
         tasks: ['emberTemplates']
       less:
-        files: 'client/**/*.less'
+        files: 'client/less/**/*.less'
         tasks: ['recess:build']


Disabled coveralls until I figure out what is wrong with grunt-karma-coveralls+coveralls.io

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -136,7 +136,7 @@
     # Tasks
     grunt.registerTask 'test',[
         'karma'
-        'coveralls'
+#        'coveralls'
     ]
     grunt.registerTask 'build',[
         'clean',


forgot to change location for live reload.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,8 +11,8 @@
         options:
           livereload: true
         files: [
-          'templates/index.jade'
-          'templates/slides.md'
+          'presentation/templates/index.jade'
+          'presentation/templates/slides.md'
         ]
       coffeelint:
         files: [


More files to watch

More files to watch

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -102,7 +102,15 @@

     # Automated recompilation and testing when developing
     watch:
-      files: ['spec/*.coffee', 'src/*.coffee', 'src/**/*.coffee']
+      files: [
+        'spec/*.coffee'
+        'src/*.coffee'
+        'src/**/*.coffee'
+        'components/*.coffee'
+        'graphs/*.json'
+        'src/**/*.coffee'
+        'component.json'
+      ]
       tasks: ['test']

     # BDD tests on browser


Add grunt subtasks for CoffeeScript compilation to build and test tasks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -72,7 +72,7 @@
   @loadNpmTasks 'grunt-mocha-phantomjs'
   @loadNpmTasks 'grunt-contrib-watch'

-  @registerTask 'build', ['coffee', 'component', 'component_build', 'uglify']
-  @registerTask 'test', ['build', 'cafemocha', 'mocha_phantomjs']
+  @registerTask 'build', ['coffee:src', 'component', 'component_build', 'uglify']
+  @registerTask 'test', ['build', 'coffee:spec', 'cafemocha', 'mocha_phantomjs']
   #@registerTask 'test', ['build', 'jshint', 'cafemocha', 'mocha_phantomjs']
   @registerTask 'default', ['build']


improve Gruntfile for v0.8

improve Gruntfile for v0.8

* add mocha task
* to generate ja/de lang file
* edit build task to show "done!"

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -114,6 +114,10 @@
       qunit:
         files: ['tests/qunit/*/*.html']

+      mocha:
+        all: 'tests/mocha/*.html'
+        core: 'test/mocha/enchant.js/*.html'
+
       exec:
         lang:
           command: 'rake lang'
@@ -133,6 +137,7 @@
   grunt.loadNpmTasks 'grunt-contrib-uglify'
   grunt.loadNpmTasks 'grunt-contrib-qunit'
   grunt.loadNpmTasks 'grunt-contrib-watch'
+  grunt.loadNpmTasks 'grunt-mocha'
   grunt.loadNpmTasks 'grunt-exec'

   grunt.registerTask 'doc', 'Make jsdoc', ()->
@@ -155,6 +160,7 @@
     done = this.async();

     sh 'git checkout build/readme.md', ()->
+      grunt.log.writeln("done!");
       done

   grunt.registerTask 'cp', '', ()->
@@ -198,5 +204,5 @@

   # Default task.
   grunt.registerTask 'default', [
-    'jshint:core', 'concat', 'uglify', 'qunit', 'lang', 'build']
-
+    'jshint:core', 'concat', 'uglify', 'qunit', 'lang:en', 'lang:ja', 'lang:de', 'build']
+


add task for test

add task for test

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,6 +17,10 @@
         tasks: ["coffee:develop"]
         options:
           livereload: true
+
+      test:
+        files: "test/**/*.coffee"
+        tasks: ["coffee:test"]

       stylus:
         files: "stylus/**/*.styl",
@@ -43,6 +47,14 @@
           cwd: 'coffee/'
           src: ['**/*.coffee']
           dest: 'scripts/'
+          ext: '.js'
+        ]
+      test:
+        files: [
+          expand: true
+          cwd: 'test/'
+          src: ['**/*.coffee']
+          dest: 'test/'
           ext: '.js'
         ]


Update Gruntfile

Update Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,18 +22,17 @@
 				options:
 		            filter: 'exclude',
 		            tasks: [
-		            	'coffee'
+		            	'concurrent'
 		            	'execute'
 		            	'bunyan'
 		            	'availabletasks'
 		           	]
 		            groups:
-		            	"Compile tasks": [
-		            		"compile:server"
-		            		"compile"
-		            	]
 		            	"Run tasks": [
 		            		"run"
+		            		"run:all"
+		            		"run:web"
+		            		"run:document-updater"
 		            		"default"
 		            	]
 		            	"Misc": [
@@ -46,6 +45,7 @@
 	grunt.registerTask 'run:document-updater', "Run document-updater-sharelatex, the real-time document server", ["bunyan", "execute:document-updater"]

 	grunt.registerTask 'run', "Run all of the sharelatex processes", ['concurrent:all']
+	grunt.registerTask 'run:all', 'run'

 	grunt.registerTask 'default', 'run'


lint ignore line length

lint ignore line length

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -56,6 +56,9 @@
     # Coding standards
     coffeelint:
       components: ['components/*.coffee']
+      options:
+        'max_line_length':
+          'level': 'ignore'

   # Grunt plugins used for building
   @loadNpmTasks 'grunt-noflo-manifest'


refactored Gruntfile.coffee code

refactored Gruntfile.coffee code

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,4 @@
-ALL_TASKS = ['jst:all', 'coffee:all', 'concat:all', 'stylus:all', 'shell:copying']
+ALL_TASKS = ['jst:all', 'coffee:all', 'concat:all', 'stylus:all']

 module.exports = (grunt) ->

@@ -12,7 +12,6 @@
   grunt.loadNpmTasks('grunt-contrib-stylus')
   grunt.loadNpmTasks('grunt-contrib-uglify')
   grunt.loadNpmTasks('grunt-contrib-watch')
-  grunt.loadNpmTasks('grunt-shell')

   grunt.initConfig

@@ -58,12 +57,5 @@
         files: ['./coffee/**/*.coffee', 'templates/**/*.html', './styl/**/*.styl']
         tasks: ALL_TASKS

-    shell:
-      copying:
-        command: 'cp formbuilder.js /home/tanmay/Documents/bri/bri-rails/public/js/lib/',
-        options:
-          stdout: true
-          stderr: true
-
   grunt.registerTask 'default', ALL_TASKS
   grunt.registerTask 'dist', ['cssmin:dist', 'uglify:dist']


Remove release task for now

Remove release task for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -142,4 +142,3 @@

   grunt.registerTask 'default', ['watch']
   grunt.registerTask 'build',   ['coffee', 'jshint', 'concat']
-  grunt.registerTask 'release', ['clean', 'build', 'uglify']


Grunt: Add flash command

Grunt: Add flash command

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,13 +1,20 @@
+make =   "make -f ./node_modules/microflo/Makefile"
+common_options = [
+  "BUILD_DIR=#{process.cwd()}/build"
+  "MICROFLO=./node_modules/.bin/microflo"
+  "GRAPH=graphs/microsynchro.fbp"
+  "MICROFLO_SOURCE_DIR=`pwd`/node_modules/microflo/microflo"
+]
+
 microflo = (target) ->
-    build = [
-        "make -f ./node_modules/microflo/Makefile"
-        target
-        "BUILD_DIR=#{process.cwd()}/build"
-        "MICROFLO=./node_modules/.bin/microflo"
-        "GRAPH=graphs/microsynchro.fbp"
-        "MICROFLO_SOURCE_DIR=`pwd`/node_modules/microflo/microflo"
-    ]
-    return build.join ' '
+  build = [ make, target ]
+  build = build.concat common_options
+  return build.join ' '
+
+flash = (device) ->
+  build = [ make, "upload", "SERIALPORT=#{device}" ]
+  build = build.concat common_options
+  return build.join ' '

 module.exports = ->
   # Project configuration
@@ -21,6 +28,7 @@
     exec:
       microflo_emscripten: microflo "build-emscripten"
       microflo_arduino: microflo "build-arduino"
+      microflo_flash: flash '/dev/ttyACM0'

     # Updating the package manifest files
     noflo_manifest:
@@ -129,6 +137,9 @@
     if target in ['all', 'arduino']
       @task.run 'exec:microflo_arduino'

+  @registerTask 'flash', '', () =>
+    @task.run 'exec:microflo_flash'
+
   @registerTask 'build', 'Build NoFlo for the chosen target platform', (target = 'all') =>
     if target is 'all' or target is 'browser' or target is 'nodejs'
       @task.run 'coffee'


feat(Gruntfile.coffee): #897 - ref new S3 signature ajax requester module & ensure images are copied to build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,6 +34,7 @@
         './client/js/uploader.s3.js',
         './client/js/ajax.requester.js',
         './client/js/deletefile.ajax.requester.js',
+        './client/js/s3.policysignature.ajax.requester.js',
         './client/js/window.receive.message.js',
         './client/js/handler.base.js',
         './client/js/handler.form.js',
@@ -349,6 +350,13 @@
                 flatten: true
                 src: ['./build/*']
                 dest: './test/temp'
+            images:
+                files: [
+                    expand: true
+                    cwd: './client/'
+                    src: ['*.gif']
+                    dest: './build/'
+                ]

         # Compress
@@ -462,6 +470,11 @@
                     'build'
                     'test'
                 ]
+            images:
+                files: ['./client/*.gif']
+                tasks: [
+                    'copy:images'
+                ]

         # Increment version with semver
         # ----------
@@ -633,6 +646,7 @@
         'concat'
         'minify'
         'usebanner'
+        'copy:images'
     ]

     # Prepare


Changes to clean task calls

Changes to clean task calls

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -42,8 +42,7 @@
           keepalive: true

     clean:
-      dev: ["release"]
-      release: ["release",]
+      release: ["release"]
       tmp: [ "release/tmp"]

     coffee:
@@ -190,7 +189,6 @@
   # Start server in preview mode
   grunt.registerTask "preview", [
     "clean:release"
-    "clean:tmp"
     "concurrent:release"
     "concurrent:optimize"
     "clean:tmp"
@@ -200,7 +198,6 @@
   # Build optimized files
   grunt.registerTask "build", [
     "clean:release"
-    "clean:tmp"
     "concurrent:release"
     "concurrent:optimize"
     "clean:tmp"
@@ -209,7 +206,6 @@
   # Deploy to GitHub Pages
   grunt.registerTask "shipit", [
     "clean:release"
-    "clean:tmp"
     "concurrent:release"
     "concurrent:optimize"
     "clean:tmp"


Updated to use the new hooks

Updated to use the new hooks

--- progress.coffee
+++ progress.coffee
@@ -111,12 +111,12 @@
 		@element.css 'width', '100%'
 		@

-Router.load ->
+Router.onRun ->
 	# Take the options from the route, if any
 	IronRouterProgress.start @route.options?.progress or {}
 	@

-Router.before (pause) ->
+Router.onBeforeAction (pause) ->
 	if @ready()
 		IronRouterProgress.done()
 	else
@@ -127,11 +127,11 @@
 			@stop()
 	@

-Router.after ->
+Router.onAfterAction ->
 	IronRouterProgress.done()
 	@

-Router.unload ->
+Router.onStop ->
 	IronRouterProgress.reset()
 	@


Optimize how fetch posts pool inserts new posts into DOM

--- assets/javascripts/views/fetch_posts_pool.js.coffee
+++ assets/javascripts/views/fetch_posts_pool.js.coffee
@@ -66,12 +66,21 @@
   hide: => @$el.hide()

   emptyPool: =>
-    pool = @pool.sortBy (i) -> i.published_at
-    for i in [0...@num_new_posts]
-      post = pool.pop()
-      @pool.collection.pop()
+    new_posts = @pool.sortBy (i) -> i.published_at
+    @pool.collection.reset()
+
+    html = []
+    for post in new_posts
       @posts.unshift(post)
-      TentStatus.Views.Post.insertNewPost(post, @$elements.posts_list, @postsFeedView)
+      html.unshift TentStatus.Views.Post::renderHTML(TentStatus.Views.Post::context(post), @postsFeedView.partials)
+    html = html.join('')
+
+    $top_post = $('.post:first', @$elements.posts_list)
+    @$elements.posts_list.prepend(html)
+
+    _.each $top_post.prevAll('.post'), (el, index) =>
+      view = new TentStatus.Views.Post el: el, post: new_posts[(new_posts.length-1) - index], parentView: @postsFeedView
+      view.trigger 'ready'

     @pool.since_id = @postsFeedView.posts.first()?.get('id')
     @num_new_posts = 0


change time frame

change time frame

--- atmospheric/002-accumulated-heat/accumulator.coffee
+++ atmospheric/002-accumulated-heat/accumulator.coffee
@@ -44,13 +44,12 @@
         in1 + in2

     exports.accumulate = (tao, time, light1, light2) ->
-        lday = time / p.period
         for i in [0..128]
             for j in [0..128]
                 cur = 128 * i + j
                 energyIn = sc * input(lng(i), lat(j), time, light1, light2) * tao
                 data[cur] = data[cur] + energyIn
-                avg[cur] = data[cur] / lday
+                avg[cur] = data[cur] / (time + 0.01)

         [128, 128 * 128, avg]
     exports.data = data


Add loading indicator and empty state to inbox

--- app/talk/inbox.cjsx
+++ app/talk/inbox.cjsx
@@ -7,6 +7,7 @@
 PromiseToSetState = require '../lib/promise-to-set-state'
 Paginator = require './lib/paginator'
 Router = {Link} = require 'react-router'
+Loading = require '../components/loading-indicator'

 PAGE_SIZE = 10

@@ -18,6 +19,7 @@
     user: null
     conversations: []
     conversationsMeta: {}
+    loading: true

   componentDidMount: ->
     @handleAuthChange()
@@ -38,7 +40,7 @@
     talkClient.type('conversations').get({user_id: @state.user.id, page_size: PAGE_SIZE, page,sort: '-updated_at'})
       .then (conversations) =>
         conversationsMeta = conversations[0]?.getMeta()
-        @setState {conversations, conversationsMeta}
+        @setState {conversations, conversationsMeta, loading: false}

   onPageChange: (page) ->
     @goToPage(page)
@@ -61,11 +63,16 @@

   render: ->
-    {conversations, user} = @state
+    {conversations, user, loading} = @state

Inbox

-      {if not user
-
Please sign in to view your inbox

+
+      {if loading
+        <Loading />
+      else if not user
+
Please sign in to view your inbox

+      else if conversations?.length is 0
+
You have not started any private conversations yet

       else if conversations?.length

           {conversations?.map(@conversationLink)}


tableverse/shape: Refactor towards support for additional shapes.

--- Oghma/tableverse/shape.coffee
+++ Oghma/tableverse/shape.coffee
@@ -16,29 +16,35 @@
 Oghma.Thingy ?= {}

 c_layer = 'annotations'
+c_shapes =
+  rectangle:
+    create: -> new Kinetic.Rect()
+    extra:  -> { x:0, y:0 }
+  circle:    true

 Oghma.Thingy.Tableverse.register( ( thingyverse, O ) ->

   class ShapeDelegate extends Oghma.KineticThingyDelegate
     draw: ->
-      if @thingy().gets( 'shape' ) != 'rectangle'
+      shape = @thingy().gets( 'shape' )
+      if ! c_shapes[shape]?
         throw "Unsupported shape: #{shape}"

       if ! @__k?
-        @__k = new Kinetic.Rect()
+        @__k = c_shapes[shape].create()
         @group().add( @__k )

       [ fill, stroke, width, height, opacity ] =
         @thingy().geta( 'fill', 'stroke', 'width', 'height', 'opacity' )
-      @__k.setAttrs(
+      attrs =
         fill:    fill
         stroke:  stroke
         opacity: opacity
         width:   width
         height:  height
-        x:       0
-        y:       0
-      )
+      for k, v of c_shapes[shape].extra( @thingy() )
+        attrs[k] = v
+      @__k.setAttrs( attrs )

     remove: ( thingy ) ->
       thingyverse.dice.remove( thingy )


Removed manifest task from gulp/shared

Removed manifest task from gulp/shared

--- client/gulp/shared.coffee
+++ client/gulp/shared.coffee
@@ -24,18 +24,4 @@
     stream.pipe uglify() if process.env.NODE_ENV == 'prod'
     stream.pipe gulp.dest paths.dest + 'js/'

-  # Compiles NodeWebKit package.json manifest
-  gulp.task 'manifest', ->
-
-    writeManifest = ->
-      manifest = require('./app/manifest.coffee')
-      plugins.fs.writeFile( paths.dest + paths.manifest.dest, manifest )
-      # plugins.fs.writeFile(paths.manifest.dest, manifest)
-
-    plugins.fs.exists paths.dest, (exists) ->
-      if exists
-        writeManifest()
-      else
-        plugins.fs.mkdir paths.dest, -> writeManifest()
-
 # # # # #


Cleaning up console.log.

Cleaning up console.log.

--- client/publication.coffee
+++ client/publication.coffee
@@ -1177,7 +1177,6 @@
   # Save reference to the template of the local annotation for access in global event handlers
   if @data.local and @data.editing
     localAnnotationEditorTemplate = @
-    console.log localAnnotationEditorTemplate

 Template.annotationEditor.destroyed = ->
   destroyEditor @


starting to work well

starting to work well

--- bot.coffee
+++ bot.coffee
@@ -2,6 +2,7 @@

 require('dotenv').config()

+Botkit = require 'botkit'
 SlackStrategy = require('./lib/passport-slack.js')
 async = require 'artillery-async'
 bodyParser = require 'body-parser'
@@ -15,6 +16,8 @@

 app = express()

+# AUTH --------------------------------------------------------------------------
+
 passport.use new SlackStrategy {
   clientID: process.env.SLACK_CLIENT_ID
   clientSecret: process.env.SLACK_CLIENT_SECRET
@@ -25,12 +28,14 @@
     return done params?.error or "Failed to get any data from oauth"
   team = params.team_id
   async.series [
-    (cb) -> setMetaData team, 'teamName', params.team, cb
+    (cb) -> setMetaData team, 'teamName', params.team_name, cb
     (cb) -> setMetaData team, 'teamId', params.team_id, cb
     (cb) -> setMetaData team, 'adminUserId', params.user_id, cb
     (cb) -> setMetaData team, 'botUserId', params.bot.bot_user_id, cb
     (cb) -> setMetaData team, 'token', params.bot.bot_access_token, cb
   ], (err) ->
+    if not err
+      startBot team
     done err, params

 app.use passport.initialize()
@@ -50,14 +55,7 @@
 app.listen process.env.PORT, ->
   log.info "Listening on #{process.env.PORT}..."

-bots = {}
-
-startBot = (team) ->
-  bots[team] ?= {}
-
-for file in fs.readdirSync process.env.DATA_DIR
-  log.info "Found db file for #{file}"
-  startBot(file)
+# FUNCTIONS ---------------------------------------------------------------------

 getDatabase = (team) ->
   if not process.env.DATA_DIR? then throw new Error('DATA_DIR missing')
@@ -69,7 +67,7 @@

   db.get "SELECT value FROM metadata WHERE key = $key", {$key: key}, (err, row) ->
     if err then return cb "Couldn't get metadata for team #{team}: #{err}"
-    return cb null, row[0]
+    return cb null, row.value

 setMetaData = (team, key, value, cb) ->
   db = getDatabase team
@@ -77,14 +75,88 @@

   db.get "SELECT COUNT(*) FROM metadata", (err, row) ->
     if err
-      db.run "CREATE TABLE metadata (key TEXT, value TEXT)", (err) ->
-        if err then return cb "Couldn't create metadata table for team #{team}"
-        db.run "INSERT INTO metadata VALUES($key, $value)", {$key: key, $value: value}, (err) ->
-          if err then return cb "Couldn't insert metadata #{key} into table for team #{team}: #{err}"
-          log.info "Set metadata key #{key} for team #{team} with new table"
-          return cb null
+      async.series [
+        (cb) -> db.run "CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT)", cb
+        (cb) -> db.run "CREATE TABLE factoids (key TEXT PRIMARY KEY, value TEXT, last_edit TEXT)", cb
+        (cb) -> db.run "INSERT INTO metadata VALUES($key, $value)", {$key: key, $value: value}, cb
+      ], (err) ->
+        if err then return cb "Couldn't initialize metadata for team #{team}: #{err}"
+        log.info "Set metadata key #{key} for team #{team} with new table"
+        return cb null
     else
-      db.run "UPDATE metadata SET value = $value WHERE key = $key", {$key: key, $value: value}, (err) ->
+      db.run "INSERT OR REPLACE INTO metadata VALUES($key, $value)", {$key: key, $value: value}, (err) ->
         if err then return cb "Couldn't update metadata for team #{team}: #{err}"
         log.info "Set metadata key #{key} for team #{team} with existing table"
         return cb null
+
+# BOTS --------------------------------------------------------------------------
+
+bots = {}
+
+controller = Botkit.slackbot(
+  clientID: process.env.SLACK_CLIENT_ID
+  clientSecret: process.env.SLACK_CLIENT_SECRET
+  scopes: ['bot']
+  debug: process.env.DEBUG
+  send_via_rtm: true
+)
+
+#handler = (event) ->
+  #return (bot, msg) ->
+    #return if msg.type in ['user_typing', 'desktop_notification']
+    #return if msg.user is bot.identity?.id
+    #console.log new Array(80).join('-')
+    #console.log "TEAM: #{bot.identifyTeam()} - TYPE: #{event} - DATA:"
+    #console.log inspect msg
+    #bot.reply msg, text: "#{event} ```#{inspect msg}```", (err) ->
+      #log.error "Failed to reply: #{err}"
+#for i in ['message_received', 'mention', 'direct_message', 'direct_mention', 'ambient']
+  #controller.on i, handler(i)
+
+handleMessage = (bot, channel, isDirect, message) ->
+  team = bot.identifyTeam()
+  bot.reply {
+    channel: channel
+  }, {
+    text: ":star: channel=#{channel} isDirect=#{isDirect} message=#{message}"
+  }
+
+controller.on 'direct_mention', (bot, msg) ->
+  return if msg.user is bot.identity?.id
+  handleMessage bot, msg.channel, true, msg.text
+
+controller.on 'direct_message', (bot, msg) ->
+  return if msg.user is bot.identity?.id
+  handleMessage bot, msg.channel, true, msg.text
+
+controller.on 'ambient', (bot, msg) ->
+  return if msg.user is bot.identity?.id
+  name = bot.identity?.name
+  text = msg.text
+  if text.toLowerCase().indexOf("#{ name } ") == 0
+    text = text.substr(name.length + 1)
+    handleMessage bot, msg.channel, true, text
+  else
+    handleMessage bot, msg.channel, false, text
+  return
+
+startBot = (team) ->
+  log.info "Starting bot for team #{team}..."
+
+  if team of bots
+    log.warning "Bot for #{team} already started. Returning."
+    return
+
+  getMetaData team, 'token', (err, token) ->
+    if err
+      log.error "Couldn't get token for #{team}: #{err}"
+      return
+    bot = bots[team] = controller.spawn(token: token)
+    bot.startRTM()
+
+for file in fs.readdirSync process.env.DATA_DIR
+  log.info "Found db file for #{file}"
+  startBot(file)
+
+# LOGIC -------------------------------------------------------------------------
+


helper: adding docs

helper: adding docs

--- app/_src/helpers/rio-images-loaded.coffee
+++ app/_src/helpers/rio-images-loaded.coffee
@@ -1,3 +1,21 @@
+# Helper that returns a promise with the of the loading images.
+# For example, if you want to animate a section only when
+# all images have been loaded through Responsive.io.
+#
+# E.g.:
+#
+# refreshImages: ($images) ->
+# 	i = 0
+# 	imagesLen = $images.length
+# 	imagesLoaded = new FOURTEEN.RIOImagesLoaded( $images )
+# 	for i in [0..imagesLen] do ResponsiveIO.refresh( $images[i] )
+#
+# init: () =>
+# 	$.when( imagesLoaded.getState() ).done =>
+# 		# hide spinner..
+# 		# animate stuff....
+#
+
 class FOURTEEN.RIOImagesLoaded

 	DATA_RIO_WIDTH = 'rio-width'


Un-break articles.js. See #31

Un-break articles.js. See #31

--- app/assets/javascripts/articles.js.coffee
+++ app/assets/javascripts/articles.js.coffee
@@ -2,18 +2,102 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-$ ->
-  $('#make-fresh').click (e) ->
-	  e.preventDefault()
-	  article_id = $(this).attr('data-id')
+@initialized = false

-	  $.ajax(
-	    type: 'PUT'
-	    url: '/articles/' + article_id + '/make_fresh'
-	    dataType: 'json'
-	   ).done ->
-  	 		$('.stale').remove()
-  	 		$('#make-fresh').remove()
-  	 		$('#stale-fresh').append 'fresh'
+initialize = (method) ->
+  if @initialized == true && !@method == "jquery"
+    console.log "already initialized"
+    return false
+
+  @initialized = true
+  @method = method
+  console.log "proceeding with initialization via #{method}"
+  delay = (ms, func) -> setTimeout func, ms
+
+  submit_form = ->
+    console.log "submitted that damned form"
+    $('.search-bar form').submit()
+
+  localize_datetimes = ->
+    dates = $(".articles time")
+    for date in dates
+      old_time = $(date).attr('datetime')
+      new_time = moment(old_time).format('MMM Do YYYY')
+      $(date).html(new_time)
+
+  attach_loading_indicators = ->
+    $(document).on 'page:fetch', -> NProgress.start()
+    $(document).on 'page:load', -> NProgress.done()
+
+  timeout = null
+
+  attach_loading_indicators()
+
+  $('#search').on 'keyup', ->
+    clearTimeout(timeout) if timeout
+    timeout = delay 400, -> submit_form()
+
+  $('html').on 'keydown','#article_content', (evt)->
+    if evt.keyCode == 9
+      $('textarea#article_content').insertAtCaret('  ')
+      evt.preventDefault()
+
+  $('#article_tag_tokens').tokenInput '/tags.json',
+    theme: "facebook"
+    prePopulate: $('#article_tag_tokens').data('load')
+
+  localize_datetimes()
+
+  date_inputs = $("#article_created_at, #article_updated_at")
+  for date in date_inputs
+    date = $(date)
+    default_date = $.datepicker.parseDate("yy-mm-dd", date.val())
+    date.datepicker({ defaultDate: default_date, dateFormat: 'M dd yy' })
+    old_time = date.val()
+    new_time = moment(old_time).format('MMM Do YYYY')
+    date.val(new_time)
+
+  $(document).on "keyup", keyboardEventHandler
+
+keyboardEventHandler = (event) ->
+  if event.keyCode == 70 or event.keyCode == 83
+    focusSearch()
+
+  if event.keyCode == 27
+    unfocusSearch()
+
+focusSearch = ->
+  $("#search").trigger "focus"
+
+unfocusSearch = ->
+  $("#search").trigger "blur"
+
+initialize_via_turbolinks = ->
+  console.log "attempting to initialize via turbolinks"
+  $(window).on "page:change", ->
+    initialize("turbolinks")
+
+initialize_via_jquery = ->
+  console.log "attempting to initialize via jQuery"
+  $ ->
+    initialize("jquery")
+
+make_fresh = ->
+	$ ->
+	  $('#make-fresh').click (e) ->
+		  e.preventDefault()
+		  article_id = $(this).attr('data-id')
+
+		  $.ajax(
+		    type: 'PUT'
+		    url: '/articles/' + article_id + '/make_fresh'
+		    dataType: 'json'
+		   ).done ->
+	  	 		$('.stale').remove()
+	  	 		$('#make-fresh').remove()
+	  	 		$('#stale-fresh').append 'fresh'

+initialize_via_turbolinks()
+initialize_via_jquery()
+make_fresh()


FIX coffeescript-1.6.3

FIX coffeescript-1.6.3

`for ... in` loops does not allow anymore the use of `own`, which does
not serve any purpose anyway.

Close #3

--- app/assets/javascripts/darwin/base.coffee
+++ app/assets/javascripts/darwin/base.coffee
@@ -71,5 +71,5 @@

   trigger: ( event_name, params... ) ->
     if @_bound[ event_name ]
-      for own callback in @_bound[ event_name ]
+      for callback in @_bound[ event_name ]
         callback( params... )


refactaring picture.js.coffee

refactaring picture.js.coffee

--- app/assets/javascripts/pictures.js.coffee
+++ app/assets/javascripts/pictures.js.coffee
@@ -41,7 +41,6 @@
     clear: ->
       @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
       @addHistory()
-      null

     save: ->
       dataURL = @canvas.toDataURL()
@@ -92,29 +91,85 @@
       @ctx.stroke()
       @ctx.closePath()

+    # 画像が読み込まれていれば true を返す。読み込まれていなければアラートを表示し false を返す
+    checkImage: (img) ->
+      if (img.width > 0) is false
+        alert "画像がありません。"
+        return false
+      true
+
+    # 適切な画像タイプならば true。対応していないタイプならばアラートを表示して false を返す
+    checkFileType: (text) ->
+      # ファイルタイプの確認
+      if text.match(/^image\/(png|jpeg|gif)$/) is null
+        alert "対応していないファイル形式です。\nファイルはPNG, JPEG, GIFに対応しています。"
+        return false
+      true
+
+    loadImg: (width, height) ->
+      _mycanvas = this
+      ->
+        try
+          width = width || @width;
+          height = height || @height;
+          _mycanvas.reload(@)
+          $(@).remove()
+        catch e
+          alert "画像を開けませんでした。"
+
+    # 画像読込ハンドラ
+    readFile: (reader) ->
+      =>
+        # img へオブジェクトを読み込む
+        this.img = $("").get(0)
+        this.img.onload = @loadImg()
+        this.img.setAttribute "src", reader.result
+
+    loadFile: (file) ->
+      # ファイルタイプの確認
+      return false  if @checkFileType(file.type) is false
+      # canvasに描画
+      reader = new FileReader()
+      reader.onload = @readFile(reader)
+      reader.readAsDataURL file
+
+    ondrop: (event) ->
+      if event.dataTransfer.files.length is 0
+        alert "画像を開けませんでした。"
+        return false
+      file = event.dataTransfer.files[0]  # ドロップされたファイル情報
+      # ファイルタイプの確認
+      return false  if @checkFileType(file.type) is false
+      # canvas への描画
+      reader = new FileReader()
+      reader.onload = @readFile(reader)
+      reader.readAsDataURL file
+      # バブリング・デフォルト処理停止
+      event.stopPropagation()
+      event.preventDefault()
+
+  # ====================================
+  # 保存した画像の一覧を表示
   listPictures = (myCanvas) ->
       $.get '/pictures/list.txt', (result)->
         # console.log result
         ids = result.split(',')
         pictures = $("#pictures")
         pictures.empty()
-        ids.forEach (id, i)->
-          if parseInt(id, 10) > 0
-            pictures.append("")
+        ids.forEach (id, i) ->
+          pictures.append("") if parseInt(id, 10) > 0
+
         thumb_pics = $("#pictures .pict_thumbnail")
         thumb_pics.click ->
           image = new Image()
           image.src = $(@).attr('src')
-          image.onload = ->
-            myCanvas.reload(image)
-        thumb_pics.mouseenter ->
-          $(@).addClass('pict_thumbnail-over')
-        thumb_pics.mouseout ->
-          $(@).removeClass('pict_thumbnail-over')
+          image.onload = -> myCanvas.reload(image)
+
+        thumb_pics.mouseenter -> $(@).addClass('pict_thumbnail-over')
+        thumb_pics.mouseout   -> $(@).removeClass('pict_thumbnail-over')
         null

   # ====================================
-  img = null
   canvas = $('#draw-area')[0]

   if canvas
@@ -144,106 +199,22 @@
     $("#clear-button").click -> myCanvas.clear()
     # undo
     $("#undo-button").click -> myCanvas.showHistory(-1)
-    # undo
+    # redo
     $("#redo-button").click -> myCanvas.showHistory(1)
     # 保存
     $("#save-button").click ->
       myCanvas.save()
       listPictures(myCanvas)
-    $("#download-button").click ->
-      myCanvas.download()
-
-    # 画像一覧
-    listPictures(myCanvas)
-
-    # エラー表示
-    alert = (text) -> window.alert text
-
-    # 画像が読み込まれていれば true を返す。読み込まれていなければアラートを表示し false を返す
-    checkImage = (img) ->
-      if (img.width > 0) is false
-        alert "画像がありません。"
-        return false
-      true
-
-    # 適切な画像タイプならば true。対応していないタイプならばアラートを表示して false を返す
-    checkFileType = (text) ->
-      # ファイルタイプの確認
-      if text.match(/^image\/(png|jpeg|gif)$/) is null
-        alert "対応していないファイル形式です。\nファイルはPNG, JPEG, GIFに対応しています。"
-        return false
-      true
-
-    #
-    # 画像ファイル読み込み・表示
-    #
-    # img 要素から画像データを
-    loadImg = (width, height) ->
-      ->
-        try
-          width = width || @width;
-          height = height || @height;
-          myCanvas.reload(this)
-          $(this).remove()
-        catch e
-          alert "画像を開けませんでした。"
-
-    # 画像読込ハンドラ
-    readFile = (reader) ->
-      ->
-        # img へオブジェクトを読み込む
-        img = $("").get(0)
-        img.onload = loadImg()
-        img.setAttribute "src", reader.result
-
-    # 縦幅の固定値が入力
-    validate = (event) ->
-      # 未入力はなにもしない
-      return false  if $(this).val() is ""
-      # キャスト(キャストできないときはエラーではなく NaN が返る)
-      height = parseInt($(this).val(), 10)
-      # 0 より大きい数字以外はアラート
-      if (height > 0) is false
-        alert "0 より大きい数字を入力してください。"
-        return false
-
-      # DOM要素の style プロパティでサイズ指定。!important を除いて最優先で提要。
-      # $('#rectangle').css() ではうまくいかない
-      $("#rectangle").get(0).style.height = $(this).val() + "px"
-      # #rectangle要素のresizeイベントを発生させる
-      $("#rectangle").trigger "resize"
-      # バブリング・デフォルト停止
-      event.stopPropagation()
-      event.preventDefault()
+    # ダウンロード
+    $("#download-button").click -> myCanvas.download()
+
+    listPictures(myCanvas)  # 保存した画像の一覧を表示

     # 参照ボタンで読込処理
-    $("#upload").change ->
-      # 選択したファイル情報
-      file = @files[0]
-      # ファイルタイプの確認
-      return false  if checkFileType(file.type) is false
-      # canvasに描画
-      reader = new FileReader()
-      reader.onload = readFile(reader)
-      reader.readAsDataURL file
-
-    # ドラッグアンドドロップで読込
-    $("#view").get(0).ondragover = -> false
-
-    # bind('ondrop', function() {});はうまく動かなかった(2012.11.07)
-    $("#view").get(0).ondrop = (event) ->
-      if event.dataTransfer.files.length is 0
-        alert "画像を開けませんでした。"
-        return false
-      # ドロップされたファイル情報
-      file = event.dataTransfer.files[0]
-      # ファイルタイプの確認
-      return false  if checkFileType(file.type) is false
-      # canvasへの描画
-      reader = new FileReader()
-      reader.onload = readFile(reader)
-      reader.readAsDataURL file
-      # バブリング・デフォルト処理停止
-      event.stopPropagation()
-      event.preventDefault()
-
+    $("#upload").change ->　myCanvas.loadFile(@files[0])
+
+    # ドラッグアンドドロップで読込む
+    canvas.ondragover = -> false
+    canvas.ondrop = (event) -> myCanvas.ondrop(event)
+
+    null


add times()

add times()

--- matrixSpec.coffee
+++ matrixSpec.coffee
@@ -20,3 +20,21 @@
         expect(A).toEqual [[1, 2], [3, 4], [5, 6]] # A not changed ?
         expect(B).toEqual [[0, 1], [2, 3], [4, 5]] # B adapted correctly ?

+
+    describe 'times() function', ->
+
+      it 'should be able to multiply a matrix in place', ->
+
+        A = [[1, 2]]
+        B = m.times(3, A)
+        expect(B).toBe A # in place ?
+        expect(B).toEqual [[3, 6]] # adapted correctly ?
+
+      it 'should be able to multiply to another matrix', ->
+
+        A = [[1, 2]]
+        B = [[]]
+        C = m.times(3, A, B)
+        expect(C).toBe B # B returned ?
+        expect(A).toEqual [[1, 2]] # A not changed ?
+        expect(B).toEqual [[3, 6]] # B adapted correctly ?


Added auto-terminate to workers

Added auto-terminate to workers

--- app/scripts/services/workerfactory.coffee
+++ app/scripts/services/workerfactory.coffee
@@ -8,7 +8,7 @@
  # Returns a promise-oriented Web Worker Constructor
 ###
 angular.module('pfdbApp')
-  .factory 'WorkerFactory', ($q) ->
+  .factory 'WorkerFactory', ($q, $timeout) ->

     class PromisingWorker
       constructor: (@script) ->
@@ -17,10 +17,21 @@
       run: (post) ->
         deferred = $q.defer()
         worker = new Worker @script
+
+        terminator = ->
+          # after 5 minutes, force terminate the worker
+          $timeout ->
+            worker.terminate()
+          , 5 * 60 * 1000
+
+        workerTimeout = terminator()
+
         worker.addEventListener "message", (result) ->
           if result.state is 'complete'
             deferred.resolve result.data
           else
+            $timeout.cancel(workerTimeout)
+            workerTimeout = terminator
             deferred.notify result.data

         worker.postMessage post


* [bug] test date wrong on travis for difference time zone.

--- test/bytewise-codec-test.coffee
+++ test/bytewise-codec-test.coffee
@@ -43,8 +43,8 @@
       assert.equal(encode(4294967318), 'Nf041f0000001600000')
       assert.equal(encode(-4294967318), 'Nf-be0ffffffe9fffff')
     it "should encode date type to a string", ->
-      assert.equal(encode(new Date(2014,1,1)), 'D042743e9073400000')
-      assert.equal(encode(new Date(-2014,1,1)), 'D-bd236a1e7c71ffff')
+      assert.equal(encode(new Date("2014-01-31T16:00:00.000Z")), 'D042743e9073400000')
+      assert.equal(encode(new Date("-002014-01-31T16:00:00.000Z")), 'D-bd236a1e7c71ffff')
     it "should encode string type to a string", ->
       assert.equal(encode("hi world"), '"hi world"')
       assert.equal(encode("你好"), '"你好"')
@@ -55,13 +55,13 @@
     it "should encode buffer type to a string", ->
       assert.equal(encode(new Buffer([1,2,3,4,5,6,7,8])), 'B0102030405060708')
     it "should encode array type to a string", ->
-      expected = [12345, 'good:\nhi,u.', new Date(2014,1,1), 1.2345, new Buffer([1,2,3,4,5,6,7,8])]
+      expected = [12345, 'good:\nhi,u.', new Date("2014-01-31T16:00:00.000Z"), 1.2345, new Buffer([1,2,3,4,5,6,7,8])]
       assert.equal encode(expected), '[Ni000003039,"good%3a\\nhi%2cu.",D042743e9073400000,Nf03ff3c083126e978d,B0102030405060708]'
     it "should encode object type to a string", ->
       expected =
         num:12345
         str:'good:\nhi,u.'
-        date:new Date(2014,1,1)
+        date:new Date("2014-01-31T16:00:00.000Z")
         float:1.2345
         buf:new Buffer([1,2,3,4,5,6,7,8])
       assert.equal encode(expected), '{num:Ni000003039,str:"good%3a\\nhi%2cu.",date:D042743e9073400000,float:Nf03ff3c083126e978d,buf:B0102030405060708}'
@@ -86,8 +86,8 @@
       assert.equal(decode('Nf041f0000001600000'), 4294967318)
       assert.equal(decode('Nf-be0ffffffe9fffff'), -4294967318)
     it "should decode date type", ->
-      assert.deepEqual(decode('D042743e9073400000'), new Date(2014,1,1))
-      assert.deepEqual(decode('D-bd236a1e7c71ffff'), new Date(-2014,1,1))
+      assert.deepEqual(decode('D042743e9073400000'), new Date("2014-01-31T16:00:00.000Z"))
+      assert.deepEqual(decode('D-bd236a1e7c71ffff'), new Date("-002014-01-31T16:00:00.000Z"))
     it "should decode string type", ->
       assert.equal(decode('"hi world"'), "hi world")
       assert.equal(decode('"你好"'), "你好")
@@ -103,13 +103,13 @@
     it "should decode buffer type ", ->
       assert.equal(decode('B0102030405060708').toString(), new Buffer([1,2,3,4,5,6,7,8]).toString())
     it "should decode array type to a string", ->
-      expected = [12345, 'good:\nhi,u.', new Date(2014,1,1), 1.2345, new Buffer([1,2,3,4,5,6,7,8])]
+      expected = [12345, 'good:\nhi,u.', new Date("2014-01-31T16:00:00.000Z"), 1.2345, new Buffer([1,2,3,4,5,6,7,8])]
       assert.deepEqual decode('[Ni000003039,"good%3a\\nhi%2cu.",D042743e9073400000,Nf03ff3c083126e978d,B0102030405060708]'), expected
     it "should decode object type", ->
       expected =
         num:12345
         str:'good:\nhi,u.'
-        date:new Date(2014,1,1)
+        date:new Date("2014-01-31T16:00:00.000Z")
         float:1.2345
         buf:new Buffer([1,2,3,4,5,6,7,8])
       assert.deepEqual decode('{num:Ni000003039,str:"good%3a\\nhi%2cu.",date:D042743e9073400000,float:Nf03ff3c083126e978d,buf:B0102030405060708}'), expected


Add more tests to custom html view

Add more tests to custom html view

--- test/core/customhtmlview.coffee
+++ test/core/customhtmlview.coffee
@@ -8,5 +8,16 @@

   describe 'constructor', ->
     it 'should instantiate without error', ->
-      router = new KDCustomHTMLView
-      router.should.exist
+      view = new KDCustomHTMLView
+      view.should.exist
+
+    it 'should set tag name', ->
+      htmlView = new KDCustomHTMLView 'koding'
+      htmlView.tagName.should.equal 'koding'
+
+    it 'should set link attributes', ->
+      htmlView = new KDCustomHTMLView {
+        tagName: 'a'
+      }
+
+      htmlView.options.attributes.href.should.equal '#'


- Fix level concept map tool.

- Fix level concept map tool.

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

 module.exports = class LevelConceptMap extends RootView
   template: template


Autoplay when observing others' replays

Autoplay when observing others' replays

--- app/views/play/level/PlayLevelView.coffee
+++ app/views/play/level/PlayLevelView.coffee
@@ -357,7 +357,9 @@
     @selectHero()

   onLoadingViewUnveiled: (e) ->
-    Backbone.Mediator.publish 'level:set-playing', playing: true if @level.get('type') in ['course-ladder', 'hero-ladder']  # We used to autoplay by default, but now we only do it if the level says to in the introduction script.
+    if @level.get('type') in ['course-ladder', 'hero-ladder'] or @observing
+      # We used to autoplay by default, but now we only do it if the level says to in the introduction script.
+      Backbone.Mediator.publish 'level:set-playing', playing: true
     @loadingView.$el.remove()
     @removeSubView @loadingView
     @loadingView = null


Animate the job's journey

Animate the job's journey

--- javascripts/application.js.coffee
+++ javascripts/application.js.coffee
@@ -36,12 +36,19 @@
       {status, worker} = progress
       @workers.add(id: worker).get(worker).addStatus(status)

-    @on 'all', -> console.log arguments
+    # @on 'all', -> console.log arguments

 class Job extends Backbone.Model
   defaults:
     status: 'waiting'
     dots: []
+
+  initialize: ->
+    @on 'change:worker', (_, worker)->
+      if worker
+        @trigger 'assigned', worker
+      else
+        @trigger 'completed'

   title: -> @id.match(/\/(.*)\./)[1].replace /_/g, ' '

@@ -64,7 +71,30 @@
   tagName: 'li'
   initialize: ->
     @listenTo @model, 'change', @render
-    @listenTo @model, 'change:worker', @remove
+    @listenTo @model, 'assigned', @assign
+    @listenTo @model, 'completed', @completed
+
+  assign: (worker)->
+    left = worker.get('left') - @$el.offset().left
+    top  = worker.get('top')  - @$el.offset().top
+    @$el.animate {translateX: "#{left}px", translateY: "#{top}px"},
+      duration: 500
+      complete: =>
+        @$el.remove()
+          .animate {translateX: "0px", translateY: "0px"}, duration: 0, complete: =>
+            @$el.appendTo $('#workers > li').eq(worker.id).find 'ul'
+
+
+  completed: ->
+    left = $('#finished').offset().left - @$el.offset().left
+    top  = $('#finished').offset().top  - @$el.offset().top
+    console.log left, top
+    @$el.animate {translateX: "#{left}px", translateY: "#{top}px"},
+      duration: 500
+      complete: =>
+        @$el.remove()
+          .animate {translateX: "0px", translateY: "0px"}, duration: 0, complete: =>
+            @$el.prependTo $ '#finished'

   render: ->
     @$el.addClass( @model.get 'status' ).html "
#{@model.title()}
"
@@ -73,15 +103,9 @@

 class View.Worker extends Backbone.View
   tagName: 'li'
-  initialize: ->
-    @listenTo @model, 'change', @render

   render: ->
-    @$el.html "
#{@model.id}
"
-    if job = @model.get 'job'
-      jobList = $ '
'
-      @$el.append jobList
-      new View.Job(model:job).render().$el.appendTo jobList
+    @$el.html "
#{@model.id}

"
     this

 class View.Flatware extends Backbone.View
@@ -89,9 +113,7 @@
     @listenTo @model.jobs, 'add', (job)->
       new View.Job(model: job).render().$el.appendTo '#waiting'

-    @listenTo @model.jobs, 'change:worker', (job, worker)->
-      unless worker
-        new View.Job(model: job).render().$el.appendTo '#finished'
-
     @listenTo @model.workers, 'add', (job)->
-      new View.Worker(model: job).render().$el.appendTo '#workers'
+      workerView = new View.Worker(model: job).render()
+      workerView.$el.appendTo '#workers'
+      workerView.model.set workerView.$el.offset()


Do not lint JS files if no .jshintrc file is found

--- atom/config.cson
+++ atom/config.cson
@@ -104,6 +104,8 @@
   "linter-jscs":
     onlyConfig: true
     preset: "jquery"
+  "linter-jshint":
+    disableWhenNoJshintrcFileInPath: true
   "linter-shellcheck": {}
   "markdown-preview":
     useGitHubStyle: true


Indent shell scripts with 2 spaces

Indent shell scripts with 2 spaces

--- atom/config.cson
+++ atom/config.cson
@@ -67,3 +67,6 @@
 ".nim.source":
   editor:
     tabLength: 2
+".shell.source":
+  editor:
+    tabLength: 2


Add Atom IDE UI config

Add Atom IDE UI config

--- atom/config.cson
+++ atom/config.cson
@@ -3,6 +3,12 @@
     general:
       analytics: false
   "atom-ide-ui":
+    "atom-ide-code-format":
+      formatOnSave: true
+      formatOnType: false
+    "atom-ide-diagnostics-ui":
+      autoVisibility: true
+      showDirectoryColumn: true
     "atom-ide-outline-view": {}
   "atom-package-deps":
     ignored: [


RegisterForm: comment-out auto filled register form

--- client/Login/Views/registerform.coffee
+++ client/Login/Views/registerform.coffee
@@ -3,13 +3,13 @@
   constructor:(options={},data)->
     super options, data

-    random = KD.utils.getRandomNumber()
+    # random = KD.utils.getRandomNumber()

     @email = new LoginInputViewWithLoader
       inputOptions    :
         name          : "email"
-        # placeholder   : "email address"
-        defaultValue  : "gokmen+#{random}@goksel.me"
+        placeholder   : "email address"
+        # defaultValue  : "gokmen+#{random}@goksel.me"
         testPath      : "register-form-email"
         validate      : @getEmailValidator()
         decorateValidation: no
@@ -27,8 +27,8 @@
       inputOptions       :
         name             : "username"
         forceCase        : "lowercase"
-        # placeholder      : "username"
-        defaultValue     : "gokmen-#{random}"
+        placeholder      : "username"
+        # defaultValue     : "gokmen-#{random}"
         testPath         : "register-form-username"
         keyup            : =>


removed heading slash from route while looking for it in jNames

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -145,7 +145,7 @@
         then onSuccess models
         else onError err
     else
-      KD.remote.api.JName.one {name: routeWithoutParams}, (err, jName)=>
+      KD.remote.api.JName.one {name: routeWithoutParams.slice(1)}, (err, jName)=>
         if err then onError err
         else if jName?
           models = []


router: develop slug with app names

router: develop slug with app names

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -198,6 +198,9 @@
     createSectionHandler = (sec)=>
       ({params:{name}, query})=> @openSection sec, name, query

+    createSectionHandlerForDevelop = (sec)=>
+      ({params:{name, slug}, query})=> @openSection slug, name, query
+
     createContentHandler       = @bound 'createContentDisplayHandler'
     createStaticContentHandler = @bound 'createStaticContentDisplayHandler'

@@ -227,6 +230,7 @@
       '/:name?/Members'                 : createSectionHandler 'Members'
       '/:name?/Topics'                  : createSectionHandler 'Topics'
       '/:name?/Develop'                 : createSectionHandler 'StartTab'
+      '/:name?/Develop/:slug'           : createSectionHandlerForDevelop 'Develop'
       '/:name?/Apps'                    : createSectionHandler 'Apps'
       '/:name?/Account'                 : createSectionHandler 'Account'
       '/:name?/Demos'                   : createSectionHandler 'Demos'


fix error when there is no account

fix error when there is no account

--- client/app/stores/account_store.coffee
+++ client/app/stores/account_store.coffee
@@ -104,6 +104,8 @@
     getDefaultMailbox: (accountID) ->

         account = _accounts.get(accountID) or @getDefault()
+        return null unless account
+
         mailboxes = account.get('mailboxes')
         mailbox = mailboxes.filter (mailbox) ->
             return mailbox.get('label') is 'INBOX'


PUB-972 Fix advanced parameters disclosure on model comparison page

--- client/src/scripts/scoring-view.coffee
+++ client/src/scripts/scoring-view.coffee
@@ -1088,7 +1088,7 @@
       comparisonRows.push inputParametersRow = [
         th [
           (div 'Input Parameters')
-          (hyperlink 'Show more', 'toggle-advanced-parameters')
+          (hyperlink 'Show more')
         ]
       ]
       comparisonRows.push durationRow = [ th 'Time' ]
@@ -1151,7 +1151,7 @@

       toggleAdvancedParameters = ($element) ->
         isHidden = yes
-        $toggleLink = $ '#toggle-advanced-parameters', $element
+        $toggleLink = $ '.y-link', $element
         $toggleLink.click ->
           if isHidden
             $('.y-expert', $element).show()


Tests: Helpers: Added timestamp to hashtags to make them unique

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -222,7 +222,7 @@
   sendHashtagActivity: (browser) ->

     paragraph = @getFakeText()
-    hashtag   = '#' + paragraph.split(' ')[0]
+    hashtag   = '#' + paragraph.split(' ')[0] + Date.now()
     post      = paragraph + ' ' + hashtag

     @doPostActivity(browser, post)


start iframe pinned

start iframe pinned

--- src/plugins/preview-iframe.coffee
+++ src/plugins/preview-iframe.coffee
@@ -10,6 +10,7 @@
       name: ''
       menu: @$iframe
       icon: 'play'
+      pinned: true

   getElement: ->
     @$iframe[0]


Time entry route test with duration only te

--- test/routes/time_entries.coffee
+++ test/routes/time_entries.coffee
@@ -57,6 +57,19 @@
       start: new Date('May 4 1987')
       end: new Date()
       message: 'I am a wonderful time entry :)'
+      projectId: testProject.id
+    request.post (base '/time_entries'), ops, (e,r,b)->
+      r.statusCode.should.be.equal 200
+      te = new TimeEntry b
+      timeEntriesCreated.push te
+      te.validate().should.be.true
+      done()
+
+  it 'should make a time entry with only duration', (done)->
+    ops = options()
+    ops.body =
+      duration: 40.5
+      message: 'check yourself before you wreck yourself'
       projectId: testProject.id
     request.post (base '/time_entries'), ops, (e,r,b)->
       r.statusCode.should.be.equal 200


[state] accept an object as transition in constructor

--- src/plugins/scaleApp.state.coffee
+++ src/plugins/scaleApp.state.coffee
@@ -16,7 +16,7 @@
       @start   = opts.start
       @current = opts.start
     if opts.transitions?
-      @addTransition id,t for id,t in opts.transitions
+      @addTransition id,t for id,t of opts.transitions

   start:    null
   current:  null


recipe: fix indentation.

recipe: fix indentation.

--- rime/recipe.coffee
+++ rime/recipe.coffee
@@ -96,18 +96,18 @@

   # callback: (err) ->
   copyFile: (src, callback) ->
-      fileName = path.basename src
-      dest = "#{@rimeDirectory}/#{fileName}"
-      fs.createReadStream(src)
-        .on('error', (e) ->
-          console.log "error copying file: #{e.message}"
-          callback e
-        )
-        .on('end', =>
-          console.log "#{fileName} copied to #{@rimeDirectory}"
-          callback()
-        )
-        .pipe fs.createWriteStream(dest)
+    fileName = path.basename src
+    dest = "#{@rimeDirectory}/#{fileName}"
+    fs.createReadStream(src)
+      .on('error', (e) ->
+        console.log "error copying file: #{e.message}"
+        callback e
+      )
+      .on('end', =>
+        console.log "#{fileName} copied to #{@rimeDirectory}"
+        callback()
+      )
+      .pipe fs.createWriteStream(dest)

   # callback: (err) ->
   installSchema: (schemaId, callback) ->


Update tests to new behaviour

Update tests to new behaviour

- Ignore border width when auto-sizing
- Fit layer size directly around text instead of the max-width of the size


Update javascript.js.coffee

Update javascript.js.coffee

--- lib/templates/coffee/assets/javascript.js.coffee
+++ lib/templates/coffee/assets/javascript.js.coffee
@@ -6,17 +6,17 @@
     return this

-  index: ->
+  index: =>
     return

-  show: ->
+  show: =>
     return

-  new: ->
+  new: =>
     return

-  edit: ->
-    return+  edit: =>
+    return


using data object

using data object

--- bokehjs/src/coffee/widget/dropdown.coffee
+++ bokehjs/src/coffee/widget/dropdown.coffee
@@ -43,7 +43,7 @@
     for item in @mget("menu")
       $item = if item?
         [label, action] = item
-        $a = $('').text(label)
+        $a = $('').text(label).data('action', action)
         that = this
         $a.click((e) -> that.change_input($(this).data('action')))
         $('

').append($a)


woops I left in a log line

woops I left in a log line

--- src/smc-webapp/latex/utils.coffee
+++ src/smc-webapp/latex/utils.coffee
@@ -39,7 +39,7 @@
     # run, then the tmp dir will not get cleaned up.   This is OK with
     # kucalc, since it's tmpfs local to the project, and will get fixed
     # longrun by replacing this code by a stateful backend.
-    console.log 'setting timer to do rm_tmp'
+    #console.log 'setting timer to do rm_tmp'
     rm_tmp = ->
         command = "find . -type d -name 'tex-preview-*' -not -newermt '-#{opts.ttl} seconds' -exec rm -rf {} \\;"
         #console.log 'doing rm_tmp', command


make some fixes per PR review

make some fixes per PR review

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1998,18 +1998,17 @@
         public_view = @props.get_my_group(@props.project_id) == 'public'
         if public_view
             if @props.redux.getStore('account').is_logged_in()
-                <ErrorDisplay title="Showing only public files" error={"You are trying to access a project that you are not a collaborator on. To view non-public files or edit files in this project you need to ask a collaborator of the project to add you."} />
+                <ErrorDisplay bsStyle="warning" title="Showing only public files" error={"You are trying to access a project that you are not a collaborator on. To view non-public files or edit files in this project you need to ask a collaborator of the project to add you."} />
             else

-                    <ErrorDisplay title="Showing only public files" error={"You are not logged in. To view non-public files or edit files in this project you need to ask a collaborator of the project to add you."} />
-                    <AccountPage />
+                    <ErrorDisplay bsStyle="warning" title="Showing only public files" error={"You are not logged in. To view non-public files or edit files in this project you'll need to sign in. If you are not a collaborator then you need to ask a collaborator of the project to add you to access non public files."} />

         else
             if @props.redux.getStore('account').is_logged_in()
                 <ErrorDisplay title="Directory is not public" error={"You are trying to access a non public project that you are not a collaborator on. You need to ask a collaborator of the project to add you."} />
             else

-                    <ErrorDisplay title="Directory is not public" error={"You are not logged in. If you are collaborator on this project you need to log in first. This project is not public."} />
+                    <ErrorDisplay title="Directory is not public" error={"You are not signed in. If you are collaborator on this project you need to sign in first. This project is not public."} />
                     <AccountPage />

@@ -2160,8 +2159,8 @@
                 </Col>
                 {@render_files_action_box(file_map, public_view) if @props.checked_files.size > 0 and @props.file_action?}
             </Row>
+            {@render_access_error() if public_view}
             {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
             {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
             {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
-            {@render_access_error() if public_view}


Unify Create button and enter key behavior

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -353,6 +353,8 @@
     handle_click : ->
         if not @props.file_search?.length > 0
             @props.actions.set_focused_page('project-new-file')
+        else if @props.file_search?[@props.file_search.length - 1] == '/'
+            @props.create_folder()
         else
             @props.create_file()

@@ -386,7 +388,7 @@
                 text = "Will create #{@filename_text().slice(last_folder_index + 1)} under the folder path #{@props.file_search.slice(0, last_folder_index + 1)}"
             style =
                 wordWrap:'break-word'
-                marginTop:'4px'
+                marginTop:'10px'
             <Alert style={style} bsStyle='info'>
                 {text}
             </Alert>
@@ -1421,6 +1423,8 @@
         if @props.file_search.length == 0
             # Tell state to render an error in file search
             @props.actions.setState(create_file_alert : true)
+        else if @props.file_search?[@props.file_search.length - 1] == '/'
+            @props.create_folder()
         else
             @props.create_file(ext)

@@ -1428,6 +1432,8 @@
     handle_file_click : ->
         if @props.file_search.length == 0
             @props.actions.set_focused_page('project-new-file')
+        else if @props.file_search?[@props.file_search.length - 1] == '/'
+            @props.create_folder()
         else
             @props.create_file()


Remove debug

Remove debug

--- lib/assets/javascripts/collection.js.coffee
+++ lib/assets/javascripts/collection.js.coffee
@@ -33,7 +33,6 @@
     TentStatus.tent_client.post.list(params: params, callback: ((res, xhr) => @fetchComplete(params, options, res, xhr)))

   fetchComplete: (params, options, res, xhr) =>
-    console.log('fetch complete', res, xhr.status)
     models = null
     if xhr.status == 200
       # success


forward and reverse states are now cached

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -1,8 +1,7 @@
-@historyCache = []
+historyCache = []

 visit = (url) ->
   if browserSupportsPushState? and document.location.href != url
-    @historyCache.push({url:document.location.href,body:document.body,title:document.title})
     reflectNewUrl url
     fetchReplacement url
   else
@@ -17,22 +16,24 @@
   xhr.onabort = -> console.log("Aborted turbolink fetch!")
   xhr.send()

-fetchHistory = (url) ->
-  while(@historyCache.length > 0)
-    cache = @historyCache.pop()
-    if cache.url == url
-      replaceDocument cache.body, cache.title
-      triggerPageChange
-      return
-
-  fetchReplacement url
+fetchHistory = (state) ->
+  cache = historyCache[state.position]
+  if cache
+    console.log(state.position)
+    console.log(cache)
+    replaceDocument cache.body, cache.title
+    triggerPageChange
+  else
+    fetchReplacement document.location.href

 fullReplacement = (html, url) ->
   replaceHTML html
   triggerPageChange()

 reflectNewUrl = (url) ->
-  window.history.pushState { turbolinks: true }, "", url
+  window.history.pushState { turbolinks: true,position: window.history.length }, "", url
+
+

 triggerPageChange = ->
   event = document.createEvent 'Events'
@@ -69,6 +70,8 @@
   document.documentElement.appendChild body, originalBody
   document.documentElement.removeChild originalBody
   document.title = title
+  historyCache[window.history.state.position] = {url:document.location.href,body:body,title:title} if cache
+

 extractLink = (event) ->
@@ -106,15 +109,15 @@
 browserSupportsPushState = window.history and window.history.pushState and window.history.replaceState

 rememberInitialPage = ->
-  window.history.replaceState { turbolinks: true }, "", document.location.href
+  window.history.replaceState { turbolinks: true, position: window.history.length - 1}, "", document.location.href
+  historyCache[window.history.state.position] = {url:document.location.href,body:document.body,title:document.title}

 if browserSupportsPushState
-  rememberInitialPage()
-
+  document.addEventListener 'DOMContentLoaded', (event) ->
+    rememberInitialPage()
   window.addEventListener 'popstate', (event) ->
     if event.state?.turbolinks
-      fetchHistory document.location.href
-
+      fetchHistory event.state
   document.addEventListener 'click', (event) ->
     handleClick event


Pass in useCapture flag

Pass in useCapture flag

This flag became optional in recent versions of browsers

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -172,11 +172,11 @@
     doc.close()
     doc

-  # Use createDocumentUsingParser if DOMParser is defined and natively
+  # Use createDocumentUsingParser if DOMParser is defined and natively
   # supports 'text/html' parsing (Firefox 12+, IE 10)
   #
   # Use createDocumentUsingDOM if createDocumentUsingParser throws an exception
-  # due to unsupported type 'text/html' (Firefox < 12, Opera)
+  # due to unsupported type 'text/html' (Firefox < 12, Opera)
   #
   # Use createDocumentUsingWrite if:
   #  - DOMParser isn't defined
@@ -196,8 +196,8 @@

 installClickHandlerLast = (event) ->
   unless event.defaultPrevented
-    document.removeEventListener 'click', handleClick
-    document.addEventListener 'click', handleClick
+    document.removeEventListener 'click', handleClick, false
+    document.addEventListener 'click', handleClick, false

 handleClick = (event) ->
   unless event.defaultPrevented
@@ -242,6 +242,7 @@
   document.addEventListener 'click', installClickHandlerLast, true
   window.addEventListener 'popstate', (event) ->
     fetchHistory event.state if event.state?.turbolinks
+  , false

 browserSupportsPushState =
   window.history and window.history.pushState and window.history.replaceState and window.history.state != undefined


Fix disappearing progress bar when using transition cache

The progress bar will disappear when you visit 2 pages which load between half speed and speed (150 ms and 300 ms).

When using transition cache ProgressBar#done and consecutively ProgressBar#_reset are called twice, once when the page is loaded from the cache, once after the replacement page is fetched. ProgressBar#_reset stores the current opacity and starts 2 timers, one with half speed which sets the opacity to 0 and one with the normal speed which restores the opacity. If you manage to load a page between these timeouts (150 ms and 300 ms) the opacity will be 0 when #_reset is called for the second time, when the second timer expires it will set the opacity to 0 forever.

This will be fixed by making sure ProgressBar#done is called only once.

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -65,6 +65,8 @@
       reflectNewUrl url
       reflectRedirectedUrl()
       changePage extractTitleAndBody(doc)...
+      if showProgressBar
+        progressBar?.done()
       manuallyTriggerHashChangeForFirefox()
       onLoadFunction?()
       triggerEvent EVENTS.LOAD
@@ -88,6 +90,7 @@
 fetchHistory = (cachedPage) ->
   xhr?.abort()
   changePage cachedPage.title, cachedPage.body
+  progressBar?.done()
   recallScrollPosition cachedPage
   triggerEvent EVENTS.RESTORE

@@ -128,7 +131,6 @@
   setAutofocusElement()
   executeScriptTags() if runScripts
   currentState = window.history.state
-  progressBar?.done()
   triggerEvent EVENTS.CHANGE
   triggerEvent EVENTS.UPDATE


Get Atom Version from .atomrc

Get Atom Version from .atomrc

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -1,5 +1,6 @@
 temp = require 'temp'
 path = require 'path'
+fs = require 'fs'

 module.exports = (grunt) ->
   grunt.loadTasks('tasks')
@@ -21,17 +22,28 @@
   if process.platform is 'darwin'
     installDir += '.app'

-  # TODO: Get Atom Version from .atomrc
+  # Get Atom Version from .atomrc
+  atomrc = fs.readFileSync(path.join(__dirname, '..', '.atomrc')).toString()
+  lines = atomrc.split "\n"
+  atomVersion = null
+  for line in lines
+    [key, value] = line.split '='
+    if key.indexOf('ATOM_VERSION') > 0
+      atomVersion = value
+
   # TODO: Get Spark Dev version from options/latest tag

   grunt.initConfig
     workDir: workDir
-    atomVersion: 'v0.140.0'
+    atomVersion: atomVersion
     sparkDevVersion: '0.0.18'
     appName: appName
     installDir: installDir

   tasks = []
+
+  console.log grunt.config.get('atomVersion')
+  return

   if !grunt.option('workDir')
     tasks = tasks.concat [


Fetch more divisions.

Fetch more divisions.

Eg. Vuosaari area contains more than enough
school districts.

--- src/views/position-details.coffee
+++ src/views/position-details.coffee
@@ -124,6 +124,7 @@
                 reset: true
             if appSettings.school_district_active_date?
                 opts.data.date = moment(appSettings.school_district_active_date).format 'YYYY-MM-DD'
+            @divList.pageSize = 40
             @divList.fetch opts
         serializeData: ->
             data = super()


Fixed infinity scroll to take account only fetched results

--- static/js/activityfeedview.coffee
+++ static/js/activityfeedview.coffee
@@ -41,6 +41,7 @@
             data: @filters)

     add_item: (item) =>
+        @collection.filters.offset += 1
         view = new ActivityView model: item, has_actor: true
         view.render()
         @$el.append view.el
@@ -50,7 +51,6 @@
         coll.each @add_item

     load_more: =>
-        @collection.filters.offset += @collection.filters.limit
         return (@collection.fetch
             reset: false
             data: _.clone @filters)


Use correct socket address

Use correct socket address

--- client/app/main.coffee
+++ client/app/main.coffee
@@ -1,5 +1,5 @@
 $                 = require('jquery')
-socket            = require('socket.io-client').connect('http://localhost:7000')
+socket            = require('socket.io-client').connect('http://utuchat.exobald.es:5000')

 messagesContainer = require('./scripts/messages.coffee').messagesContainer
 usersContainer    = require('./scripts/users.coffee').usersContainer


Clean out extra track info divs to prevent duplicates from showing

--- assets/front.coffee
+++ assets/front.coffee
@@ -274,6 +274,16 @@
 							document.getElementById('length'+tag).innerHTML = minutes + ":" + seconds
 				# console.log(segment.tracks[0].metadata.artist)
 				# console.log(segment.tracks[0].metadata.id)
+			# Sorry, subsequent maintainer, more bad code here. -- Rosuav
+			tag = trackids.length
+			while 1
+				tag = tag + 1
+				artist = document.getElementById('artist'+tag)
+				if artist
+					artist.innerHTML = ""
+					document.getElementById('length'+tag).innerHTML = ""
+				else
+					break
 	setTimeout getTrackInfo, 1000
 	setInterval getTrackInfo, TIMING_INTERVAL


Client: Activity: Feed: Added getTwitterShareText method

--- client/activity/lib/components/activitysharepopup/index.coffee
+++ client/activity/lib/components/activitysharepopup/index.coffee
@@ -63,6 +63,21 @@
       shareUrlInput.setSelectionRange(0, shareUrlInput.value.length)

+  getTwitterShareText: ->
+
+    { tags, title, body } = @props.message
+    hashTags = ''
+
+    if tags
+      hashTags  = ("##{tag.slug}"  for tag in tags when tag?.slug)
+      hashTags  = _.unique(hashTags).join " "
+      hashTags += " "
+
+    itemText  = shortenText title or body, maxLength: 100, minLength: 100
+    shareText = "#{itemText} #{hashTags}- #{@props.url}"
+
+    return shareText
+
   render: ->

     return null  unless @props.isOpened


AdminInvitationsView: Show invite button again when tab changed.

--- client/admin/lib/views/invitations/admininvitationsview.coffee
+++ client/admin/lib/views/invitations/admininvitationsview.coffee
@@ -42,6 +42,10 @@
       tabView.showPaneByIndex tabView.lastOpenPaneIndex
       @inviteButton.show()

+    tabView.on 'PaneDidShow', =>
+      @inviteButton.show()
+
+
   createInviteButton: ->

     @addSubView @inviteButton = new KDButtonView


fix #69 : document.url is assigned in contextualize method since 1.3

--- lib/plugins/helpers/relations.plugin.coffee
+++ lib/plugins/helpers/relations.plugin.coffee
@@ -7,7 +7,7 @@
 	name: 'relations'

 	# Parsing all files has finished
-	parseFinished: ({docpad,logger,util},next) ->
+	contextualizeFinished: ({docpad,logger,util},next) ->
 		# Prepare
 		documents = docpad.documents
 		logger.log 'debug', 'Generating relations'
@@ -44,4 +44,4 @@
 					tasks.complete()

 # Export Relations Plugin
-module.exports = RelationsPlugin+module.exports = RelationsPlugin