Fixed typos in French translation.

Fixed typos in French translation.

--- coffeescripts/cards-fr.coffee
+++ coffeescripts/cards-fr.coffee
@@ -154,10 +154,10 @@
             text: """Après avoir exécuté une manœuvre verte, vous pouvez effectuer une action de concentration gratuite."""
         '"Backstabber"':
             ship: "Chasseur TIE"
-            text: """Quand vous attque en dehors de l'arc de tir du défenseur, lancez 1 dé d'attaque supplémentaire."""
+            text: """Quand vous attaquez en dehors de l'arc de tir du défenseur, lancez 1 dé d'attaque supplémentaire."""
         '"Dark Curse"':
             ship: "Chasseur TIE"
-            text: """Quand vous défendez, les vaisseaux qui vous attquent ne peuvent ni utiliser de marquers de concentration, ni relancer les dés d'attaque."""
+            text: """Quand vous défendez, les vaisseaux qui vous attaquent ne peuvent ni utiliser de marqueurs de concentration, ni relancer les dés d'attaque."""
         '"Mauler Mithel"':
             ship: "Chasseur TIE"
             text: """Quand vous attaquez à portée 1, lancez 1 dé d'attaque supplémentaire."""
@@ -186,10 +186,10 @@
             text: """Quand vous effectuez une action de tonneau, vous pouvez recevoir 1 marqueur de stress pour utiliser le gabarit (%BANKLEFT% 1) ou (%BANKRIGHT% 1) à la place du gabarit (%STRAIGHT% 1)."""
         "Kir Kanos":
             ship: "Intercepteur TIE"
-            text: """Quand vous attaquez à portée 2-3, vous pouvez dépenser 1 marqueur d'évasion pour ajouter 1 résultat %HIT% result à votre jet."""
+            text: """Quand vous attaquez à portée 2-3, vous pouvez dépenser 1 marqueur d'évasion pour ajouter 1 résultat %HIT% à votre jet."""
         "Tetran Cowall":
             ship: "Intercepteur TIE"
-            text: """Quand vous révélez une manœuvre %UTURN% maneuver, vous pouvez considérer la vitesse de celle-ci comme "1", "3" ou "5"."""
+            text: """Quand vous révélez une manœuvre %UTURN%, vous pouvez considérer la vitesse de celle-ci comme "1", "3" ou "5"."""
         "Turr Phennir":
             ship: "Intercepteur TIE"
             text: """Après avoir effectué une attaque, vous pouvez effectuer une action gratuite d'accélération ou de tonneau."""
@@ -221,10 +221,10 @@
             name: "Pilote l'escadron Delta"
         "Colonel Vessery":
             ship: "Défenseur TIE"
-            text: """Quand vous attaquez, juste après avoir lancé les dés d'attaque, vous pouvez vérouiller le défenseur s'il a déjà un marqueur d'acquisition de cible rouge."""
+            text: """Quand vous attaquez, juste après avoir lancé les dés d'attaque, vous pouvez verrouiller le défenseur s'il a déjà un marqueur d'acquisition de cible rouge."""
         "Rexler Brath":
             ship: "Défenseur TIE"
-            text: """Après avoir effectué une attque qui inflige au moins 1 carte de dégâts au défenseur, vous pouvez dépenser 1 marqueur de concentration pour retourner ces cartes face visible."""
+            text: """Après avoir effectué une attaque qui inflige au moins 1 carte de dégâts au défenseur, vous pouvez dépenser 1 marqueur de concentration pour retourner ces cartes face visible."""
         # Bombardier TIE
         "Scimitar Squadron Pilot":
             ship: "Bombardier TIE"
@@ -461,7 +461,7 @@
             text: """Augmentez votre valeur de pilotage de 2."""
         "Opportunist":
             name: "Opportuniste"
-            text: """Quand vous attaquez, si le défenseur n'a pas de marqueur de concentration ou d'évasion, vous pouvez recevoir 1 marqueur de stresss pour lancer 1 dé d'attaque supplémentaire.

Vous ne pouvez pas utiliser cette capacité si vous avez au moins un marqueur de stress."""
+            text: """Quand vous attaquez, si le défenseur n'a pas de marqueur de concentration ou d'évasion, vous pouvez recevoir 1 marqueur de stress pour lancer 1 dé d'attaque supplémentaire.

Vous ne pouvez pas utiliser cette capacité si vous avez au moins un marqueur de stress."""
         # Astromechs
         "R2 Astromech":
             name: "Astromech R2"
@@ -479,7 +479,7 @@
             text: """Pendant la phase de dénouement, choisissez une de vos cartes dégâts face visible assortie du trait Vaisseau, et retournez-la face cachée."""
         "R7 Astromech":
             name: "Astromech R7"
-            text: """Une fois par tour, quand vous défendez, si vous avez vérouillé l'attaquant, vous pouvez dépenser l'acquisition de cible pour choisir tout ou partie des dés d'attaque. L'attaquant doit relancer les dés choisis."""
+            text: """Une fois par tour, quand vous défendez, si vous avez verrouillé l'attaquant, vous pouvez dépenser l'acquisition de cible pour choisir tout ou partie des dés d'attaque. L'attaquant doit relancer les dés choisis."""
         "R7-T1":
             text: """Action : choisissez un vaisseau ennemi situé à portée 1-2. Si vous êtes dans son arc de tir, vous pouvez le verrouiller. Ensuite, vous pouvez effectuer une action d'accélération gratuite."""
         "R4-D6":
@@ -493,7 +493,7 @@
         # Torpilles
         "Proton Torpedoes":
             name: "Torpilles à protons"
-            text: """Attaque (acquisition de cible) : utilisez votre acquisition de cible et défaussez cette carte pour effectuer cette attaque.

Vous pouvez échanger 1 de vos résultats %FOCUS% contre 1 resultat %CRIT%."""
+            text: """Attaque (acquisition de cible) : utilisez votre acquisition de cible et défaussez cette carte pour effectuer cette attaque.

Vous pouvez échanger 1 de vos résultats %FOCUS% contre 1 résultat %CRIT%."""
         "Advanced Proton Torpedoes":
             name: "Torpilles à protons avancées"
             text: """Attaque (acquisition de cible) : utilisez votre acquisition de cible et défaussez cette carte pour effectuer cette attaque.

Vous pouvez échanger jusqu'à 3 de vos résultats vierge contre autant de résultats %FOCUS%."""
@@ -596,7 +596,7 @@
             text: """Quand vous défendez, vous pouvez relancer 1 de vos résultats %FOCUS%. Si la valeur de pilotage de l'attaquant est de 2 ou moins, vous pouvez relancer 1 de vos résultats vierges à la place."""
         "Navigator":
             name: "Navigateur"
-            text: """Quand vous révélez une manoeuvre, vous pouvez tourner le cadran sur une autre manoeuvre ayant la même direction.

Vous ne pouvez pas touner le cadran sur une manoeuvre rouge si vous avez au moins un marqueurs de stress."""
+            text: """Quand vous révélez une manoeuvre, vous pouvez tourner le cadran sur une autre manoeuvre ayant la même direction.

Vous ne pouvez pas tourner le cadran sur une manoeuvre rouge si vous avez au moins un marqueurs de stress."""
         "Lando Calrissian": # Traduction non officielle
             text: """Action : lancez 2 dés de défense. Pour chaque résultat %FOCUS%, assignez 1 marqueur de concentration à votre vaisseau. Pour chaque résultat %EVADE%, assignez 1 marqueur d'évasion à votre vaisseau."""
         "Mara Jade": # Traduction non officielle
@@ -610,7 +610,7 @@
             text: """Au début de la phase d'activation, vous pouvez défausser cette carte pour permettre à tous les vaisseaux alliés qui dévoilent une manœuvre rouge de considérer cette dernière comme une manœuvre blanche jusqu'à la fin de la phase."""
         "WED-15 Repair Droid":
             name: "Droïde de réparation WED-15"
-            text: """Action : dépensez 1 énergie pour défausser 1 de vos cartes de déga^ts face cachée, ou dépensez 3 énergie pour défausser 1 de vos cartes de dégâts face visible."""
+            text: """Action : dépensez 1 énergie pour défausser 1 de vos cartes de dégâts face cachée, ou dépensez 3 énergie pour défausser 1 de vos cartes de dégâts face visible."""
         "Carlist Rieekan":
             text: """Au début de la phase d'activation, vous pouvez défausser cette carte pour traiter la valeur de pilotage de chaque vaisseau allié comme si elle s'élevait à "12", jusqu'à la fin de la phase."""
         "Jan Dodonna":
@@ -758,6 +758,6 @@
             ship: "Corvette CR90 (proue)"
             text: """Quand vous défendez, une fois par attaque, si vous recevez une carte de dégâts face visible, vous pouvez la défausser et piocher une autre carte de dégâts face visible."""
         "Outrider": # Traduction non officielle
-            text: """Tant que vous êtes équipé d'une carte d'amélioration %CANNON%, vous ne pouvez pas effectuer d'attque d'arme principale et vous pouvez effectuer une attaque d'arme secondaire %CANNON% contre des vaisseaux en dehors de votre arc de tir."""
+            text: """Tant que vous êtes équipé d'une carte d'amélioration %CANNON%, vous ne pouvez pas effectuer d'attaque d'arme principale et vous pouvez effectuer une attaque d'arme secondaire %CANNON% contre des vaisseaux en dehors de votre arc de tir."""

     exportObj.setupCardData basic_cards, pilot_translations, upgrade_translations, modification_translations, title_translations


new(map): closes #84 add zoom controls

new(map): closes #84 add zoom controls

--- src/components/Map.coffee
+++ src/components/Map.coffee
@@ -14,7 +14,7 @@
 OSM_ATTR = '© OpenStreetMap'

-{ Map, TileLayer, Marker } = leaflet
+{ Map, TileLayer, Marker} = leaflet

 {
   div,
@@ -53,6 +53,7 @@
     # workaround due to a bug in react-leaflet:
     if (map = @refs.map)?
       map.fireLeafletEvent 'load', map
+      map.getLeafletElement().addControl(L.control.zoom position: 'bottomright')

   render: ->


Run program using signals

Run program using signals

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -67,7 +67,9 @@
   step: (world, args) ->
     program = world.get('program')
     counter = world.get('program_counter')
-    action = program[counter]
+    signal = program[counter]
+    action = world.get('signals')[signal]
+
     counter = counter + 1
     counter= 0 if counter >= program.length
     world.put('program_counter', counter)


Remove duplication in rendering between nodes and ancestors

--- src/d3-flame-graph.coffee
+++ src/d3-flame-graph.coffee
@@ -119,34 +119,43 @@
         .enter()
           .append('g').attr('class', (d, idx) -> if idx == 0 then 'root node' else 'node')

+      @_renderNodes nodes,
+        x: (d) => @x(d.x)
+        y: (d) => @y(d.y)
+        width: (d) => @x(d.dx)
+        height: (d) => @cellHeight()
+        text: (d) => @label(d) if d.name and @x(d.dx) > 40
+
+      console.timeEnd('render')
+
+      console.log("Rendered #{@container.selectAll('.node')[0]?.length} elements")
+      @_renderBreadcrumbs()._enableNavigation() if @breadcrumbs()
+      @_renderTooltip()                         if @tooltip()
+      @
+
+    _renderNodes: (nodes, attrs) ->
       nodes.append('rect')
-        .attr('width', (d) => @x(d.dx))
-        .attr('height', (d) => @cellHeight())
-        .attr('x', (d) => @x(d.x))
-        .attr('y', (d) => @y(d.y))
+        .attr('width', attrs.width)
+        .attr('height', @cellHeight())
+        .attr('x', attrs.x)
+        .attr('y', attrs.y)
         .attr('fill', (d) => @color()(d))

       nodes.append('text')
         .attr('class', 'label')
         .attr('dy', "#{@fontSize / 2}em")
-        .attr('x', (d) => @x(d.x) + 2)
-        .attr('y', (d) => @y(d.y) + @cellHeight() / 2)
+        .attr('x', (d) => attrs.x(d) + 2)
+        .attr('y', (d, idx) => attrs.y(d, idx) + @cellHeight() / 2)
         .style('font-size', "#{@fontSize}em")
-        .text((d) => @label(d) if d.name and @x(d.dx) > 40)
+        .text(attrs.text)
       # overlaying a transparent rectangle to capture events
       # TODO: maybe there's a smarter way to do this?
       nodes.append('rect')
         .attr('class', 'overlay')
-        .attr('width', (d) => @x(d.dx))
-        .attr('height', (d) => @cellHeight())
-        .attr('x', (d) => @x(d.x))
-        .attr('y', (d) => @y(d.y))
-
-      console.timeEnd('render')
-
-      console.log("Rendered #{@container.selectAll('.node')[0]?.length} elements")
-      @_renderBreadcrumbs()._enableNavigation() if @breadcrumbs()
-      @_renderTooltip()                         if @tooltip()
+        .attr('width', attrs.width)
+        .attr('height', @cellHeight())
+        .attr('x', attrs.x)
+        .attr('y', attrs.y)
       @

     _renderTooltip: () ->
@@ -184,22 +193,12 @@
         .append('g')
           .attr('class', 'ancestor')

-      group.append('rect')
-        .attr('width', @width())
-        .attr('height', @cellHeight())
-        .attr('x', 0)
-        .attr('y', (d, idx) => @height() - ((idx + 1) * @cellHeight()))
-        .attr('fill', (d) => @color()(d))
-
-      group.append('text')
-        .attr('class', 'label')
-        .attr('dy', '#{@fontSize / 2}em')
-        .attr('x', (d) => 2)
-        .attr('y', (d, idx) => @height() - ((idx + 1) * @cellHeight()) + @cellHeight() / 2)
-        .style('font-size', "#{@fontSize}em")
-        .text((d) => "↩ #{getClassAndMethodName(d.name)}")
-
-      breadcrumbs.exit().remove()
+      @_renderNodes group,
+        x: (d) => 0
+        y: (d, idx) => debugger; @height() - ((idx + 1) * @cellHeight())
+        width: @width()
+        height: @cellHeight()
+        text: (d) => "↩ #{getClassAndMethodName(d.name)}"
       @

     _enableNavigation: () ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -6,7 +6,7 @@
     return memo if ran
     ran = true

-    return memo = this.apply()
+    return memo = this.apply(this, arguments)

   _.once = function(func) {
     var ran = false, memo;


Commented available on chemistry

Commented available on chemistry

--- source/core/attributes.coffee
+++ source/core/attributes.coffee
@@ -33,7 +33,7 @@
   chemistry: (children) ->
     children = @attributes.children or @default.children or []
     for item, index in children
-      for key of item when not @constructor.available or key in @constructor.available
+      for key of item # when not @constructor.available or key in @constructor.available
         base = key.split(".")
         type = base[0]
         class_name = base[1]


:lipstick: Prepare For Splitting Up autocomplete-spec

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -1,7 +1,7 @@
-{triggerAutocompletion, buildIMECompositionEvent, buildTextInputEvent} = require "./spec-helper"
-_ = require "underscore-plus"
-{KeymapManager} = require 'atom'
-TestProvider = require "./lib/test-provider"
+{triggerAutocompletion, buildIMECompositionEvent, buildTextInputEvent} = require('./spec-helper')
+_ = require('underscore-plus')
+{KeymapManager} = require('atom')
+TestProvider = require('./lib/test-provider')

 describe "Autocomplete", ->
   [completionDelay, editorView, editor, autocompleteManager, mainModule] = []
@@ -9,12 +9,12 @@
   beforeEach ->
     runs ->
       # Set to live completion
-      atom.config.set "autocomplete-plus.enableAutoActivation", true
-      atom.config.set "editor.fontSize", "16"
+      atom.config.set('autocomplete-plus.enableAutoActivation', true)
+      atom.config.set('editor.fontSize', '16')

       # Set the completion delay
       completionDelay = 100
-      atom.config.set "autocomplete-plus.autoActivationDelay", completionDelay
+      atom.config.set('autocomplete-plus.autoActivationDelay', completionDelay)
       completionDelay += 100 # Rendering

       workspaceElement = atom.views.getView(atom.workspace)
@@ -22,13 +22,13 @@

   describe "when auto-activation is enabled", ->
     beforeEach ->
-      runs -> atom.config.set "autocomplete-plus.enableAutoActivation", true
-
-      waitsForPromise -> atom.workspace.open("sample.js").then (e) ->
+      runs -> atom.config.set('autocomplete-plus.enableAutoActivation', true)
+
+      waitsForPromise -> atom.workspace.open('sample.js').then (e) ->
         editor = e

       # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) ->
+      waitsForPromise -> atom.packages.activatePackage('autocomplete-plus').then (a) ->
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

@@ -38,44 +38,44 @@

     describe "on changed events", ->
       it "should show the suggestion list when suggestions are found", ->
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-
-        # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+
+        # Trigger an autocompletion
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)
+        expect(editorView.querySelector('.autocomplete-plus')).toExist()
+
+        # Check suggestions
+        suggestions = ['function', 'if', 'left', 'shift']
+        [].forEach.call editorView.querySelectorAll('.autocomplete-plus li span'), (item, index) ->
+          expect(item.innerText).toEqual(suggestions[index])
+
+      it "should not show the suggestion list when no suggestions are found", ->
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+
+        # Trigger an autocompletion
+        editor.moveToBottom()
+        editor.insertText('x')
+        advanceClock(completionDelay)
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+
+      it "should show the suggestion list when backspacing", ->
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+
+        # Trigger an autocompletion
+        editor.moveToBottom()
+        editor.insertText('fu')
+        advanceClock(completionDelay)
+        key = atom.keymaps.constructor.keydownEvent('backspace', target: editorView)
+        atom.keymaps.handleKeyboardEvent(key)
+        advanceClock(completionDelay)
+
         expect(editorView.querySelector(".autocomplete-plus")).toExist()
-
-        # Check suggestions
-        suggestions = ["function", "if", "left", "shift"]
-        [].forEach.call editorView.querySelectorAll(".autocomplete-plus li span"), (item, index) ->
-          expect(item.innerText).toEqual suggestions[index]
-
-      it "should not show the suggestion list when no suggestions are found", ->
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-
-        # Trigger an autocompletion
-        editor.moveToBottom()
-        editor.insertText("x")
-        advanceClock completionDelay
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-
-      it "should show the suggestion list when backspacing", ->
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-
-        # Trigger an autocompletion
-        editor.moveToBottom()
-        editor.insertText("fu")
-        advanceClock completionDelay
-        key = atom.keymaps.constructor.keydownEvent('backspace', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key)
-        advanceClock completionDelay
-
-        expect(editorView.querySelector(".autocomplete-plus")).toExist()
-        expect(editor.lineTextForBufferRow(13)).toBe 'f'
+        expect(editor.lineTextForBufferRow(13)).toBe('f')

       it "should not update the suggestion list while composition is in progress", ->
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)

         autocompleteView = atom.views.getView(autocompleteManager)

@@ -83,42 +83,42 @@

         editorView.dispatchEvent(buildIMECompositionEvent('compositionstart', target: editorView))
         editorView.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "~", target: editorView))
-        advanceClock completionDelay
+        advanceClock(completionDelay)

         expect(autocompleteManager.changeItems).not.toHaveBeenCalled()

         editorView.dispatchEvent(buildIMECompositionEvent('compositionend', target: editorView))
         editorView.firstChild.dispatchEvent(buildTextInputEvent(data: 'ã', target: editorView))

-        expect(editor.lineTextForBufferRow(13)).toBe 'fã'
+        expect(editor.lineTextForBufferRow(13)).toBe('fã')

     describe "accepting suggestions", ->
       it "hides the suggestions list when a suggestion is confirmed", ->
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

         # Trigger an autocompletion
         editor.moveToBottom()
         editor.moveToBeginningOfLine()
-        editor.insertText("f")
-        advanceClock completionDelay
-        expect(editorView.querySelector(".autocomplete-plus")).toExist()
-
-        # Accept suggestion
-        autocompleteView = atom.views.getView(autocompleteManager)
-        atom.commands.dispatch(autocompleteView, "autocomplete-plus:confirm")
-
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+        editor.insertText('f')
+        advanceClock(completionDelay)
+        expect(editorView.querySelector('.autocomplete-plus')).toExist()
+
+        # Accept suggestion
+        autocompleteView = atom.views.getView(autocompleteManager)
+        atom.commands.dispatch(autocompleteView, 'autocomplete-plus:confirm')
+
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

       describe "when tab is used to accept suggestions", ->
         beforeEach ->
           atom.config.set('autocomplete-plus.confirmCompletion', 'tab')

         it "inserts the word and moves the cursor to the end of the word", ->
-          expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+          expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

           # Trigger an autocompletion
-          triggerAutocompletion editor
-          advanceClock completionDelay
+          triggerAutocompletion(editor)
+          advanceClock(completionDelay)

           autocompleteView = atom.views.getView(autocompleteManager)
           # Accept suggestion
@@ -126,19 +126,19 @@
           atom.keymaps.handleKeyboardEvent(key)

           # Check for result
-          expect(editor.getBuffer().getLastLine()).toEqual "function"
+          expect(editor.getBuffer().getLastLine()).toEqual('function')

           # Check for cursor position
           bufferPosition = editor.getCursorBufferPosition()
-          expect(bufferPosition.row).toEqual 13
-          expect(bufferPosition.column).toEqual 8
+          expect(bufferPosition.row).toEqual(13)
+          expect(bufferPosition.column).toEqual(8)

         it "does not insert the word when enter completion not enabled", ->
-          expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+          expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

           # Trigger an autocompletion
-          triggerAutocompletion editor
-          advanceClock completionDelay
+          triggerAutocompletion(editor)
+          advanceClock(completionDelay)

           autocompleteView = atom.views.getView(autocompleteManager)
           # Accept suggestion
@@ -146,234 +146,234 @@
           atom.keymaps.handleKeyboardEvent(key)

           # Check for result
-          expect(editor.getBuffer().getLastLine()).toEqual ""
+          expect(editor.getBuffer().getLastLine()).toEqual('')

       describe "when enter is used to accept suggestions", ->
         beforeEach ->
           atom.config.set('autocomplete-plus.confirmCompletion', 'enter')

         it "inserts the word and moves the cursor to the end of the word", ->
-          expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+          expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

           # Trigger an autocompletion
-          triggerAutocompletion editor
-          advanceClock completionDelay
+          triggerAutocompletion(editor)
+          advanceClock(completionDelay)

           autocompleteView = atom.views.getView(autocompleteManager)
           # Accept suggestion
           key = atom.keymaps.constructor.keydownEvent('enter', keyCode: 13, target: editorView)
-          atom.keymaps.handleKeyboardEvent(key);
+          atom.keymaps.handleKeyboardEvent(key)

           # Check for result
-          expect(editor.getBuffer().getLastLine()).toEqual "function"
+          expect(editor.getBuffer().getLastLine()).toEqual('function')

           # Check for cursor position
           bufferPosition = editor.getCursorBufferPosition()
-          expect(bufferPosition.row).toEqual 13
-          expect(bufferPosition.column).toEqual 8
+          expect(bufferPosition.row).toEqual(13)
+          expect(bufferPosition.column).toEqual(8)

         it "does not insert the word when tab completion not enabled", ->
-          expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
+          expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

           # Trigger an autocompletion
-          triggerAutocompletion editor
-          advanceClock completionDelay
+          triggerAutocompletion(editor)
+          advanceClock(completionDelay)

           autocompleteView = atom.views.getView(autocompleteManager)
           # Accept suggestion
           key = atom.keymaps.constructor.keydownEvent('tab', keyCode: 9, target: editorView)
-          atom.keymaps.handleKeyboardEvent(key);
+          atom.keymaps.handleKeyboardEvent(key)

           # Check for result
-          expect(editor.getBuffer().getLastLine()).toEqual "f "
+          expect(editor.getBuffer().getLastLine()).toEqual('f ')

     describe "select-previous event", ->
       it "selects the previous item in the list", ->

         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
-
-        items = editorView.querySelectorAll(".autocomplete-plus li")
-        expect(items[0]).toHaveClass("selected")
-        expect(items[1]).not.toHaveClass("selected")
-        expect(items[2]).not.toHaveClass("selected")
-        expect(items[3]).not.toHaveClass("selected")
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)
+
+        items = editorView.querySelectorAll('.autocomplete-plus li')
+        expect(items[0]).toHaveClass('selected')
+        expect(items[1]).not.toHaveClass('selected')
+        expect(items[2]).not.toHaveClass('selected')
+        expect(items[3]).not.toHaveClass('selected')

         autocompleteView = atom.views.getView(autocompleteManager)
         # Select previous item
-        atom.commands.dispatch autocompleteView, "autocomplete-plus:select-previous"
-
-        items = editorView.querySelectorAll(".autocomplete-plus li")
-        expect(items[0]).not.toHaveClass("selected")
-        expect(items[1]).not.toHaveClass("selected")
-        expect(items[2]).not.toHaveClass("selected")
-        expect(items[3]).toHaveClass("selected")
+        atom.commands.dispatch(autocompleteView, 'autocomplete-plus:select-previous')
+
+        items = editorView.querySelectorAll('.autocomplete-plus li')
+        expect(items[0]).not.toHaveClass('selected')
+        expect(items[1]).not.toHaveClass('selected')
+        expect(items[2]).not.toHaveClass('selected')
+        expect(items[3]).toHaveClass('selected')

       it "closes the autocomplete when up arrow pressed when only one item displayed", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor, false, 'q'
-        advanceClock completionDelay
+        triggerAutocompletion(editor, false, 'q')
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('down', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key);
-
-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        atom.keymaps.handleKeyboardEvent(key)
+
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).not.toExist()

       it "does not close the autocomplete when down arrow pressed when many items", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('down', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key);
-
-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        atom.keymaps.handleKeyboardEvent(key)
+
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).toExist()

       it "does close the autocomplete when down arrow while up,down navigation not selected", ->
         atom.config.set('autocomplete-plus.navigateCompletions', 'ctrl-p,ctrl-n')
         # Trigger an autocompletion
-        triggerAutocompletion editor, false
-        advanceClock completionDelay
+        triggerAutocompletion(editor, false)
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('down', target: editorView)
         atom.keymaps.handleKeyboardEvent(key)

-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).not.toExist()

     describe "select-next event", ->
       it "selects the next item in the list", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
-
-        items = editorView.querySelectorAll(".autocomplete-plus li")
-        expect(items[0]).toHaveClass("selected")
-        expect(items[1]).not.toHaveClass("selected")
-        expect(items[2]).not.toHaveClass("selected")
-        expect(items[3]).not.toHaveClass("selected")
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)
+
+        items = editorView.querySelectorAll('.autocomplete-plus li')
+        expect(items[0]).toHaveClass('selected')
+        expect(items[1]).not.toHaveClass('selected')
+        expect(items[2]).not.toHaveClass('selected')
+        expect(items[3]).not.toHaveClass('selected')

         # Select next item
         autocompleteView = atom.views.getView(autocompleteManager)
-        atom.commands.dispatch autocompleteView, "autocomplete-plus:select-next"
-
-        items = editorView.querySelectorAll(".autocomplete-plus li")
-        expect(items[0]).not.toHaveClass("selected")
-        expect(items[1]).toHaveClass("selected")
-        expect(items[2]).not.toHaveClass("selected")
-        expect(items[3]).not.toHaveClass("selected")
+        atom.commands.dispatch(autocompleteView, 'autocomplete-plus:select-next')
+
+        items = editorView.querySelectorAll('.autocomplete-plus li')
+        expect(items[0]).not.toHaveClass('selected')
+        expect(items[1]).toHaveClass('selected')
+        expect(items[2]).not.toHaveClass('selected')
+        expect(items[3]).not.toHaveClass('selected')

       it "closes the autocomplete when up arrow pressed when only one item displayed", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor, false, 'q'
-        advanceClock completionDelay
+        triggerAutocompletion(editor, false, 'q')
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('up', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key);
-
-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        atom.keymaps.handleKeyboardEvent(key)
+
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).not.toExist()

       it "does not close the autocomplete when up arrow pressed when many items", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('up', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key);
-
-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        atom.keymaps.handleKeyboardEvent(key)
+
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).toExist()

       it "does close the autocomplete when up arrow while up,down navigation not selected", ->
         atom.config.set('autocomplete-plus.navigateCompletions', 'ctrl-p,ctrl-n')
         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)

         # Accept suggestion
         key = atom.keymaps.constructor.keydownEvent('up', target: editorView)
-        atom.keymaps.handleKeyboardEvent(key);
-
-        autocomplete = editorView.querySelector(".autocomplete-plus")
+        atom.keymaps.handleKeyboardEvent(key)
+
+        autocomplete = editorView.querySelector('.autocomplete-plus')
         expect(autocomplete).not.toExist()

     describe "when a suggestion is clicked", ->
       it "should select the item and confirm the selection", ->
         # Trigger an autocompletion
-        triggerAutocompletion editor
-        advanceClock completionDelay
+        triggerAutocompletion(editor)
+        advanceClock(completionDelay)

         # Get the second item
-        item = editorView.querySelectorAll(".autocomplete-plus li")[1]
+        item = editorView.querySelectorAll('.autocomplete-plus li')[1]

         # Click the item, expect list to be hidden and
         # text to be added
-        mouse = document.createEvent 'MouseEvents'
-        mouse.initMouseEvent 'mousedown', true, true, window
+        mouse = document.createEvent('MouseEvents')
+        mouse.initMouseEvent('mousedown', true, true, window)
         item.dispatchEvent mouse
-        mouse = document.createEvent 'MouseEvents'
-        mouse.initMouseEvent 'mouseup', true, true, window
-        item.dispatchEvent mouse
-
-        expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-        expect(editor.getBuffer().getLastLine()).toEqual item.innerText
+        mouse = document.createEvent('MouseEvents')
+        mouse.initMouseEvent('mouseup', true, true, window)
+        item.dispatchEvent(mouse)
+
+        expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+        expect(editor.getBuffer().getLastLine()).toEqual(item.innerText)

     describe ".cancel()", ->
       it "unbinds autocomplete event handlers for move-up and move-down", ->
-        triggerAutocompletion editor, false
+        triggerAutocompletion(editor, false)
         autocompleteManager.cancel()
-
-        atom.commands.dispatch atom.views.getView(editor), "core:move-down"
-        expect(editor.getCursorBufferPosition().row).toBe 1
-
-        atom.commands.dispatch atom.views.getView(editor) , "core:move-up"
-        expect(editor.getCursorBufferPosition().row).toBe 0
+        editorView = atom.views.getView(editor)
+        atom.commands.dispatch(editorView, 'core:move-down')
+        expect(editor.getCursorBufferPosition().row).toBe(1)
+
+        atom.commands.dispatch(editorView, 'core:move-up')
+        expect(editor.getCursorBufferPosition().row).toBe(0)

   describe "when a long completion exists", ->
     beforeEach ->
       runs ->
-        atom.config.set "autocomplete-plus.enableAutoActivation", true
-
-      waitsForPromise -> atom.workspace.open("samplelong.js").then (e) ->
+        atom.config.set('autocomplete-plus.enableAutoActivation', true)
+
+      waitsForPromise -> atom.workspace.open('samplelong.js').then (e) ->
         editor = e

       # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) ->
+      waitsForPromise -> atom.packages.activatePackage('autocomplete-plus').then (a) ->
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

     it "sets the width of the view to be wide enough to contain the longest completion without scrolling", ->
       editor.moveToBottom()
       editor.insertNewline()
-      editor.insertText "t"
-      advanceClock completionDelay
+      editor.insertText('t')
+      advanceClock(completionDelay)

       autocompleteView = atom.views.getView(autocompleteManager)
-      expect(autocompleteView.scrollWidth).toBe autocompleteView.offsetWidth
+      expect(autocompleteView.scrollWidth).toBe(autocompleteView.offsetWidth)

   describe "css", ->
     [css] = []

     beforeEach ->
-      runs -> atom.config.set "autocomplete-plus.enableAutoActivation", true
-
-      waitsForPromise -> atom.workspace.open("css.css").then (e) ->
+      runs -> atom.config.set('autocomplete-plus.enableAutoActivation', true)
+
+      waitsForPromise -> atom.workspace.open('css.css').then (e) ->
         editor = e

       # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("language-css").then (c) -> css = c
-
-      # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) ->
+      waitsForPromise -> atom.packages.activatePackage('language-css').then (c) -> css = c
+
+      # Activate the package
+      waitsForPromise -> atom.packages.activatePackage('autocomplete-plus').then (a) ->
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

@@ -383,57 +383,57 @@
     it "includes completions for the scope's completion preferences", ->
       runs ->
         editor.moveToEndOfLine()
-        editor.insertText "o"
-        editor.insertText "u"
-        editor.insertText "t"
-
-        advanceClock completionDelay
-
-        autocompleteView = atom.views.getView(autocompleteManager)
-        items = autocompleteView.querySelectorAll("li")
-        expect(editorView.querySelector(".autocomplete-plus")).toExist()
-        expect(items.length).toBe 10
-        expect(items[0]).toHaveText "outline"
-        expect(items[1]).toHaveText "outline-color"
-        expect(items[2]).toHaveText "outline-width"
-        expect(items[3]).toHaveText "outline-style"
+        editor.insertText('o')
+        editor.insertText('u')
+        editor.insertText('t')
+
+        advanceClock(completionDelay)
+
+        autocompleteView = atom.views.getView(autocompleteManager)
+        items = autocompleteView.querySelectorAll('li')
+        expect(editorView.querySelector('.autocomplete-plus')).toExist()
+        expect(items.length).toBe(10)
+        expect(items[0]).toHaveText('outline')
+        expect(items[1]).toHaveText('outline-color')
+        expect(items[2]).toHaveText('outline-width')
+        expect(items[3]).toHaveText('outline-style')

   describe "when auto-activation is disabled", ->
     beforeEach ->
       runs ->
-        atom.config.set "autocomplete-plus.enableAutoActivation", false
-
-      waitsForPromise -> atom.workspace.open("sample.js").then (e) ->
+        atom.config.set('autocomplete-plus.enableAutoActivation', false)
+
+      waitsForPromise -> atom.workspace.open('sample.js').then (e) ->
         editor = e

       # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) ->
+      waitsForPromise -> atom.packages.activatePackage('autocomplete-plus').then (a) ->
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

       runs ->

     it "does not show suggestions after a delay", ->
-      triggerAutocompletion editor
-      advanceClock completionDelay
+      triggerAutocompletion(editor)
+      advanceClock(completionDelay)
       expect(editorView.querySelector(".autocomplete-plus")).not.toExist()

     it "shows suggestions when explicitly triggered", =>
-      triggerAutocompletion editor
-      advanceClock completionDelay
-
-      editorView = atom.views.getView(editor);
-      expect(editorView.querySelector(".autocomplete-plus")).not.toExist()
-      atom.commands.dispatch editorView, "autocomplete-plus:activate"
-      expect(editorView.querySelector(".autocomplete-plus")).toExist()
+      triggerAutocompletion(editor)
+      advanceClock(completionDelay)
+
+      editorView = atom.views.getView(editor)
+      expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
+      atom.commands.dispatch(editorView, 'autocomplete-plus:activate')
+      expect(editorView.querySelector('.autocomplete-plus')).toExist()

   describe "HTML label support", ->
     beforeEach ->
-      waitsForPromise -> atom.workspace.open("sample.js").then (e) ->
+      waitsForPromise -> atom.workspace.open('sample.js').then (e) ->
         editor = e

       # Activate the package
-      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) ->
+      waitsForPromise -> atom.packages.activatePackage('autocomplete-plus').then (a) ->
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

@@ -444,14 +444,14 @@
       runs ->
         # Register the test provider
         testProvider = new TestProvider(editor)
-        mainModule.registerProviderForEditor testProvider, editor
+        mainModule.registerProviderForEditor(testProvider, editor)

         editor.moveToBottom()
-        editor.insertText "o"
-
-        advanceClock completionDelay
-
-        autocompleteView = atom.views.getView(autocompleteManager)
-
-        expect(autocompleteView.querySelector("li .label")).toHaveHtml "<span style=\"color: red\">ohai</span>"
-        expect(autocompleteView.querySelector("li")).toHaveClass "ohai"
+        editor.insertText('o')
+
+        advanceClock(completionDelay)
+
+        autocompleteView = atom.views.getView(autocompleteManager)
+
+        expect(autocompleteView.querySelector('li .label')).toHaveHtml('<span style="color: red">ohai</span>')
+        expect(autocompleteView.querySelector('li')).toHaveClass('ohai')


Fixed specs

Fixed specs

--- spec/core/EventBusSpec.coffee
+++ spec/core/EventBusSpec.coffee
@@ -1,4 +1,4 @@
-C = require "../../../calamity.js"
+C = require "../../calamity.js"

 describe "EventBus", ->
 	bus = null


Add guard for pending state specs for backwards compatibility

--- spec/results-view-spec.coffee
+++ spec/results-view-spec.coffee
@@ -421,28 +421,29 @@
       runs ->
         expect(atom.workspace.getActivePaneItem().getPath()).toContain('sample.')

-    it "opens the file containing the result in pending state when the search result is single-clicked", ->
-      pathNode = resultsView.find(".search-result")[0]
-      pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, which: 1))
-      editor = null
-      waitsFor ->
-        editor = atom.workspace.getActiveTextEditor()
-
-      runs ->
-        expect(editor.isPending()).toBe true
-        expect(atom.views.getView(editor)).toHaveFocus()
-
-    it "opens the file containing the result in non-pending state when the search result is double-clicked", ->
-      pathNode = resultsView.find(".search-result")[0]
-      pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 1))
-      pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 2))
-      editor = null
-      waitsFor ->
-        editor = atom.workspace.getActiveTextEditor()
-
-      runs ->
-        expect(editor.isPending()).toBe false
-        expect(atom.views.getView(editor)).toHaveFocus()
+    if atom.workspace.buildTextEditor().isPending?
+      it "opens the file containing the result in pending state when the search result is single-clicked", ->
+        pathNode = resultsView.find(".search-result")[0]
+        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, which: 1))
+        editor = null
+        waitsFor ->
+          editor = atom.workspace.getActiveTextEditor()
+
+        runs ->
+          expect(editor.isPending()).toBe true
+          expect(atom.views.getView(editor)).toHaveFocus()
+
+      it "opens the file containing the result in non-pending state when the search result is double-clicked", ->
+        pathNode = resultsView.find(".search-result")[0]
+        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 1))
+        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 2))
+        editor = null
+        waitsFor ->
+          editor = atom.workspace.getActiveTextEditor()
+
+        runs ->
+          expect(editor.isPending()).toBe false
+          expect(atom.views.getView(editor)).toHaveFocus()

     describe "when `openProjectFindResultsInRightPane` option is true", ->
       beforeEach ->


Changed menu label

Changed menu label

--- menus/fold-functions.cson
+++ menus/fold-functions.cson
@@ -19,7 +19,7 @@
   {
     'label': 'Packages'
     'submenu': [
-      'label': 'atom-fold-functions'
+      'label': 'Fold Functions'
       'submenu': [
         {
           'label': 'Fold'


ensure tabs work for discussion mastery paths

closes OUT-3167

Test plan:
- visit discussion page
- ensure no tabs are visible
- enable mastery paths
- visit discussion page
- ensure tabs are visible
- mastery paths tab should be disabled
  by default
- mastery paths tab should be enabled when
  discussion is set to graded
- mastery paths tab should function
  properly (wrt adding follow-on assignments)

Change-Id: Ib50e2b535faad02cf53384a405fc4711e4cfb9e4
Reviewed-on: https://gerrit.instructure.com/198774
Tested-by: Jenkins
Reviewed-by: Frank Murphy III <ed7a7068a7b39653c9ca1c429cffcb87c4a069c6@instructure.com>
QA-Review: Brian Watson <4411c2aac809443ec63e2dd517de0f2dbc7408c6@instructure.com>
Product-Review: Jonathan Fenton <5549b92428699b688d6c4f80786628c27e48a3e1@instructure.com>

--- app/coffeescripts/views/DiscussionTopics/EditView.coffee
+++ app/coffeescripts/views/DiscussionTopics/EditView.coffee
@@ -40,6 +40,7 @@
 import SisValidationHelper from '../../util/SisValidationHelper'
 import AssignmentExternalTools from 'jsx/assignments/AssignmentExternalTools'
 import * as returnToHelper from '../../../jsx/shared/helpers/returnToHelper'
+import 'jqueryui/tabs'

 RichContentEditor.preloadRemoteModule()


Restore the previous statements

Restore the previous statements

--- src/chaplin/views/view.coffee
+++ src/chaplin/views/view.coffee
@@ -194,7 +194,7 @@
   #   e.g.
   #   @delegate('click', 'button.confirm', @confirm)
   delegate: (eventName, second, third) ->
-    if Backbone.utilsBackbone.utils?.delegate
+    if Backbone.utilsBackbone.utils and Backbone.utilsBackbone.utils?.delegate
       return Backbone.utils.delegate(this, eventName, second, third)
     if typeof eventName isnt 'string'
       throw new TypeError 'View#delegate: first argument must be a string'
@@ -251,7 +251,7 @@

   # Remove all handlers registered with @delegate.
   undelegate: (eventName, second, third) ->
-    if Backbone.utilsBackbone.utils?.undelegate
+    if Backbone.utilsBackbone.utils and Backbone.utilsBackbone.utils?.undelegate
       return Backbone.utils.undelegate(this, eventName, second, third)
     if eventName
       if typeof eventName isnt 'string'


compilation fixed

compilation fixed

--- src/client/common_stat.coffee
+++ src/client/common_stat.coffee
@@ -89,16 +89,14 @@
       success: (data, textStatus) ->
         renderTables(data)
         $('.common-rating').tablesorter({
-          headers:
-          {
+          headers: {
             0: { sorter: false}
             2: { sorter: 'ratingToString'}
           }
           sortList: [
             [2, 1]
           ]
-          textSorter:
-          {
+          textSorter: {
             2: (a, b, direction, column, table)->
               return playercomparator.compare(a, b)
           }
@@ -108,8 +106,7 @@
         })
         ['wins', 'roles', 'impact'].forEach((className)->
           $(".#{className}").tablesorter({
-            headers:
-            {
+            headers: {
               0: { sorter: false}
             }
             sortList: [


Social: use static accessor

Social: use static accessor

--- workers/social/lib/social/models/socialapi/message.coffee
+++ workers/social/lib/social/models/socialapi/message.coffee
@@ -44,13 +44,13 @@
     success: (client, data, callback)->
       SocialMessage.ensureGroupChannel client, (err, socialApiChannelId)->
         data.channelId = socialApiChannelId
-        @doRequest 'postToChannel', client, data, callback
+        SocialMessage.doRequest 'postToChannel', client, data, callback

   @reply = permit 'create posts',
     success: (client, data, callback)->
       if not data.messageId or not data.body
-        return callback { message: "Request is not valid for add a reply to the message" }
-      @doRequest 'addReply', client, data, callback
+        return callback message: "Request is not valid for adding a reply"
+      SocialMessage.doRequest 'addReply', client, data, callback

   # todo add permission here
   @edit = secure (client, data, callback)->
@@ -61,26 +61,25 @@
       return callback err if err
       return callback {message: "You can not edit this post"} unless res
       {editMessage} = require './requests'
-      editMessage data, (err, activities)->
-        callback err, activities
+      editMessage data, callback

   @delete = permit 'delete posts',
     success: (client, data, callback)->
       if not data.id
-        return callback { message: "Request is not valid for deleting a message"}
-      @doRequest 'deleteMessage', client, data, callback
+        return callback message: "Request is not valid for deleting a message"
+      SocialMessage.doRequest 'deleteMessage', client, data, callback

   @like = permit 'like posts',
     success: (client, data, callback)->
       if not data.id
-        return callback { message: "Request is not valid for liking a message"}
-      @doRequest 'likeMessage', client, data, callback
+        return callback message: "Request is not valid for liking a message"
+      SocialMessage.doRequest 'likeMessage', client, data, callback

   @unlike = permit 'like posts',
     success:  (client, data, callback)->
       if not data.id
-        return callback { message: "Request is not valid for unliking a message"}
-      @doRequest 'unlikeMessage', client, data, callback
+        return callback message: "Request is not valid for unliking a message"
+      SocialMessage.doRequest 'unlikeMessage', client, data, callback

   @doRequest = (funcName, client, options, callback)->
     fetchGroup client, (err, group)->


Removing return statements from the coffeescript.

--- src/coffee/application.coffee
+++ src/coffee/application.coffee
@@ -7,6 +7,7 @@
 TOUCHSTART_SECTION = null

 easingFunction = jQuery.fn.velocity.Easings.easeInQuad
+

 $(document).keydown (e) ->
    if e.which == 40
@@ -21,16 +22,16 @@
          return
       CURRENT_SECTION -= 1
       navigateToCurrentSection()
-   return
+

 $(window).resize ->
    adjustContainerHeight()
-   return
+

 $(window).mousewheel (event) ->
    ACCUMULATED_SCROLL += -(event.deltaY * event.deltaFactor)
    processAccumulatedMove()
-   return
+

 $(window).on 'touchstart', (event) ->
    event.preventDefault()
@@ -38,7 +39,7 @@
    TOUCHSTART_Y = e.touches[0].pageY
    TOUCHSTART_SECTION = CURRENT_SECTION
    console.log e
-   return
+

 $(window).on 'touchmove', (event) ->
    event.preventDefault()
@@ -49,7 +50,7 @@
    if CURRENT_SECTION != TOUCHSTART_SECTION
       TOUCHSTART_Y = y
       TOUCHSTART_SECTION = CURRENT_SECTION
-   return
+

 processAccumulatedMove = ->
    if ACCUMULATED_SCROLL < 0 and CURRENT_SECTION == 0 or ACCUMULATED_SCROLL > 0 and CURRENT_SECTION == SECTION_COUNT - 1
@@ -74,7 +75,7 @@
          navigateToCurrentSection 200
          return
       ), 600)
-   return
+

 transitionToAccumulated = ->
    height = $(window).height()
@@ -84,19 +85,19 @@
    $('#page-wrapper').velocity('stop').velocity { translateY: -(CURRENT_SECTION * height + ACCUMULATED_SCROLL * easing_ratio / 3) },
       easing: 'linear'
      duration: 100
-   return
+

 navigateToCurrentSection = (duration) ->
    if duration == undefined
       duration = 300
    offset = -CURRENT_SECTION * $(window).height()
    $('#page-wrapper').velocity('stop').velocity { translateY: offset }, duration
-   return
+

 adjustContainerHeight = ->
    height = $(window).height()
    $('.container').height(height).css lineHeight: height + 'px'
    navigateToCurrentSection()
-   return
+

 adjustContainerHeight()


more cruft cleanup

more cruft cleanup

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -153,37 +153,32 @@
         "left:#{offset[0]}px; top:#{ypos}px")
       plot_wrapper.append(view.$el)
       @$el.append(plot_wrapper)
+      @$el.attr(
+        'style',
+        "height:#{@mget('height')}px; width:#{@mget('width')}px;")
     @render_end()

 class PlotView extends Continuum.DeferredView
-  default_options : {
-    scale:1.0
-  }
+  default_options : {scale:1.0}

   build_renderers : ->
-    build_views(@model, @renderers, @mget('renderers')
-      ,
+    build_views(@model, @renderers, @mget('renderers'),
         plot_id : @id,
         plot_model : @model
-        plot_view : @
-    )
+        plot_view : @)

   build_axes : ->
-    build_views(@model, @axes, @mget('axes')
-      ,
+    build_views(@model, @axes, @mget('axes'),
         plot_id : @id
         plot_model : @model
-        plot_view : @
-    )
+        plot_view : @)

   build_tools : ->
-    build_views(@model, @tools, @mget('tools')
-      ,
+    build_views(@model, @tools, @mget('tools'),
         plot_id : @id,
         plot_model : @model
-        plot_view : @
-    )
+        plot_view : @)

   build_overlays : ->
     #add ids of renderer views into the overlay spec
@@ -195,12 +190,10 @@
       overlayspec['options']['rendererviews'] = []
       for renderer in overlay.get('renderers')
         overlayspec['options']['rendererviews'].push(@renderers[renderer.id])
-    build_views(@model, @overlays, overlays
-      ,
+    build_views(@model, @overlays, overlays,
         plot_id : @id,
         plot_model : @model
-        plot_view : @
-    )
+        plot_view : @)

   bind_overlays : ->
     for overlayspec in @mget('overlays')
@@ -248,7 +241,6 @@
     safebind(this, @model, 'destroy', () => @remove())
     #@$el.attr('style', "display:block; height:300px; width:400px;")

-    window.plot_el = @$el
     @$el.append($("""

@@ -316,7 +308,6 @@
     super(force)
     all_views = _.flatten(_.map([@tools, @axes, @renderers, @overlays], _.values))

-    window.av = all_views
     if _.any(all_views, (v) -> v._dirty)
       @ctx.clearRect(0,0,  @mget('width'), @mget('height'))
       for v in all_views
@@ -362,7 +353,7 @@
     safebind(this, @model, 'change', @request_render)
     safebind(this, @mget_ref('mapper'), 'change', @request_render)

-  tagName : 'g'
+  tagName : 'div'

   get_offsets : (orientation) ->
     offsets =
@@ -685,7 +676,6 @@
   mouse_coords : (e, x, y) ->
     [x_, y_] = [@plot_model.rxpos(x), @plot_model.rypos(y)]
     return [x_, y_]
-

   _start_drag : (e, x, y) ->
     @dragging = true
@@ -865,7 +855,6 @@
         @dragging = false
         @button_panning = false
       else
-        @
         @_start_drag(e, x, y))

     @plotview.moveCallbacks.push((e, x, y) =>


Add Content-Length to header if there's post data.

--- client.coffee
+++ client.coffee
@@ -84,6 +84,9 @@
                 headers: {
                     'user-agent': 'VersionOne JS SDK Client/5.0 ' + SDK_CLIENT_VERSION
                 }
+
+            req_options.headers['Content-Length'] = options.postdata.length if options.postdata?
+
             request_done = (response) ->
                 alldata = []
                 response.on 'data', (data)->


Moved the 'install' section in the manual up.

--- client.coffee
+++ client.coffee
@@ -281,6 +281,9 @@
 showHelpPage = !->
 	Page.nav !->
 		Markdown.render """
+## Install
+Do you want to install Happening against Humanity in more groups? Tap the 'Add Plugin' button in that group, and enter the code '159r555232' in the search to do so!
+
 ## Happening against Humanity
 Happening against Humanity is a politically very incorrect game that is not suited for children, families, or people who are offended by anything. At all. It is, however, a very fun game to play with some friends who don't care too much about any of that!

@@ -300,9 +303,6 @@

 ## Inspiration
 The game is based on the game 'Cards against Humanity', and uses its original set of cards (with some changes, with thanks to /Pretend You're Xyzzy/), which have been made available to use under the [Attribution-NonCommercial-ShareAlike 3.0 Creative Commons license](http://creativecommons.org/licenses/by-nc-sa/3.0/).
-
-## Install
-Do you want to install Happening against Humanity in more groups? Tap the 'Add Plugin' button in that plugin, and enter the code '159r555232' in the search to do so!

 Do you have ideas for more cards? Please suggest them in the support Happening!
 """


Fix beacon tool tip

Fix beacon tool tip

--- client.coffee
+++ client.coffee
@@ -1134,7 +1134,7 @@
 					popup = L.popup()
 						.setLatLng(location)
 						.setContent(popupString)
-					marker.bindPopup(popup)
+					marker.bindPopup(popup,  {closeOnClick: true, closeButton: false})
 					markerClick = () ->
 						beaconMarkers[beacon.key()].togglePopup()
 					marker.on('contextmenu', markerClick)


Remove unused require

Remove unused require

--- lib/bookmarks.coffee
+++ lib/bookmarks.coffee
@@ -1,4 +1,4 @@
-{$, _} = require 'atom'
+{_} = require 'atom'

 module.exports =
 class Bookmarks


Refactor.

Refactor.

--- src/jquery.popover.coffee
+++ src/jquery.popover.coffee
@@ -59,11 +59,11 @@

 			@_content.addClass @settings.containerClass

-			@_content.css(display:'none').appendTo @settings.attach
+			@_content.css(display:'none', position:'absolute').appendTo @settings.attach

 			pos = getPosition @element, @settings.position, @_content, @settings.offsetX, @settings.offsetY

-			@_content.css(position:'absolute').css(pos)
+			@_content.css(pos)

 			@_isOpen = yes

@@ -78,6 +78,7 @@

 			cb = -> $(@).remove()
 			@settings.hideFn.apply @_content, [cb]
+
 		destroy: =>
 			@hide() if @_isOpen()
 			@element.unbind @_method, @show


Camelize error property names

Camelize error property names

--- src/interfaces/json_api.coffee
+++ src/interfaces/json_api.coffee
@@ -441,7 +441,7 @@
   # 3. Add error to resource
   addErrorsFromPersistenceAttempt = (errors, resource) ->
     _.each errors, (error) ->
-      resource.errors().add(error.field || 'base', error.key, error.message)
+      resource.errors().add(s.camelize(error.field) || 'base', s.camelize(error.key), error.message)
     resource

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


improved the URL expansion with a link replacement addition to the expandUrls method

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -135,10 +135,20 @@

   applyTextExpansions: (text, shorten)->
     return null unless text
-    # @expandWwwDotDomains @expandUrls @expandUsernames @expandTags text
+
     text = text.replace /
/g, ' '
+    text = @expandUsernames text
+
+    # Expand URLs with intention to replace them after putShowMore
+    {links,text} = @expandUrls text, yes
+
     text = __utils.putShowMore text if shorten
-    text = @expandUrls @expandUsernames text
+
+    # Reinsert URLs into text
+    if links? then for link,i in links
+      text = text.replace "[tempLink#{i}]", link
+
+    return text
     # @expandWwwDotDomains @expandUrls @expandUsernames text

   expandWwwDotDomains: (text) ->
@@ -179,21 +189,11 @@
       tag = t.replace "#", ""
       "#{tag}"

-  expandUrls: (text) ->
-    return null unless text
-
-    # urlGrabber = ///
-    #   (\s|^)                              # Start after a whitespace or string[0]
-    #   ([a-zA-Z]+\://)?                    # Captures any protocol (just not //)
-    #   (\w+:\w+@)?                         # Username:Password
-    #   ([a-zA-Z\d-]|[a-zA-Z\d-]\.)*        # Subdomains
-    #   [a-zA-Z\d-]{2,}                     # Domain name
-    #   \.                                  # THE DOT
-    #   ([a-zA-Z]{2,4}(:\d+)?)              # Domain Extension with Port
-    #   ([/\?\#][\S/]*)*                    # Some Request, greedy capture
-    #   \b                                  # Last word boundary
-    #   /?                                 # Optional trailing Slash
-    # ///g
+  expandUrls: (text,replaceAndYieldLinks=no) ->
+    return null unless text
+
+    links = []
+    linkCount = 0

     urlGrabber = ///
     (?!\s)                                                      # leading spaces
@@ -208,15 +208,17 @@
     (?!\S)
     ///g

-    # used to be /(\s|^)([a-zA-Z]+\:\/\/)?(\w+:\w+@)?([a-zA-Z\d-]|[a-zA-Z\d-]\.)*[a-zA-Z\d-]{2,}\.([a-zA-Z]{2,4}(:\d+)?)([\/\?#][\S\/]*)*\b\/?/g
-    text.replace urlGrabber, (url) ->
+
+    # This will change the original string to either a fully replaced version
+    # or a version with temporary replacement strings that will later be replaced
+    # with the expanded html tags
+    text = text.replace urlGrabber, (url) ->

       url = url.trim()
       originalUrl = url

       # remove protocol and trailing path
       visibleUrl = url.replace(/(ht|f)tp(s)?\:\/\//,"").replace(/\/.*/,"")
-
       checkForPostSlash = /.*(\/\/)+.*\/.+/.test originalUrl # test for // ... / ...

       if not /[A-Za-z]+:\/\//.test url
@@ -224,7 +226,23 @@
         # url has no protocol
         url = '//'+url

-      "#{visibleUrl}#{if checkForPostSlash then "/…" else ""}"
+      # Just yield a placeholder string for replacement later on
+      # this is needed if the text should get shortened, add expanded
+      # string to array at corresponding index
+      if replaceAndYieldLinks
+        links.push "#{visibleUrl}#{if checkForPostSlash then "/…" else ""}"
+        "[tempLink#{linkCount++}]"
+      else
+        # yield the replacement inline (good for non-shortened text)
+        "#{visibleUrl}#{if checkForPostSlash then "/…" else ""}"
+
+    if replaceAndYieldLinks
+      {
+        links
+        text
+      }
+    else
+      text

   putShowMore: (text, l = 500)->
     shortenedText = __utils.shortenText text,
@@ -258,16 +276,15 @@
       longText = Encoder.htmlDecode longText

       tempText = longText.slice 0, maxLength
-      lastClosingTag = tempText.lastIndexOf "</a"
-      lastOpeningTag = tempText.lastIndexOf "<a"
-
+      lastClosingTag = tempText.lastIndexOf "]"
+      lastOpeningTag = tempText.lastIndexOf "["

       if lastOpeningTag <= lastClosingTag
         finalMaxLength = maxLength
       else
         finalMaxLength = lastOpeningTag

-      return longText if longText.replace(/(<([^>]+)>)/ig,"").length < minLength or longText.replace(/(<([^>]+)>)/ig,"").length < maxLength
+      return longText if longText.length < minLength or longText.length < maxLength

       longText = longText.substr 0, finalMaxLength


[Completes #26965823] deletes houses matching any of the four angles

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -60,6 +60,7 @@
       # TODO: add points to the treats / massage.
       # This is just points presentation,
       # and it could be done with massage as well.
+      # Or later with a view.
       settings = @settings # so it can be passed to points as options
       ephemeris.stdout.on "data", (precious) ->
         points = new Points [], {data: JSON.parse(precious), settings: settings}
@@ -68,6 +69,7 @@
         # It's just about output format from here on.
         if json.length > 0
           ensemble = new (require "lin").Ensemble
+          longitude = "   longitude" # the name cliff label
           rpad = ' ' # right-padding for better readability
           table =
             [ { key: " "
@@ -83,7 +85,7 @@
               , val: (its, it) ->
                 if it.get('id') is '?' then its.id else it.get 'name'
               }
-            , { key: "   longitude"
+            , { key: longitude
               , req: ["lon"]
               , act: true
               , val: (its) ->
@@ -149,7 +151,7 @@
               id: lon.representations[i]
               lon: i * 30

-          # Process, sort and write to the stream.
+          # Process and sort.
           for i, item of json
             out.push { order: [] }
             for col in table
@@ -159,6 +161,7 @@
               out[i][col.key] = piece
               if col.sort? and not item.marker
                 out[i].order.push Number(item[col.sort])
+              out[i].id = item.id # for post-processing
             # Output markers for each representation.
             if item.marker is true
               # TODO: append `" topical % fortune"` house numbers to `what`.
@@ -167,9 +170,26 @@
               mark = ' ' + mark for count in [0..(5 - mark.length)]
               out[i].order.push Number(item.lon)
               out[i]['what'] = what.green
-              out[i]['   longitude'] = mark.green
+              out[i][longitude] = mark.green
           out = _.sortBy out, (obj) -> obj['order'][0]
-          stream.write cliff.stringifyObjectRows out, titles, color
+
+          # Reduce and write to the stream.
+          [seq, prev] = [{}, {}]
+          for i, item of out
+            out[i].extra = false # innocent by default (i.e. not guilty)
+            # TODO: this shoud be checking for hidden longitude degrees, instead
+            if prev[longitude] isnt item[longitude]
+              if _.size(seq) > 1
+                # One or more of the angles can be found in this sequence.
+                if (_.union ['AS', 'MC', 'DS', 'IC'], _.keys seq).length >= 1
+                  for id, idx of seq
+                    # Makes any house extra.
+                    out[idx].extra = true if id[0] is 'H'
+              seq = {} # reset the sequence
+            seq["#{item.id}"] = i
+            prev = item
+          outer = _.filter out, (final) -> final.extra is false
+          stream.write cliff.stringifyObjectRows outer, titles, color

         else stream.write "Given no data."
         stream.write "\n\n"


remove some verbosity

remove some verbosity

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -141,7 +141,6 @@
         # and returns a function to write raw data to the socket.

         @_connect @url, (data) =>
-            console.log("got '#{data}' of length #{data.length}")
             if data.length > 0  # all messages must start with a channel; length 0 means nothing.

                 # Incoming messages are tagged with a single UTF-16
@@ -159,7 +158,6 @@

                 channel = data[0]
                 data    = data.slice(1)
-                console.log("channel = #{channel}, data = #{data}")

                 @_handle_data(channel, data)


Bind 'this' for callback

Bind 'this' for callback

--- lib/list-edit.coffee
+++ lib/list-edit.coffee
@@ -111,7 +111,7 @@
       if not listElements?
         atom.notifications.addWarning 'List selection outside list.'
       else
-        f editor, textBuffer, bufferText, selectionIxRange, listElements
+        (f.bind this) editor, textBuffer, bufferText, selectionIxRange, listElements

 # for testing in console:


export the Change

export the Change

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -364,4 +364,4 @@
     throw new OperatorError("Not Implemented")

 module.exports = { NumericPrefix, RegisterPrefix, Delete, OperatorError, Yank,
-  Put, Join, Indent, Outdent }
+  Put, Join, Indent, Outdent, Change }


Remove deprecated way of creating a text editor for search (fix #198)

--- lib/todo-view.coffee
+++ lib/todo-view.coffee
@@ -16,19 +16,20 @@
 module.exports =
 class ShowTodoView extends ScrollView
   @content: (collection, filterBuffer) ->
-    filterEditor = deprecatedTextEditor(
-      mini: true
-      tabLength: 2
-      softTabs: true
-      softWrapped: false
-      buffer: filterBuffer
-      placeholderText: 'Search Todos'
-    )
+    # FIXME: Creating text editor this way results in weird getScopeChain error in Atom core - deprecated
+    # filterEditor = deprecatedTextEditor(
+    #   mini: true
+    #   tabLength: 2
+    #   softTabs: true
+    #   softWrapped: false
+    #   buffer: filterBuffer
+    #   placeholderText: 'Search Todos'
+    # )

     @div class: 'show-todo-preview', tabindex: -1, =>
       @div class: 'input-block', =>
-        @div class: 'input-block-item input-block-item--flex', =>
-          @subview 'filterEditorView', new TextEditorView(editor: filterEditor)
+        @div class: 'input-block-item input-block-item--flex'
+          # @subview 'filterEditorView', new TextEditorView(editor: filterEditor)
         @div class: 'input-block-item', =>
           @div class: 'btn-group', =>
             @button outlet: 'scopeButton', class: 'btn'
@@ -107,9 +108,9 @@
       @disposables.add editor.onDidSave =>
         @search()

-    @filterEditorView.getModel().onDidStopChanging =>
-      @filter() if @firstTimeFilter
-      @firstTimeFilter = true
+    # @filterEditorView.getModel().onDidStopChanging =>
+    #   @filter() if @firstTimeFilter
+    #   @firstTimeFilter = true

     @scopeButton.on 'click', @toggleSearchScope
     @optionsButton.on 'click', @toggleOptions


Eagerly reload parent directory after creating a file.

Fixes some flaky specs.

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -705,6 +705,7 @@
       @updateRoots() if atom.config.get('tree-view.squashDirectoryNames')
       @emitter.emit 'directory-created', {path: createdPath}
     dialog.onDidCreateFile (createdPath) =>
+      @entryForPath(createdPath)?.reload()
       atom.workspace.open(createdPath)
       @updateRoots() if atom.config.get('tree-view.squashDirectoryNames')
       @emitter.emit 'file-created', {path: createdPath}


removed duplicate function.

removed duplicate function.

--- picoCTF-web/web/coffee/login.coffee
+++ picoCTF-web/web/coffee/login.coffee
@@ -1,9 +1,3 @@
-window.checkLoggedIn = ->
- apiCall "GET", "/api/user/isloggedin"
-  .done (data) ->
-    if data['status'] == 1
-      document.location.href = "/problems"
-
 login = (e) ->
   e.preventDefault()
   apiCall "POST", "/api/user/login", $("#login-form").serialize()


Remove SelectList shim

Remove SelectList shim

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -22,10 +22,6 @@
   module.exports.$$$ = $$$
   module.exports.EditorView = require '../src/editor-view'
   module.exports.WorkspaceView = require '../src/workspace-view'
-
-  # TODO Remove once packages have been updated
-  module.exports.SelectList = require '../src/select-list-view'
-
   module.exports.SelectListView = require '../src/select-list-view'
   module.exports.ScrollView = require '../src/scroll-view'
   module.exports.Task = require '../src/task'


Documentation fix: s/traverse/traverseChildren/

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -45,7 +45,7 @@

 # Parse a string of CoffeeScript code or an array of lexed tokens, and
 # return the AST. You can then compile it by calling `.compile()` on the root,
-# or traverse it by using `.traverse()` with a callback.
+# or traverse it by using `.traverseChildren()` with a callback.
 exports.nodes = (source, options) ->
   if typeof source is 'string'
     parser.parse lexer.tokenize source, options


Move a try/catch from compile to loadFile

This try/catch should only be necessary for dynamically loaded files. Also added a lengthier explanation of why this try/catch is needed.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -33,13 +33,7 @@
   if options.sourceMap
     map = new SourceMap

-  try
-    fragments = parser.parse(lexer.tokenize code, options).compileToFragments options
-  catch err
-    # Add source file information to error so it can be pretty-printed later.
-    err.filename = options.filename
-    err.code = code
-    throw err
+  fragments = parser.parse(lexer.tokenize code, options).compileToFragments options

   currentLine = 0
   currentLine += 1 if options.header
@@ -152,7 +146,15 @@
 loadFile = (module, filename) ->
   raw = fs.readFileSync filename, 'utf8'
   stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
-  answer = compile(stripped, {filename, sourceMap: true, literate: helpers.isLiterate filename})
+  try
+    answer = compile(stripped, {filename, sourceMap: true, literate: helpers.isLiterate filename})
+  catch err
+    # As the filename and code of a dynamically loaded file will be different
+    # from the original file compiled with CoffeeScript.run, add that
+    # information to error so it can be pretty-printed later.
+    err.filename = filename
+    err.code = stripped
+    throw err
   sourceMaps[filename] = answer.sourceMap
   module._compile answer.js, filename


fix bug don't stop loop

fix bug don't stop loop

--- coffee/ticket.coffee
+++ coffee/ticket.coffee
@@ -106,7 +106,7 @@

       for t in ticketslist
         tickets.push t
-        if t.show is SHOW.NOT then return
+        if t.show is SHOW.NOT then continue
         selectableTickets.push t

       selectableTickets.sortById()


codelimate

codelimate

--- fme-sortable.coffee
+++ fme-sortable.coffee
@@ -31,8 +31,8 @@
         dropped_fmeIndex = e.dataTransfer.getData('text/plain')
         dropped_model = scope.fmeList[dropped_fmeIndex]
         element.removeClass('dropzone')
-        #timeout needed see http://stackoverflow.com/questions/19391773/angularjs-parent-scope-not-updated-in-directive-with-isolated-scope-two-way-b
         $timeout ->
-          scope.fmeList.splice(dropped_fmeIndex,1)
-          scope.fmeList.splice(scope.fmeIndex,0,dropped_model)
-          scope.fmeOnDrop()+          scope.fmeList.splice(dropped_fmeIndex, 1)
+          scope.fmeList.splice(scope.fmeIndex, 0, dropped_model)
+          scope.fmeOnDrop()
+


Fix superView warnings in Layer::centerFrame.

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -29,14 +29,14 @@
 	exportable: true
 	# default: fallback
 	get: -> @style[cssProperty]
-	set: (value) ->
+	set: (value) ->
 		@style[cssProperty] = value
 		@emit "change:#{cssProperty}", value

 frameProperty = (name) ->
 	exportable: false
 	get: -> @frame[name]
-	set: (value) ->
+	set: (value) ->
 		frame = @frame
 		frame[name] = value
 		@frame = frame
@@ -94,9 +94,9 @@

 	@define "clip", layerProperty "clip", "overflow", true
-	@define "scrollHorizontal", layerProperty "scrollHorizontal", "overflowX", false, (layer, value) ->
+	@define "scrollHorizontal", layerProperty "scrollHorizontal", "overflowX", false, (layer, value) ->
 		layer.ignoreEvents = false if value is true
-	@define "scrollVertical", layerProperty "scrollVertical", "overflowY", false, (layer, value) ->
+	@define "scrollVertical", layerProperty "scrollVertical", "overflowY", false, (layer, value) ->
 		layer.ignoreEvents = false if value is true

 	@define "scroll",
@@ -169,35 +169,35 @@
 		# Convert a point on screen to this views coordinate system
 		# TODO: needs tests
 		Utils.convertPoint point, null, @
-
+
 	screenFrame: ->
 		# Get this views absolute frame on the screen
 		# TODO: needs tests
 		Utils.convertPoint @frame, @, null
-
+
 	contentFrame: ->
 		Utils.frameMerge @subLayers.map (layer) -> layer.frame.properties

 	centerFrame: ->
-		# Get the centered frame for its superview
+		# Get the centered frame for its superLayer
 		# We always make these pixel perfect
 		# TODO: needs tests
-		if @superView
+		if @superLayer
 			frame = @frame
-			frame.midX = parseInt @superView.width / 2.0
-			frame.midY = parseInt @superView.height / 2.0
+			frame.midX = parseInt @superLayer.width / 2.0
+			frame.midY = parseInt @superLayer.height / 2.0
 			return frame
-
+
 		else
 			frame = @frame
 			frame.midX = parseInt window.innerWidth / 2.0
 			frame.midY = parseInt window.innerHeight / 2.0
 			return frame
-
+
 	center: -> @frame = @centerFrame() # Center  in superLayer
 	centerX: -> @x = @centerFrame().x # Center x in superLayer
 	centerY: -> @y = @centerFrame().y # Center y in superLayer
-
+
 	pixelAlign: ->
 		@x = parseInt @x
 		@y = parseInt @y
@@ -208,12 +208,12 @@

 	@define "style",
 		get: -> @_element.style
-		set: (value) ->
+		set: (value) ->
 			_.extend @_element.style, value
 			@emit "change:style"

 	@define "html",
-		get: ->
+		get: ->
 			@_elementHTML?.innerHTML

 		set: (value) ->
@@ -225,14 +225,14 @@
 			if not @_elementHTML
 				@_elementHTML = document.createElement "div"
 				@_element.appendChild @_elementHTML
-
+
 			@_elementHTML.innerHTML = value

 			# If the contents contains something else than plain text
 			# then we turn off ignoreEvents so buttons etc will work.

 			if not (
-				@_elementHTML.childNodes.length == 1 and
+				@_elementHTML.childNodes.length == 1 and
 				@_elementHTML.childNodes[0].nodeName == "#text")
 				@ignoreEvents = false

@@ -245,12 +245,12 @@
 		@style = Config.layerBaseCSS

 	@define "classList",
-		get: -> @_element.classList
+		get: -> @_element.classList

 	##############################################################
 	# DOM ELEMENTS
-
+
 	_createElement: ->
 		return if @_element?
 		@_element = document.createElement "div"
@@ -268,7 +268,7 @@
 		_RootElement.appendChild @_element

 	destroy: ->
-
+
 		if @superLayer
 			@superLayer._subLayers = _.without @superLayer._subLayers, @

@@ -299,7 +299,7 @@
 	## ANIMATION

 	animate: (options) ->
-
+
 		options.layer = @
 		options.curveOptions = options

@@ -333,35 +333,35 @@
 			# 	@backgroundColor = null

 			@backgroundColor = null
-
+
 			# Set the property value
 			@_setPropertyValue "image", value

 			imageUrl = value

-			# Optional base image value
+			# Optional base image value
 			# imageUrl = Config.baseUrl + imageUrl

 			# If the file is local, we want to avoid caching
 			# if Utils.isLocal()
 			# 	imageUrl += "?nocache=#{Date.now()}"
-
+
 			# As an optimization, we will only use a loader
 			# if something is explicitly listening to the load event
-
+
 			if @events?.hasOwnProperty "load" or @events?.hasOwnProperty "error"
-
+
 				loader = new Image()
 				loader.name = imageUrl
 				loader.src = imageUrl
-
+
 				loader.onload = =>
 					@style["background-image"] = "url('#{imageUrl}')"
 					@emit "load", loader
-
+
 				loader.onerror = =>
 					@emit "error", loader
-
+
 			else
 				@style["background-image"] = "url('#{imageUrl}')"

@@ -370,25 +370,25 @@

 	@define "superLayer",
 		exportable: false
-		get: ->
+		get: ->
 			@_superLayer or null
 		set: (layer) ->

 			return if layer is @_superLayer
-
+
 			# Check the type
 			if not layer instanceof Layer
 				throw Error "Layer.superLayer needs to be a Layer object"

 			# Cancel previous pending insertions
 			Utils.domCompleteCancel @__insertElement
-
+
 			# Remove from previous superlayer sublayers
 			if @_superLayer
 				@_superLayer._subLayers = _.without @_superLayer._subLayers, @
 				@_superLayer._element.removeChild @_element
 				@_superLayer.emit "change:subLayers", {added:[], removed:[@]}
-
+
 			# Either insert the element to the new superlayer element or into dom
 			if layer
 				layer._element.appendChild @_element
@@ -396,13 +396,13 @@
 				layer.emit "change:subLayers", {added:[@], removed:[]}
 			else
 				@_insertElement()
-
+
 			# Set the superlayer
 			@_superLayer = layer
-
+
 			# Place this layer on top of it's siblings
 			@bringToFront()
-
+
 			@emit "change:superLayer"

 	superLayers: ->
@@ -417,14 +417,14 @@
 		recurse @

 		superLayers
-
+
 	# Todo: should we have a recursive subLayers function?
 	# Let's make it when we need it.

 	@define "subLayers",
 		exportable: false
 		get: -> _.clone @_subLayers
-
+
 	@define "siblingLayers",
 		exportable: false
 		get: ->
@@ -438,12 +438,12 @@

 	addSubLayer: (layer) ->
 		layer.superLayer = @
-
+
 	removeSubLayer: (layer) ->
-
+
 		if layer not in @subLayers
 			return
-
+
 		layer.superLayer = null

 	subLayersByName: (name) ->
@@ -538,7 +538,7 @@

 	##############################################################
 	## EVENTS
-
+
 	addListener: (event, originalListener) =>

 		# Modify the scope to be the calling object, just like jquery
@@ -565,7 +565,7 @@
 	removeAllListeners: ->

 		return if not @_eventListeners
-
+
 		for eventName, listeners of @_eventListeners
 			for listener in listeners
 				@removeListener eventName, listener


PR feedback

PR feedback


#0 #done @功能 增加响应500的统一处理

#0 #done @功能 增加响应500的统一处理

--- common.coffee
+++ common.coffee
@@ -31,6 +31,13 @@
   res.statusCode = 404
   res.end()

+exports.response500 = (res, message)->
+  res.statusCode = 500
+  if typeof message is 'string'
+    res.end message
+  else
+    res.json message || {}
+

 #检查文件夹是否存在，如果不存在，则创建
 exports.dirPromise = (dir)->


Main file no longer requires jquery by default

--- game/main.coffee
+++ game/main.coffee
@@ -12,7 +12,6 @@

 situation = require('raconteur')
 undum = require('undum-commonjs')
-$ = require('jquery')
 oneOf = require('raconteur/lib/oneOf.js')
 elements = require('raconteur/lib/elements.js')
 qualities = require('raconteur/lib/qualities.js')
@@ -83,4 +82,4 @@

 # Get the party started when the DOM is ready.

-$(undum.begin)+window.onload = undum.begin


Another un-needed bit removed

Another un-needed bit removed

--- src/scripts/uptime.coffee
+++ src/scripts/uptime.coffee
@@ -28,7 +28,7 @@
   if elements.length > 1
     last = elements.pop()
     response = elements.join ', '
-    response += ' and ' + last if last
+    response += ' and ' + last
   else
     response = elements.join ', '


Update phonegap.coffee

Update phonegap.coffee

--- src/tasks/phonegap.coffee
+++ src/tasks/phonegap.coffee
@@ -40,6 +40,17 @@
     build.run platforms, (err, result) ->
       if err then grunt.fatal err
       done()
+
+  grunt.registerTask 'phonegap:serve', 'Serve a Phonegap application', ->
+    helpers = require('./helpers')(grunt)
+    helpers.mergeConfig defaults
+    serve = require('./serve')(grunt)
+
+    platform = @args[0] || _.first(grunt.config.get('phonegap.config.platforms'))
+    device  = @args[1] || ''
+
+    done = @async()
+    serve.serve platform, device, -> done()

   grunt.registerTask 'phonegap:run', 'Run a Phonegap application', ->
     helpers = require('./helpers')(grunt)


adding min and max zoom for the summary leaflet

--- static/coffee/nmis.coffee
+++ static/coffee/nmis.coffee
@@ -2494,11 +2494,12 @@
     summaryMap = L.map(mapDiv, {}).setView(ll, mapZoom)

     tileset = "nigeria_base"
-    maxZoom = 17
+    maxZoom = 11
+    minZoom = 6
     L.tileLayer("http://{s}.tiles.mapbox.com/v3/modilabs.#{tileset}/{z}/{x}/{y}.png", {
-      minZoom: 0
+      minZoom: minZoom
       maxZoom: maxZoom
-    }).addTo(summaryMap);
+    }).addTo(summaryMap)

   launch_summary = (params, state, lga, query_results={})->
     relevant_data = lga.ssData.relevant_data


Fix SQL for updating replies

Fix SQL for updating replies

Before that, updating a reply failed because the ID of the original post
was added to the parameters of the query but wasn't used by the query.
The result was PostgreSQL complaining that "bind message supplies 5
parameters, but prepared statement "" requires 4".

Fixes #98.

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -554,7 +554,7 @@
             else
                 irt_query = "NULL"
             if isSet
-                db.query "UPDATE items SET xml=$3, updated=#{updated_query} WHERE node=$1 AND id=$2"
+                db.query "UPDATE items SET xml=$3, updated=#{updated_query}, in_reply_to=#{irt_query} WHERE node=$1 AND id=$2"
                 , params
                 , cb2
             else


fixed memory leak involving iframes

fixed memory leak involving iframes

--- src/main/coffee/NR.Views.coffee
+++ src/main/coffee/NR.Views.coffee
@@ -231,9 +231,18 @@
             btn.removeClass "btn-primary"
             btn.addClass "btn-default"
         this._suppressRender = true
-
+
+    _leakCleanup: () ->
+        # We need to navigate all iframes inside the article to about:blank first.
+        # Failure to do so results in memory leaks.
+        frameList = this.$('iframe')
+        for i in frameList
+            i.contentWindow.location.href = "about:blank"
+        frameList.remove()
+
     render: () =>
         if not this.model? || (this.model? && not this._suppressRender)
+            this._leakCleanup()
             super()
             if this.model?
                 this.model.unregisterEvent("change:unread", this._updateUnread)
@@ -274,6 +283,12 @@
         else
             this._suppressRender = false

+    destroy: () =>
+        # We need to navigate all iframes inside the article to about:blank first.
+        # Failure to do so results in memory leaks.
+        this._leakCleanup()
+        super()
+
 class NR.Views.NewsArticleListing extends SimpleMVC.CollectionView
     @id "post-list-ui"
     @listClass "post-list"


Invert scrolling; scroll up for more saturated

Fixes #4

--- client/main.coffee
+++ client/main.coffee
@@ -439,7 +439,7 @@
   wheelEvent = (e) ->
     delta = if (e.wheelDelta || e.detail || e.originalEvent.wheelDelta || e.originalEvent.detail) > 0 then 0.025 else -0.025
     hsl = Session.get "currentColor"
-    hsl.s = Math.max(0, Math.min(1, hsl.s + delta))
+    hsl.s = Math.max(0, Math.min(1, hsl.s - delta))
     Session.set "currentColor", hsl if Session.equals "editActive", true
     converter.scheme.generate Session.get("schemeMode") unless Session.equals "schemeMode", "none"


Remove mysterious app.all function that didn't seem to do anything apart from set some unused global variables.

--- code/server.coffee
+++ code/server.coffee
@@ -82,14 +82,7 @@
     socketOptions:
       keepAlive: 1

-server_hostname = ''
-root_url = ''
 port = process.env.COBALT_PORT
-app.all "*", (req, res, next) ->
-  server_hostname = req.header('host')
-  root_url = "http://#{server_hostname}"
-  root_url = root_url + ":#{process.env.COBALT_PORT}" unless (port == '8000' || isNaN(parseInt(port)))
-  next()

 # Templating language
 app.set('view engine', 'ejs')


update the Quark T&C's link

update the Quark T&C's link

--- competition.coffee
+++ competition.coffee
@@ -21,7 +21,7 @@

           The Small Print

-          Please note that this competition is being run by Quark Expeditions, and all Quark Expeditions Terms & Conditions here apply.
+          Please note that this competition is being run by Quark Expeditions, and all Quark Expeditions Terms & Conditions apply.
           The Zooniverse reserves the right to disqualify any user from the competition if it is determined that they are not performing the markings to the best of their ability in order to gain an advantage in the prize draw. Zooniverse and Quark Expeditions employees, family and friends are not eligible to win the competition. All decisions made by the Zooniverse and Quark Expeditions with regard to competition are final.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -15,10 +15,10 @@
     destroySpy = true

   obj.I.x = 10000
-  obj.I.y = 4000
+  obj.I.y = 50

   obj.update()

-  ok destroySpy
+  ok destroySpy,

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -4,6 +4,8 @@

 App.width = 640
 App.height = 480
+
+test "should"

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


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -16,8 +16,8 @@

   destroySpy = false

-  obj.bind 'destroy', ->
-    destroySpy = true
+  #obj.bind 'destroy', ->
+  #  destroySpy = true

   obj.update()


Fix tests

Fix tests

--- test/client/search.coffee
+++ test/client/search.coffee
@@ -1,4 +1,4 @@
-define ['support', 'views/search2']
+define ['support', 'views/search']
 , ({should, eventStreamPromise}, {searchView})->

   describe 'search view', ->


remove leftover comment

remove leftover comment

--- core-coffee/linkHistogram.coffee
+++ core-coffee/linkHistogram.coffee
@@ -51,7 +51,7 @@
                .attr("transform", (d) ->
                     "translate(" + x(d.x) + "," + y(d.y) + ")"
                   )
-      #x(data[0].dx) - 1)
+
       bar.append("rect")
          .attr("x", 1)
          .attr("width", (d) ->


display name and no redundant requiring

display name and no redundant requiring

--- src/components/async-button.cjsx
+++ src/components/async-button.cjsx
@@ -2,19 +2,18 @@
 BS = require 'react-bootstrap'

 module.exports = React.createClass
-  displayText: 'AsyncButton'
+  displayName: 'AsyncButton'

   propTypes:
     store: React.PropTypes.object.isRequired
     isWaiting: React.PropTypes.bool.isRequired
     isDone: React.PropTypes.bool
-    isFailed: React.PropTypes.bool.isRequired
-    waitingText: React.PropTypes.any.isRequired # TODO: This should be a Component or array
-    errorText: React.PropTypes.any.isRequired
+    isFailed: React.PropTypes.bool
+    waitingText: React.PropTypes.any # TODO: This should be a Component or array
+    errorText: React.PropTypes.any
     doneText: React.PropTypes.any

   getDefaultProps: ->
-    isWaiting: false
     isDone: false
     isFailed: false
     waitingText: 'Loading…'


removing `is true` from conditional

removing `is true` from conditional

--- src/directives/table_view.coffee
+++ src/directives/table_view.coffee
@@ -113,7 +113,7 @@
       opts = util.extendAttributes "macTable", defaults, attrs

       # Disable lockFirstColumn if rowAutoHeight is true
-      opts.lockFirstColumn = false if opts.rowAutoHeight is true
+      opts.lockFirstColumn = false if opts.rowAutoHeight

       # Default special row height to row height if height is not defined
       opts.headerHeight      = opts.rowHeight unless attrs.macTableHeaderHeight?


Allow remove on a blank Store

Allow remove on a blank Store

--- app/assets/service/store.coffee
+++ app/assets/service/store.coffee
@@ -38,6 +38,7 @@
         @file.cancel()
         delete @file
         return true
+      return true unless @asset
       @asset.remove().then (asset) =>
           Store.removedAsset = asset
           messages.add


rethink: testing out implementing something

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -1,4 +1,4 @@
-TABLES = 'accounts  central_log'
+TABLES = 'accounts  central_log key_value'

 winston = require('winston')
 winston.remove(winston.transports.Console)
@@ -11,6 +11,7 @@

 TABLES = misc.split(TABLES)

+PROJECT_GROUPS = misc.PROJECT_GROUPS

 PROJECT_COLUMNS = exports.PROJECT_COLUMNS = ['project_id', 'account_id', 'title', 'last_edited', 'description', 'public', 'bup_location', 'size', 'deleted', 'hide_from_accounts'].concat(PROJECT_GROUPS)

@@ -109,27 +110,34 @@
         #TODO

 class KeyValueStore
-    #   c = new (require("cassandra").Salvus)(); d = c.key_value_store('test')
-    #   d.set(key:[1,2], value:[465, {abc:123, xyz:[1,2]}], ttl:5)
-    #   d.get(key:[1,2], console.log)   # but call it again in > 5 seconds and get nothing...
-    constructor: (opts={}) ->
+    constructor: (@db, opts={}) ->
         @opts = defaults(opts, name:required)
-        # TODO
+        @table = @db.table("key_value")

     set: (opts={}) =>
         opts = defaults opts,
+            key         : required
+            value       : required
+            cb          : undefined
+        # TODO: make a composite index so this stays fast
+        @table.filter(name:@opts.name, key:opts.key).run (err, r) =>
+            if err
+                cb(err); return
+            if r.length == 0
+                @table.insert({name: @opts.name, key:opts.key, value:opts.value}).run((err)=>opts.cb?(err))
+            else
+                @table.update({id:r[0].id, name: @opts.name, key:opts.key, value:opts.value}).run((err)=>opts.cb?(err))
+
+    get: (opts={}) =>
+        opts = defaults opts,
             key         : undefined
-            value       : undefined
-            ttl         : 0
-            cb          : undefined
-        # TODO
-
-    get: (opts={}) =>
-        opts = defaults opts,
-            key         : undefined
-            timestamp   : false      # if specified, result is {value:the_value, timestamp:the_timestamp} instead of just value.
-            cb          : undefined  # cb(error, value)
-        # TODO
+            cb          : required   # cb(error, value)
+        # TODO: make a composite index so this stays fast
+        @table.filter(name:@opts.name, key:opts.key).run (err, r) =>
+            if err
+                opts.cb(err)
+            else
+                opts.cb(undefined, r[0]?.value)

     delete: (opts={}) ->
         opts = defaults(opts, key:undefined, cb:undefined)
@@ -174,7 +182,7 @@
         ], (err) => cb?(err))

     key_value_store: (opts={}) => # key_value_store(name:"the name")
-        new KeyValueStore(@, opts)
+        new KeyValueStore(@db, opts)

     uuid_value_store: (opts={}) => # uuid_value_store(name:"the name")
         new UUIDValueStore(@, opts)


Remove Faker depedency.

Remove Faker depedency.

--- test/test_document.coffee
+++ test/test_document.coffee
@@ -4,7 +4,6 @@
 chai = require 'chai'
 should = chai.should()
 expect = chai.expect
-Faker = require 'Faker'
 mongojs = require 'mongojs'
 Document = require('../index').Document
 Embed = require('../index').Embed


Soak up nulls in music script

Soak up nulls in music script

--- scripts/music.coffee
+++ scripts/music.coffee
@@ -24,9 +24,9 @@
       limit: 1
       handlers:
         success: (data) ->
-          track = data?.recenttracks?.track[0]
+          track = data?.recenttracks?.track?[0]

-          if track['@attr'].nowplaying is 'true'
+          if track?['@attr']?.nowplaying is 'true'
             callbacks.success
               title: track.name
               artist: track.artist['#text']


Fix hupped queries

Fix hupped queries

This prevents calling old (already called) callbacks

--- db.coffee
+++ db.coffee
@@ -42,14 +42,21 @@
 	db.prepare = (sql, parameters..., callback) ->
 		myID = i++
 		parameters.unshift sql
-		parameters.push callback

 		query = null

 		# (re)prepares the query
-		hup = ->
-			query = oldPrepare.apply db, parameters
-		do hup
+		hup = (callback) ->
+			# copy parameters array
+			tmp = do parameters.slice
+
+			# append the given callback
+			tmp.push callback
+
+			# prepare the query
+			query = oldPrepare.apply db, tmp
+
+		hup callback

 		returnValue =
 			bind: (parameters...) -> query.bind.apply query, parameters


Added TNM score generator to services.

Added TNM score generator to services.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -455,19 +455,30 @@
         'Non-Hispanic': 'Not Hispanic or Latino'
         null: 'Not specified'
       }
-
-    # Add accordion control boolean values to subject encounters.
+    # The TNM metastatis parameters.
+    TNM_METASTASIS =
+      {
+        false: '0'
+        true: '1'
+      }
+
+    # Extend the subject encounters.
     for enc in subject.encounters
+      # Construst the TNM score as a single string: TxNxMx.
+      tnm = enc.outcome.tnm
+      tnm_score = 'T'.concat(tnm.grade.toString(), 'N', tnm.lymph_status.toString(), 'M', TNM_METASTASIS[tnm.metastasis])
+      _.extend enc, tnm_score: tnm_score
+      # Add accordion control boolean values to subject encounters.
       _.extend enc, accordion_open: true
-    # The encounters.
+    # The subject encounters.
     encounters: subject.encounters

-    # The demographics accordion control.
-    demogrOpen: true
     # The demographic data.
     age: subject.age
     races: (RACE_CHOICES[race] for race in subject.races).join(', ')
     ethnicity: ETHNICITY_CHOICES[subject.ethnicity]
+    # The demographics accordion control.
+    demogrOpen: true

 svcs.factory 'Intensity', ['Chart', (Chart) ->