removed dumb swears

removed dumb swears

--- src/scripts/hubot-demolition-man.coffee
+++ src/scripts/hubot-demolition-man.coffee
@@ -11,13 +11,12 @@
 #
 # Author:
 #   whitman, jan0sch
+
 moment = require 'moment'

 module.exports = (robot) ->

   words = [
-    'arsch',
-    'arschloch',
     'arse',
     'ass',
     'bastard',
@@ -29,23 +28,19 @@
     'cunt',
     'damn',
     'damnit',
-    'depp',
     'dick',
     'douche',
     'fag',
-    'fotze',
+    'fanny',
     'fuck',
     'fucked',
     'fucking',
-    'kacke',
+    'prick',
     'piss',
-    'pisse',
-    'scheisse',
-    'schlampe',
     'shit',
-    'wank',
-    'wichser'
+    'wank'
   ]
+
   regex = new RegExp("\\b(#{words.join('|')})\\b", 'ig')

   robot.hear regex, (msg) ->
@@ -58,8 +53,8 @@
     log.add user, credit

     if robot.brain.violation[user][key]
-      warn = user + ", you have been fined " + robot.brain.violation[user][key] + " credits today"
-      msg.send 'You have been fined ' + credit + ' credit(s) for a violation of the verbal morality statute. ('+warn+')'
+      warn = "#{user}, you are fined #{robot.brain.violation[user][key]} credits today"
+      msg.send "You are fined #{credit} credit#{['s' if credit > 1]} for a violation of the Verbal Morality Statute. (#{warn})"

 class Logger

@@ -67,17 +62,17 @@
     robot.brain.violation ?= {}
     @brain = robot.brain

-    add: (user, credits, dateKey) ->
-      dateKey ?= moment().format('YYYYMMDD')
+  add: (user, credits, date) ->
+    date ?= moment().format('YYYYMMDD')

-      try
-        if not @brain.violation[user]
-          @brain.violation[user] = {}
-          @brain.violation[user][dateKey] = credits
+    try
+      if not @brain.violation[user]
+        @brain.violation[user] = {}
+        @brain.violation[user][date] = credits
+      else
+        if not @brain.violation[user][date]
+          @brain.violation[user][date] = credits
         else
-          if not @brain.violation[user][dateKey]
-            @brain.violation[user][dateKey] = credits
-          else
-            @brain.violation[user][dateKey] += credits
-      catch error
-        console.log error
+          @brain.violation[user][date] += credits
+    catch error
+      console.log error


Refactor widget.svc to use getSsoSessionId getter method

--- src/services/widgets/widgets.svc.coffee
+++ src/services/widgets/widgets.svc.coffee
@@ -8,7 +8,9 @@
     # Getters
     #====================================
     @config = {}
-    @config.ssoSessionId = ""
+
+    @getSsoSessionId = ->
+      return ImpacMainSvc.getSsoSessionId()

     #====================================
     # Register Listeners
@@ -24,11 +26,10 @@
     @load = (force=false) ->
       deferred = $q.defer()

-      if _.isEmpty(_self.config.ssoSessionId) || force
+      if _.isEmpty(_self.getSsoSessionId()) || force

         $q.all([ImpacMainSvc.loadUserData(force), ImpacDashboardsSvc.load(force)]).then(
           (results) ->
-            _self.config.ssoSessionId = results[0].sso_session if results[0].sso_session
             deferred.resolve(_self.config)
           (error) ->
             deferred.reject(error)
@@ -158,7 +159,7 @@
           else
             data = {
               owner: widget.owner
-              sso_session: _self.config.ssoSessionId
+              sso_session: _self.getSsoSessionId()
               metadata: widget.metadata
               engine: widget.category
             }


Clean up constants

Clean up constants

--- demos/visualizer-lazy/Grid.coffee
+++ demos/visualizer-lazy/Grid.coffee
@@ -1,16 +1,19 @@
 'use strict'

-COLORS =
-	BACKGROUND: '#272822'
-	PATHBACKGROUND: '#49483E'
-	GRID: '#EDF'
-	PATHINDICATOR: 'rgb(24, 155, 230)'
-	TEXT: '#EDF'
-	ALTERED: '#6e2121'
-
-FONTS =
-	NORMAL: '20px Consolas, monospace'
-	SMALL: '14px Consolas, monospace'
+
+colors =
+	background: '#272822'
+	grid: '#EDF'
+	path:
+		arrow: 'rgb(24, 155, 230)'
+		background: '#49483E'
+	text: '#EDF'
+	altered: '#6e2121'
+
+
+fonts =
+	normal: '20px Consolas, monospace'
+	small: '14px Consolas, monospace'

 Grid = (@original, @playfield, @pathSet, @canvas) ->
@@ -34,7 +37,7 @@

 	@hitRegions = []
 	@_setupHitRegions()
-	@arrowImages = getArrowImages @cellSize / 3, COLORS
+	@arrowImages = getArrowImages @cellSize / 3
 	@draw()
 	return

@@ -44,7 +47,7 @@
 	canvas.width = size
 	canvas.height = size
 	con2d = canvas.getContext '2d'
-	con2d.strokeStyle = COLORS.PATHINDICATOR
+	con2d.strokeStyle = colors.path.arrow
 	con2d.lineWidth = 2

 	con2d.translate size / 2, size / 2
@@ -56,6 +59,7 @@
 	con2d.lineTo  size / 2 + 1, size / 2 - 4
 	con2d.stroke()
 	canvas
+

 getArrowImages = (size) ->
 	'^': getArrowImage size, 0
@@ -144,9 +148,9 @@
 			charOriginal = @original.getAt i, j
 			charNow = @playfield.getAt i, j
 			@con2d.fillStyle = if charNow == charOriginal
-					COLORS.BACKGROUND
+					colors.background
 				else
-					COLORS.ALTERED
+					colors.altered

 			@con2d.fillRect(
 				i * @cellSize
@@ -156,12 +160,12 @@
 			)

 	#active path
-	@con2d.fillStyle = COLORS.PATHBACKGROUND
+	@con2d.fillStyle = colors.path.background
 	@highlightedPath?.list.forEach (entry) =>
 		@con2d.fillRect entry.x * @cellSize, entry.y * @cellSize, @cellSize, @cellSize

 	#chars
-	@con2d.fillStyle = COLORS.TEXT
+	@con2d.fillStyle = colors.text
 	for i in [0...@playfield.width]
 		for j in [0...@playfield.height]
 			charRaw = @playfield.getAt i, j
@@ -169,10 +173,10 @@

 			if charCode > 0
 				charPretty = if 32 <= charCode <= 126
-					@con2d.font = FONTS.NORMAL
+					@con2d.font = fonts.normal
 					charRaw
 				else
-					@con2d.font = FONTS.SMALL
+					@con2d.font = fonts.small
 					"##{charCode}"

 				@con2d.fillText(
@@ -189,7 +193,7 @@
 	@con2d.save()
 	@con2d.translate 0.5, 0.5

-	@con2d.strokeStyle = COLORS.GRID
+	@con2d.strokeStyle = colors.grid
 	@con2d.beginPath()
 	for i in [1...@playfield.width]
 		@con2d.moveTo i * @cellSize, 0


fix for typo. refactoring.

fix for typo. refactoring.

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -37,22 +37,17 @@
     # if 3 elements, assume: gamertag, network, weaponSlot
     if input.length is 3
       el = input[1].toLowerCase()
-      membershipType = checkNetwork(el)
-      if membershipType is '1'
-        # replaces underscores with spaces for xbox users
-        data['displayName'] = input[0].splt('_').join(' ')
-      else
-        data['displayName'] = input[0]
+      data['membershipType'] = checkNetwork(el)
+      data['displayName'] = input[0]
     else if input.length is 2
       el = input[0].toLowerCase()
-      membershipType = checkNetwork(el)
-      if membershipType is null
+      data['membershipType'] = checkNetwork(el)
+      if data['membershipType'] is null
         # assume first input was gamertag
         data['displayName'] = input[0]
       else
         # assume gamertag not provided, use slack first name
         data['displayName'] = res.message.user.slack.profile.first_name
-      data['membershipType'] = membershipType
     else if input.length is 1
       # assume only weaponSlot was provided
       data['membershipType'] = null
@@ -103,6 +98,9 @@

 tryPlayerId = (res, membershipType, displayName, robot) ->
   deferred = new Deferred()
+  # replaces underscores with spaces (for xbox)
+  # safe to call on PSN IDs because underscores are not allowed
+  displayName = displayName.split('_').join(' ')

   if membershipType
     networkName = if membershipType is '1' then 'xbox' else 'playstation'


Fix error with clicking to upload on mod creation

--- scripts/create.coffee
+++ scripts/create.coffee
@@ -86,7 +86,7 @@
 , false)

 document.querySelector('.upload-mod input').addEventListener('change', (e) ->
-    select(e.target.files[0])
+    selectFile(e.target.files[0])
 , false)

 dragNop = (e) ->


adding greating and leaving message

adding greating and leaving message

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -64,3 +64,12 @@

   robot.respond /my glass is very empty/i, (msg)->
     msg.send "Why don't you fetch something for you an me"
+
+
+  enterReplies = ['Hi', 'Hello friend.', 'I see you :eye: :eye:']
+  leaveReplies = ['Finally he left. We didn\'t like him anyway', 'You won\'t be missed here ;p', 'This channel is not for quitters. I am glad he left!']
+
+  robot.enter (res) ->
+    res.send res.random enterReplies
+  robot.leave (res) ->
+    res.send res.random leaveReplies


Don't ask for money, if it's free.

Don't ask for money, if it's free.

--- client/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -248,39 +248,43 @@
       delete data.cardYear
       delete data.cardCV

-      modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)=>
-        # These will go into Recurly module
-        newData.username    = 'group_unnamed'
-        newData.ipAddress   = '0.0.0.0'
-        newData.firstName   = 'Group'
-        newData.lastName    = 'Unnamed'
-        newData.email       = 'group@example.com'
-        newData.pin         = 'xxx'
-        newData.accountCode = yes
-
-        formData.plan.subscribe newData, (err, account)=>
+      if formData.plan.feeMonthly is 0
+        KD.remote.api.JGroup.create formData, (err, group)=>
           if err
-            # Show error messages here.
-            onError err
+            callback? err
+            new KDNotificationView title: err.message, duration: 1000
           else
-            # DONE
-            onSuccess()
-
-            console.log account
-
-            KD.remote.api.JGroup.create formData, (err, group)=>
-              if err
-                callback? err
-                new KDNotificationView title: err.message, duration: 1000
-              else
-                account.attachToGroup group, (err, account)=>
-                  console.log "Attach"
-                  console.log err
-                  console.log account
-                  console.log "-------"
-                  callback? err, group
-                  @getSingleton("groupsController").showGroupCreatedModal group
-                  @destroy()
+            callback? err, group
+            @getSingleton("groupsController").showGroupCreatedModal group
+            @destroy()
+      else
+        modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)=>
+          # These will go into Recurly module
+          newData.username    = 'group_unnamed'
+          newData.ipAddress   = '0.0.0.0'
+          newData.firstName   = 'Group'
+          newData.lastName    = 'Unnamed'
+          newData.email       = 'group@example.com'
+          newData.pin         = 'xxx'
+          newData.accountCode = yes
+
+          formData.plan.subscribe newData, (err, account)=>
+            if err
+              # Show error messages here.
+              onError err
+            else
+              # DONE
+              onSuccess()
+
+              KD.remote.api.JGroup.create formData, (err, group)=>
+                if err
+                  callback? err
+                  new KDNotificationView title: err.message, duration: 1000
+                else
+                  account.attachToGroup group, (err, account)=>
+                    callback? err, group
+                    @getSingleton("groupsController").showGroupCreatedModal group
+                    @destroy()

   makeSlug: ->


home/vms: pass status to details as well

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/home/lib/virtualmachines/components/machineslist/listitem.coffee
+++ client/home/lib/virtualmachines/components/machineslist/listitem.coffee
@@ -61,6 +61,7 @@
     <main className="MachinesListItem-machineDetails">
       <MachineDetails
         machine={@props.machine}
+        status={@props.status}
         shouldRenderSpecs={@props.shouldRenderSpecs}
         shouldRenderPower={@props.shouldRenderPower}
         shouldRenderDisconnect={@props.shouldRenderDisconnect}


creep_actions: add meta actions for all roles

--- src/logic/creep_actions.coffee
+++ src/logic/creep_actions.coffee
@@ -72,19 +72,45 @@
     return
   chill_at_base(creep)

+### Healer meta actions ###
+
+smart_heal = (creep) ->
+  if creep.getActiveBodyparts(Game.HEAL) < 0
+    return false
+  hurt_creeps = (c for _, c of Game.creeps when c != creep and
+    c.hits < c.hitsMax)
+  if hurt_creeps.length == 0
+    creep.moveTo(closest_spawn(creep))
+    return
+  creep_to_heal = hurt_creeps[0]
+  set_job(creep, "healing #{creep_to_heal.name}")
+  creep.moveTo(creep_to_heal)
+  creep.heal(creep_to_heal)
+
+### Harvester meta actions ###
+
+smart_harvest = (creep) ->
+  if creep.energy >= creep.energyCapacity
+    return_to_closest_spawn(creep)
+    creep.transferEnergy(closest_spawn(creep))
+  else
+    harvest_closest_source(creep)
+
 ### ###

 # Different modes describe different creep actions
 default_mode = {
   WARRIOR: smart_attack
+  HEALER: smart_heal
+  HARVESTER: smart_harvest
 }
-pacifist = {
-  WARRIOR: no_attack
-}
-statue = {
-  CREEP: do_nothing
-}
+
+pacifist = default_mode
+pacifist[WARRIOR] = no_attack
+
+statue = default_mode
+statue[CREEP] = do_nothing

 player_1 = default_mode
 player_2 = default_mode
@@ -98,24 +124,14 @@
     if ACTIONS_MODE.CREEP?
       ACTIONS_MODE.CREEP(creep)
       continue
-
-    if creep.getActiveBodyparts(Game.HEAL) > 0
-      hurt_creeps = (c for _, c of Game.creeps when c != creep and
-        c.hits < c.hitsMax)
-      if hurt_creeps.length == 0
-        creep.moveTo(closest_spawn(creep))
+
+    if creep.memory.role == HEALER
+      if ACTIONS_MODE.HEALER(creep)
         continue
-      creep_to_heal = hurt_creeps[0]
-      set_job(creep, "healing #{creep_to_heal.name}")
-      creep.moveTo(creep_to_heal)
-      creep.heal(creep_to_heal)
-      continue
     if creep.memory.role == WARRIOR
-      ACTIONS_MODE.WARRIOR(creep)
-      continue
+      if ACTIONS_MODE.WARRIOR(creep)
+        continue
     if creep.memory.role == HARVESTER
-      if creep.energy >= creep.energyCapacity
-        return_to_closest_spawn(creep)
-        creep.transferEnergy(closest_spawn(creep))
-      else
-        harvest_closest_source(creep)
+      if ACTIONS_MODE.HARVESTER(creep)
+        continue
+    LOG("Couldn't find a job for #{creep.name}")


Move loop inside of if/else.

Move loop inside of if/else.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -103,13 +103,13 @@
             chat = JSON.parse(body)
             people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers])

-            for username in people
-                if points[username] == "awebot"
-                    # No points for awebot
-                else
+            if points[username] == "awebot"
+                # No points for awebot
+            else
+                for username in people
                     points[username] ?= 0
                     points[username] += 5
-                save(robot)
+                    save(robot)

                 # Winners (and then recall) is equal to all users
                 # with points, and their points respectively.


Use new style of pause request

Use new style of pause request

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -37,18 +37,20 @@

     deletePaused: =>
         $.ajax
-            url: "#{ @url() }/paused"
+            url:  "#{ @url() }/paused"
             type: 'DELETE'

     unpause: =>
         $.ajax
-            url: "#{ @url() }/unpause?user=#{ app.getUsername() }"
+            url:  "#{ @url() }/unpause?user=#{ app.getUsername() }"
             type: 'POST'

     pause: =>
         $.ajax
-            url: "#{ @url() }/pause?user=#{ app.getUsername() }"
-            type: 'POST'
+            url:         "#{ @url() }/pause"
+            type:        'POST'
+            contentType: 'application/json'
+            data:         JSON.stringify user: app.getUsername()

     run: (confirmedOrPromptData) ->
         options =
@@ -65,12 +67,12 @@

     bounce: =>
         $.ajax
-            url: "#{ @url() }/bounce?user=#{ app.getUsername() }"
+            url:  "#{ @url() }/bounce?user=#{ app.getUsername() }"
             type: "POST"

     destroy: =>
         $.ajax
-            url: "#{ @url() }?user=#{ app.getUsername() }"
+            url:  "#{ @url() }?user=#{ app.getUsername() }"
             type: "DELETE"

     ###


components-gtk: ComponentLoader: only load .glade and .ui files

--- components-gtk/ComponentLoader.coffee
+++ components-gtk/ComponentLoader.coffee
@@ -4,7 +4,8 @@

 exports = (loader, done) ->
   Utils.forEachInDirectory Gio.File.new_for_path('.'), (child) =>
-    return unless child.get_path().match(/.*\.glade/)
+    path = child.get_path()
+    return unless path.match(/.*\.glade$/) or path.match(/.*\.ui$/)
     log "Available glade ui file at #{child.get_path()}"
     name = child.get_basename().replace('\.glade', '').replace(/[^a-zA-Z\/\-0-9_]/g, '')
     component = GladeConstructorComponent.getComponentForFile child


added operators to the grammar cson

added operators to the grammar cson

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -3,10 +3,6 @@
 fileTypes: ['idr']
 patterns:
   [
-    {
-      name: 'operator.assignment'
-      match: '='
-    }
     {
       name: 'keyword.control.idris'
       match: '\\b(if|then|else|do|let|in|codata|record|dsl)\\b'
@@ -155,6 +151,10 @@
         [
           include: '#type_signature'
         ]
+    }
+    {
+      name: 'keyword.operator.idris'
+      match: "[|&!%$?~+:\\-.=</>\\\\*]+"
     }
   ]
 repository:


changed maximum speed/acceleration so the end state isn't so boring

--- evolution.coffee
+++ evolution.coffee
@@ -885,7 +885,7 @@
         (t) -> Math.max( .1, Math.abs(t.foodAffinity()) * 2 )
       ],
       maxAcceleration : [
-        => @radius
+        => @radius / 2
         (t) -> t.radius / 4
         (t) -> t.maxSpeed() * 2
       ]
@@ -908,7 +908,7 @@
       @genes.maxAcceleration[0] = @genes.maxAcceleration[0]()
     else
       @genes.maxAcceleration[0]
-  maxSpeed: -> @maxSp ||= @maxAcceleration() * 1.5
+  maxSpeed: -> @maxSp ||= @radius * 1.5
   react: ->
     super()
     x = 0


Fix URI::mergeParams when value is undefined

--- src/marbles/http/uri.js.coffee
+++ src/marbles/http/uri.js.coffee
@@ -37,7 +37,7 @@

   mergeParams: (params) =>
     params = for k,v of params
-      v = if typeof v.join is 'function'
+      v = if typeof v is 'object' && typeof v.join is 'function'
         encodeURIComponent(v.join(','))
       else
         encodeURIComponent(v)


Add project routes

Add project routes

--- config/routes.coffee
+++ config/routes.coffee
@@ -37,6 +37,14 @@
   app.patch '/users/:id', app.controllers.users.update
   app.delete '/users/:id', app.controllers.users.destroy

+  app.get '/projects/', app.controllers.projects.index
+  app.get '/projects/new', app.controllers.projects.new
+  app.post '/projects/', app.controllers.projects.create
+  app.get '/projects/:id', app.controllers.projects.show
+  app.get '/projects/:id/edit', app.controllers.projects.edit
+  app.patch '/projects/:id', app.controllers.projects.update
+  app.delete '/projects/:id', app.controllers.projects.destroy
+
   app.use errorHandlers.notFound
   app.use errorHandlers.validationError
   app.use errorHandlers.internalError


Comment out Janky check

Comment out Janky check

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -19,7 +19,7 @@
   grunt = gruntObject

   grunt.registerTask 'publish-build', 'Publish the built app', ->
-    return if process.env.JANKY_SHA1 #and process.env.JANKY_BRANCH isnt 'master'
+    # return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
     return unless process.platform is 'win32'
     tasks = ['upload-assets']
     tasks.unshift('build-docs', 'prepare-docs') if process.platform is 'darwin'


change shortcut key to ctrl-alt-c

change shortcut key to ctrl-alt-c

--- keymaps/csscomb.cson
+++ keymaps/csscomb.cson
@@ -1,2 +1,2 @@
 'atom-workspace':
-  'ctrl-C': 'csscomb:execute'
+  'ctrl-alt-c': 'csscomb:execute'


remove form-submit handler

remove form-submit handler

--- content/gitoe.coffee
+++ content/gitoe.coffee
@@ -85,6 +85,7 @@
     btn_repo_open.on "click", ()->
       path = input_repo_path.val()
       cb.repo_open? path
+      false

     for region in [ "branches", "history", "status" ]
       legend = @elem region, "legend"


Texts clearing right trim bug fixed

Texts clearing right trim bug fixed

--- file/clear.coffee
+++ file/clear.coffee
@@ -8,7 +8,7 @@
 		if node instanceof Text
 			# trim
 			node.text = node.text.replace /^[\s\uFEFF\xA0]+/g, ''
-			node.text = node.text.replace /([^\r\n]+)(?:[\s\uFEFF\xA0]+)$/g, '$1'
+			node.text = node.text.replace /([^\r\n]+)(?:[\s\uFEFF\xA0]*)$/g, '$1'

 			# remove empty texts
 			if node.text.length is 0


More improvements

More improvements

--- model/Recipe/Bitly/BitlyExportGoogleSpreadsheet.coffee
+++ model/Recipe/Bitly/BitlyExportGoogleSpreadsheet.coffee
@@ -27,7 +27,6 @@
     ,
       $setOnInsert:
         cls: "BitlyExportGoogleSpreadsheet"
-        isAutorun: true
       $set:
         position: position++
     )


Start off NOT allowing everyone

Start off NOT allowing everyone

--- jarkle/client/webgl_synth.coffee
+++ jarkle/client/webgl_synth.coffee
@@ -1,6 +1,8 @@
 NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']

 NOTE_ON_MIDI_NUMBER = 144
+
+ONLY_MASTERS = 'only masters'

 class @WebGlSynth
   constructor: (@schema, @skeletonConfig, @vis, noteMap, @pubSub) ->
@@ -9,7 +11,7 @@
     @synth = new Synth(new AudioContext(), noteMap, pubSub, @schema,
                        skeletonConfig)
     @playerManager = new PlayerManager(@schema)
-    @currentPlayerId = null
+    @currentPlayerId = ONLY_MASTERS

   handleNoteMessage: (noteMessage) =>
     userId = noteMessage.userId
@@ -77,7 +79,7 @@
       when 'E'
         # No players (apart from masters)
         Session.set 'infoMessage', null
-        @currentPlayerId = 'only masters'
+        @currentPlayerId = ONLY_MASTERS
         @pubSub.trigger CURRENT_PLAYER,
           _id: @currentPlayerId
       when 'F'


Updating

Updating

--- config.cson
+++ config.cson
@@ -1,22 +1,30 @@
 "*":
+  "":
+    basic:
+      html:
+        text:
+          editor:
+            softWrap: true
+    source:
+      yaml:
+        editor:
+          softWrap: true
   core:
     disabledPackages: [
       "language-gfm"
     ]
-  welcome:
-    showOnStartup: false
-  ".basic.html.text":
-    editor:
-      softWrap: true
-  ".source.yaml":
-    editor:
-      softWrap: true
-  "toggle-soft-wrap":
-    linter:
-      showErrorPanel: false
+    projectHome: "/home/fusion809/GitHub/mine"
+  editor:
+    showIndentGuide: true
+    softWrap: true
   "rss-ticker":
     feed: "https://aur.archlinux.org/rss/"
     fileTypes:
       emacs: "source.lisp"
       spacemacs: "source.lisp"
     userId: "20e33b17-b6ee-be85-6d79-5468d2ceffbc"
+  "toggle-soft-wrap":
+    linter:
+      showErrorPanel: false
+  welcome:
+    showOnStartup: false


rearranged charges methods

rearranged charges methods

--- server/methods.coffee
+++ server/methods.coffee
@@ -1,3 +1,13 @@
+wrap = (resource, method, params) ->
+  Stripe = StripeAPI(Billing.settings.secretKey)
+  call = Async.wrap Stripe[resource], method
+  try
+    call params
+  catch e
+    console.error e
+    throw new Meteor.Error 500, e.message
+
+
 Meteor.methods

   #
@@ -73,28 +83,14 @@
   #
   createCharge: (params) ->
     console.log "Creating charge"
-
-    Stripe = StripeAPI(Billing.settings.secretKey)
-    createCharge = Async.wrap Stripe.charges, 'create'
-    try
-      createCharge params
-    catch e
-      console.error e
-      throw new Meteor.Error 500, e.message
+    wrap 'charges', 'create', params

   #
   # List charges with any filters applied
   #
   listCharges: (params) ->
     console.log "Getting past charges"
-
-    Stripe = StripeAPI(Billing.settings.secretKey)
-    listCharges = Async.wrap Stripe.charges, 'list'
-    try
-      listCharges params
-    catch e
-      console.error e
-      throw new Meteor.Error 500, e.message
+    wrap 'charges', 'list', params

   #


Fix a glitch when MAIL_URL is not set

Fix a glitch when MAIL_URL is not set

--- server/methods.coffee
+++ server/methods.coffee
@@ -1,6 +1,8 @@
 share.getMailDomail = ->
-  matches = Meteor.settings.mailUrl.match(/\/\/(.+)%40(.+):(.+)@(.+):(\d+)/)
-  matches[2]
+  if process.env.MAIL_URL
+    matches = process.env.MAIL_URL.match(/\/\/(.+)%40(.+):(.+)@(.+):(\d+)/)
+    return matches[2]
+  return ""

 Meteor.methods
   setPassword: (userId, password) ->


Extend plot range to -1,1.

Extend plot range to -1,1.

--- main.coffee
+++ main.coffee
@@ -24,20 +24,20 @@
     @h = Canvas.height

     @x2X = d3.scale.linear() # to pixels
-        .domain([-0.5, 0.5])
+        .domain([-1, 1])
         .range([0.25*@w, 0.75*@w])

     @X2x = @x2X.invert

     @y2Y = d3.scale.linear()
-        .domain([-0.5, 0.5])
+        .domain([-1, 1])
         .range([0.75*@h, 0.25*@h])

     @Y2y = @y2Y.invert

     @fn: (x, xm, ym) ->
-        n = -0.5+(x+0.5)*(2*ym+1)/(2*xm+1)
-        p = ym+(x-xm)*(1-2*ym)/(1-2*xm)
+        n = -1+(x+1)*(ym+1)/(xm+1)
+        p = ym+(x-xm)*(1-ym)/(1-xm)
         n*(x<xm) + p*(x>=xm)

 class Vector
@@ -78,7 +78,7 @@
     width = 480 - margin.left - margin.right
     height = 480 - margin.top - margin.bottom

-    constructor: (@xm=0, @ym=0.4) ->
+    constructor: (@xm=0, @ym=0.8) ->
         super "plot"

         chartArea = @obj
@@ -88,7 +88,7 @@
         chartArea.append("g")
             .attr("class", "axis")
             .attr("transform", "translate(#{margin.left}, #{margin.top+height-110})")
-            .call(@xAxis)
+                .call(@xAxis)

         chartArea.append("g")
             .attr("class", "axis")
@@ -106,11 +106,11 @@
             .attr("cx", Xm)
             .attr("cy", Ym)

-        @line1 = @roofLine(-0.5, -0.5)
+        @line1 = @roofLine(-1, -1)
             .attr("x2", Xm)
             .attr("y2", Ym)

-        @line2 = @roofLine(0.5, 0.5)
+        @line2 = @roofLine(1, 1)
             .attr("x2", Xm)
             .attr("y2", Ym)

@@ -172,13 +172,13 @@
         @xAxis = d3.svg.axis()
             .scale(Trans.x2X)
             .orient("bottom")
-            .tickValues([-0.5, 0, 0.5])
+            .tickValues([-1, 0, 1])
             .outerTickSize([0])

         @yAxis = d3.svg.axis()
             .scale(Trans.y2Y)
             .orient("left")
-            .tickValues([-0.5, 0, 0.5])
+            .tickValues([-1, 0, 1])
             .outerTickSize([0])

 class Histo extends d3Object
@@ -242,7 +242,7 @@
         x = Trans.X2x @pos.x
         y = Trans.fn x, plot.xm, plot.ym
         @limit =  Trans.y2Y y
-        @bin = Math.floor((y+0.5)*20)
+        @bin = Math.floor((y+1)*20/2)

     collision: -> @pos.y > @limit


Dashboard: Added custom partials section to dashboard.

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -77,11 +77,11 @@
           viewClass  : BadgeDashboardView
           lazy       : yes
       ,
-        name         : 'Home Pages'
+        name         : 'CustomPartials'
         hiddenHandle : @getData().privacy is 'public'
         kodingOnly   : yes # this is only intended for koding group, we assume koding group is super-group
         viewOptions  :
-          viewClass  : HomePagesDashboardView
+          viewClass  : CustomViewsManager
           lazy       : yes

       # CURRENTLY DISABLED


Move identity on nav

Move identity on nav

--- client/modules/packages/ui/nav.coffee
+++ client/modules/packages/ui/nav.coffee
@@ -55,7 +55,7 @@

-

+

 					You are


Do not pass undefined protocols to Websocket, to stop Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch

--- pace.coffee
+++ pace.coffee
@@ -65,7 +65,7 @@
     trackMethods: ['GET']

     # Should we track web socket connections?
-    trackWebSockets: false
+    trackWebSockets: true

     # A list of regular expressions or substrings of URLS we should ignore (for both tracking and restarting)
     ignoreURLs: []
@@ -350,7 +350,10 @@

     if _WebSocket? and options.ajax.trackWebSockets
       window.WebSocket = (url, protocols) =>
-        req = new _WebSocket(url, protocols)
+        if protocols?
+          req = new _WebSocket(url, protocols)
+        else
+          req = new _WebSocket(url)

         if shouldTrack('socket')
           @trigger 'request', {type: 'socket', url, protocols, request: req}


Team progression graph draft.

Team progression graph draft.

--- web/coffee/progression_graphs.coffee
+++ web/coffee/progression_graphs.coffee
@@ -7,6 +7,12 @@
   title: "Team Score Progression",
   legend: {
     position: "none"
+  },
+  vAxis: {
+    title: "Score"
+  },
+  hAxis: {
+    ticks: []
   },
   pointSize: 3
 }
@@ -29,10 +35,11 @@

   return _.extend continuousBucket, buckets

-maxValuesFromBuckets = (buckets, sampleKey) ->
+maxValuesFromBucketsExtended = (buckets, sampleKey) ->
   maxValues = []

   lastInsertedValue = 0
+
   _.each buckets, (samples) ->
     values = _.pluck(samples, sampleKey)

@@ -45,21 +52,33 @@

   return maxValues

-@drawTeamProgressionGraph = (selector) ->
+progressionDataToPoints = (data, bucketWindow, dataPoints) ->
+      buckets = timestampsToBuckets data, "time", bucketWindow
+      steps = maxValuesFromBucketsExtended buckets, "score"
+
+      if steps.length < dataPoints
+        return steps
+
+      return _.rest(steps, steps.length - dataPoints)
+
+@drawTeamProgressionGraph = (selector, points) ->
   div = divFromSelector selector
   apiCall "GET", "/api/stats/team/score_progression", {}
   .done (data) ->
-    graphData = [["Time", "Score"]]
-
-    lastSubmission = null
-    _.each data.data, (submission) ->
-      if lastSubmission
-        graphData.push [submission.time - 1, lastSubmission.score]
-      graphData.push [submission.time, submission.score]
-      lastSubmission = submission
+    if data.data.length > 0
+
+      graphData = [
+        ["Time", "Score", {role: "tooltip"}]
+      ]

-    packagedData = google.visualization.arrayToDataTable graphData
+      steps = progressionDataToPoints data.data, 600, 30
+
+      (graphData.push(["", score, score]) for score in steps)

-    chart = new google.visualization.LineChart(div)
-    chart.draw(packagedData, teamGraphOptions)
+      console.log graphData

+      packagedData = google.visualization.arrayToDataTable graphData
+
+      chart = new google.visualization.SteppedAreaChart(div)
+      chart.draw(packagedData, teamGraphOptions)
+


Add regional convention invitations for 2019 and 2020

--- client/views/pages/store/store.coffee
+++ client/views/pages/store/store.coffee
@@ -135,6 +135,10 @@
 				short: 'CO-inv17'
 			,
 				short: 'CO-inv18'
+			,
+				short: 'CO-inv19'
+			,
+				short: 'CO-inv20'
 			,
 				short: 'inv'
 			,


convert objects to strings once, not in a handlebars helper

--- src/reactive-attributes.coffee
+++ src/reactive-attributes.coffee
@@ -1,36 +1,34 @@
+# Convert attribute to string
+# ---------------------------
+attrString = (key, val) ->
+  if Match.test val, Array
+    vals = val.join ' '
+    "#{key}='#{vals}' "
+  else if Match.test val, String
+    "#{key}='#{val}' "
+
+
 # Add reactive HTML attributes to templates
 # -----------------------------------------
 # attrSet.call(this, ...)
 attrSet = (handlebarsId, template, attrSets, attrLogic) ->
   that = this
+
+  # Validate inputs
   check template, Match.ObjectIncluding in: String
   check handlebarsId, String
   check attrSets, Object
   check attrLogic, Function

+  # Stringify all attribute sets now so we don't have to do it programmatically
+  for own set, attrs of attrSets
+    fullAttrString = ''
+    fullAttrString += attrString attr, val for own attr, val of attrs
+    attrSets[set] = fullAttrString
+
   Template[template.in][handlebarsId] = ->
     attrSet = attrLogic.call(that)
-    if attrSets.hasOwnProperty attrProfile
-      return attrSets[attrProfile]
+    if attrSets.hasOwnProperty attrSet
+      return attrSets[attrSet]
     else
-      throw new Error "Attribute set #{attrSet} isn't specified for #{handlebarsId}"
-
-
-# Convert an object with array or string values into HTML attributes
-# ------------------------------------------------------------------
-# Removed the "default class" option, if you want to prepend a specific
-# class onto the element's classes, you'll have to add it in the objects,
-# perhaps have the class properties concat onto one with that class.
-Handlebars.registerHelper 'attrSet', (attrSet) ->
-  fullAttrString = ''
-  genAttrString = (key, val) ->
-    if Match.test val, Array
-      vals = val.join ' '
-      "#{key}='#{vals}' "
-    else if Match.test val, String
-      "#{key}='#{val}' "
-  check attrSet, Object
-  for own key, val of attrSet
-    fullAttrString += genAttrString key, val
-  return fullAttrString
-
+      throw new Error "Attribute set #{attrSet} isn't specified for #{handlebarsId}"


put logging in production to normal

put logging in production to normal

--- config/environments/production.coffee
+++ config/environments/production.coffee
@@ -2,4 +2,6 @@
     app = compound.app
     app.configure 'production', ->
         app.use require('express').errorHandler()
-        #app.enable 'quiet'
+        app.enable 'log actions'
+        app.enable 'env info'
+        app.enable 'quiet'


make log noisy in production mode

make log noisy in production mode

--- config/environments/production.coffee
+++ config/environments/production.coffee
@@ -2,4 +2,4 @@
     app = compound.app
     app.configure 'production', ->
         app.use require('express').errorHandler()
-        app.enable 'quiet'
+        #app.enable 'quiet'


Update addBlacklist.coffee

Update addBlacklist.coffee

--- src/routes/addBlacklist.coffee
+++ src/routes/addBlacklist.coffee
@@ -3,7 +3,7 @@
 global.AnonyPages.app.get '/addBlacklist/:key/:identifier', (req, res) ->
     config = global.AnonyPages.config
     i18n   = global.AnonyPages.i18n
-    path   = __dirname + "/../blacklist.list"
+    path   = "../blacklist.list"

     if !req.params.key? or !req.params.identifier or req.params.key != config.adminKey
         false


Add new lat/lng fields

Add new lat/lng fields

--- src/salesforce-sobjects.coffee
+++ src/salesforce-sobjects.coffee
@@ -18,6 +18,8 @@
     'billingstate',
     'billingpostalcode',
     'billingcountry',
+    'billinglatitude',
+    'billinglongitude',
     'createdbyid',
     'createddate',
     'jigsaw',
@@ -47,6 +49,8 @@
     'shippingstate',
     'shippingpostalcode',
     'shippingcountry',
+    'shippinglatitude',
+    'shippinglongitude',
     'systemmodstamp',
     'tickersymbol',
     'type',
@@ -91,6 +95,8 @@
     'mailingstate',
     'mailingpostalcode',
     'mailingcountry',
+    'mailinglatitude',
+    'mailinglongitude',
     'mobilephone',
     'salutation',
     'firstname',
@@ -101,6 +107,8 @@
     'otherpostalcode',
     'othercountry',
     'otherphone',
+    'otherlatitude',
+    'otherlongitude'
     'phone',
     'photourl',
     'connectionreceivedid',


documentation for command.coffee

documentation for command.coffee

--- command.coffee
+++ command.coffee
@@ -1,7 +1,8 @@
 # The `coffee` utility. Handles command-line compilation of CoffeeScript
 # into various forms: saved into `.js` files or printed to stdout, piped to
-# JSLint or recompiled every time the source is saved, printed as a token
-# stream or as the syntax tree.
+# [JSLint](http://javascriptlint.com/) or recompiled every time the source is
+# saved, printed as a token stream or as the syntax tree, or launch an
+# interactive REPL.

 # External dependencies.
 fs:           require 'fs'
@@ -9,6 +10,7 @@
 optparse:     require 'optparse'
 CoffeeScript: require 'coffee-script'

+# The help banner that is printed when `coffee` is called without arguments.
 BANNER: '''
   coffee compiles CoffeeScript source files into JavaScript.

@@ -16,6 +18,7 @@
     coffee path/to/script.coffee
         '''

+# The list of all the valid option flags that `coffee` knows how to handle.
 SWITCHES: [
   ['-c', '--compile',       'compile to JavaScript and save as .js files']
   ['-i', '--interactive',   'run an interactive CoffeeScript REPL']
@@ -32,11 +35,14 @@
   ['-h', '--help',          'display this help message']
 ]

+# Top-level objects shared by all the functions.
 options: {}
 sources: []
 option_parser: null

-# The CommandLine handles all of the functionality of the `coffee` utility.
+# Run `coffee` by parsing passed options and determining what action to take.
+# Many flags cause us to divert before compiling anything. Flags passed after
+# `--` will be passed verbatim to your script as arguments in `process.ARGV`
 exports.run: ->
   parse_options()
   return usage()                              if options.help
@@ -53,20 +59,9 @@
   process.ARGV: process.argv: flags
   watch_scripts() if options.watch
   compile_scripts()
-  this

-# The "--help" usage message.
-usage: ->
-  puts option_parser.help()
-  process.exit 0
-
-# The "--version" message.
-version: ->
-  puts "CoffeeScript version ${CoffeeScript.VERSION}"
-  process.exit 0
-
-# Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
-# or JSLint results.
+# Asynchronously read in each CoffeeScript in a list of source files and
+# compile them.
 compile_scripts: ->
   compile: (source) ->
     path.exists source, (exists) ->
@@ -75,7 +70,9 @@
   compile(source) for source in sources

 # Compile a single source script, containing the given code, according to the
-# requested options. Both compile_scripts and watch_scripts share this method.
+# requested options. Both compile_scripts and watch_scripts share this method
+# in common. If evaluating the script directly sets `__filename`, `__dirname`
+# and `module.filename` to be correct relative to the script's path.
 compile_script: (source, code) ->
   o: options
   try
@@ -94,7 +91,8 @@
   catch err
     if o.watch                  then puts err.message else throw err

-# Listen for and compile scripts over stdio.
+# Attach the appropriate listeners to compile scripts incoming over **stdin**,
+# and write them back to **stdout**.
 compile_stdio: ->
   code: ''
   process.stdio.open()
@@ -103,8 +101,9 @@
   process.stdio.addListener 'close', ->
     process.stdio.write CoffeeScript.compile code, compile_options()

-# Watch a list of source CoffeeScript files, recompiling them every time the
-# files are updated.
+# Watch a list of source CoffeeScript files using `fs.watchFile`, recompiling
+# them every time the files are updated. May be used in combination with other
+# options, such as `--lint` or `--print`.
 watch_scripts: ->
   watch: (source) ->
     fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
@@ -112,14 +111,17 @@
       fs.readFile source, (err, code) -> compile_script(source, code)
   watch(source) for source in sources

-# Write out a JavaScript source file with the compiled code.
+# Write out a JavaScript source file with the compiled code. By default, files
+# are written out in `cwd` as `.js` files with the same name, but the output
+# directory can be customized with `--output`.
 write_js: (source, js) ->
   filename: path.basename(source, path.extname(source)) + '.js'
   dir:      options.output or path.dirname(source)
   js_path:  path.join dir, filename
   fs.writeFile js_path, js

-# Pipe compiled JS through JSLint (requires a working 'jsl' command).
+# Pipe compiled JS through JSLint (requires a working `jsl` command), printing
+# any errors or warnings that arise.
 lint: (js) ->
   jsl: process.createChildProcess('jsl', ['-nologo', '-stdin'])
   jsl.addListener 'output', (result) ->
@@ -129,19 +131,30 @@
   jsl.write js
   jsl.close()

-# Pretty-print a token stream.
+# Pretty-print a stream of tokens.
 print_tokens: (tokens) ->
   strings: for token in tokens
     [tag, value]: [token[0], token[1].toString().replace(/\n/, '\\n')]
     "[$tag $value]"
   puts strings.join(' ')

-# Use OptionParser for all the options.
+# Use the [OptionParser module](optparse.html) to extract all options from
+# `process.ARGV` that are specified in `SWITCHES`.
 parse_options: ->
   option_parser: new optparse.OptionParser SWITCHES, BANNER
   options: option_parser.parse(process.ARGV)
   sources: options.arguments[2...options.arguments.length]

-# The options to pass to the CoffeeScript compiler.
+# The compile-time options to pass to the CoffeeScript compiler.
 compile_options: ->
   if options['no-wrap'] then {no_wrap: true} else {}
+
+# Print the `--help` usage message and exit.
+usage: ->
+  puts option_parser.help()
+  process.exit 0
+
+# Print the `--version` message and exit.
+version: ->
+  puts "CoffeeScript version ${CoffeeScript.VERSION}"
+  process.exit 0


more textarea tests, including test on broken clear() behavior

--- www/test/src/textarea.coffee
+++ www/test/src/textarea.coffee
@@ -2,28 +2,30 @@
     "jquery", "views/helper/textarea"
 ], ($, Textarea) ->
     describe "textarea:", ->
+        beforeEach -> $("body").append('

')
+        afterEach -> $("#fixture").remove()
+
+        create = (text) ->
+            text = "" unless text?
+            view = new Textarea(realm: "europe", placeholder: "Blah")
+            view.setElement $("#fixture") # otherwise view disables itself because it's not :visible
+            view.render()
+            view.reveal(text)
+            view
+
         it "is not active initially", ->
             expect(Textarea.active()).not.toBe true

         it "is not active after reveal", ->
-            view = new Textarea(realm: "europe", placeholder: "Blah")
-            view.render()
-            view.reveal("")
+            view = create()
             expect(Textarea.active()).not.toBe true

         it "is active if has revealed content", ->
-            view = new Textarea(realm: "europe", placeholder: "Blah")
-            view.render()
-            view.reveal("something")
+            view = create("something")
             expect(Textarea.active()).toBe true

         it "is active if content is typed in", ->
-            $("body").append('

')
-
-            view = new Textarea(realm: "europe", placeholder: "Blah")
-            view.setElement $("#fixture") # otherwise view disables itself because it's not :visible
-            view.render()
-            view.reveal("")
+            view = create()

             e = $.Event("keyup")
             e.which = 65 # A - we just want to trigger keyup once
@@ -31,4 +33,19 @@
             view.$("textarea").trigger e

             expect(Textarea.active()).toBe true
-            $("#fixture").remove()
+
+        describe "preview", ->
+            it "is not updated on reveal by default", ->
+                view = create("blah")
+                expect(view.$(".helper-textarea-preview ._content").html()).toEqual ""
+
+            it "is updated on reveal if preview is on", ->
+                view = create("blah")
+                view.$(".helper-textarea-show-preview").click()
+                expect(view.$(".helper-textarea-preview ._content").html()).toEqual "
blah
"
+
+            it "is cleared on clear()", ->
+                view = create("blah")
+                view.$(".helper-textarea-show-preview").click()
+                view.clear()
+                expect(view.$(".helper-textarea-preview ._content").html()).toEqual ""


The assemblyId for members must be number.

--- jobs/import-members.coffee
+++ jobs/import-members.coffee
@@ -68,7 +68,7 @@
   baseUri + member.lastName + member.firstName + assemblyId + ".jpg"

 persistMember = (session, member, db, promise) ->
-  assemblyIdForMember = member.Id
+  assemblyIdForMember = Number(member.Id)

   memberDetails =
     type: typeForDistrict(member.District),


Get rid of underscore

Get rid of underscore

--- jquery.transparency.coffee
+++ jquery.transparency.coffee
@@ -1,5 +1,3 @@
-_ = require "underscore"
-
 jQuery.fn.render = (data) ->
   data     = [data] unless jQuery.isArray(data)
   template = this
@@ -13,7 +11,7 @@
       attribute = tmp[1] if tmp.length > 1
       template.find(".#{klass}").each ->
         if attribute
-          jQuery(this).attr attribute, object[key]
+          jQuery(this).attr attribute, value
         else
-          jQuery(this).prepend object[key]
+          jQuery(this).prepend value
   return this


Keep last user code line visible by scrolling down (#303)

Also removing what appears to be math to dynamically expand coco editor vertically.

--- ozaria/site/views/play/level/tome/SpellView.coffee
+++ ozaria/site/views/play/level/tome/SpellView.coffee
@@ -281,7 +281,6 @@
           @lastBackspace = nowDate
           @ace.remove "left"

-
   hookACECustomBehavior: ->
     aceConfig = me.get('aceConfig') ? {}
     @ace.commands.on 'exec', (e) =>
@@ -594,7 +593,6 @@

   setThang: (thang) ->
     @focus()
-    @lastScreenLineCount = null
     @updateLines()
     return if thang.id is @thang?.id
     @thang = thang
@@ -627,39 +625,20 @@
     return if @destroyed
     lineCount = @aceDoc.getLength()
     lastLine = @aceDoc.$lines[lineCount - 1]
+    cursorPosition = @ace.getCursorPosition()
     if /\S/.test lastLine
-      cursorPosition = @ace.getCursorPosition()
       wasAtEnd = cursorPosition.row is lineCount - 1 and cursorPosition.column is lastLine.length
       @aceDoc.insertNewLine row: lineCount, column: 0  #lastLine.length
       @ace.navigateLeft(1) if wasAtEnd
       ++lineCount
       # Force the popup back
       @ace?.completer?.showPopup(@ace)
-    screenLineCount = @aceSession.getScreenLength()
-    if screenLineCount isnt @lastScreenLineCount
-      @lastScreenLineCount = screenLineCount
-      lineHeight = @ace.renderer.lineHeight or 20
-      tomeHeight = $('#tome-view').innerHeight()
-      spellTopBarHeight = $('#spell-top-bar-view').outerHeight()
-      spellToolbarHeight = $('.spell-toolbar-view').outerHeight()
-      @spellPaletteHeight ?= 75
-      spellPaletteAllowedHeight = Math.min @spellPaletteHeight, tomeHeight / 3
-      maxHeight = tomeHeight - spellTopBarHeight - spellToolbarHeight - spellPaletteAllowedHeight
-      minHeight = Math.max 8, (Math.min($("#canvas-wrapper").outerHeight(),$("#level-view").innerHeight() - 175) / lineHeight) - 2
-      linesAtMaxHeight = Math.floor(maxHeight / lineHeight)
-      lines = Math.max minHeight, Math.min(screenLineCount + 2, linesAtMaxHeight)
-      # 2 lines buffer is nice
-      @ace.setOptions minLines: lines, maxLines: lines
-      # Move spell palette up, slightly overlapping us.
-      newTop = 185 + lineHeight * lines
-      #spellPaletteView.css('top', newTop)
-      # Expand it to bottom of tome if too short.
-      #newHeight = Math.max @spellPaletteHeight, tomeHeight - newTop + 10
-      #spellPaletteView.css('height', newHeight) if @spellPaletteHeight isnt newHeight
+    # Ensure current user code line visible and not truncated at bottom of editor
+    if cursorPosition.row >= lineCount - 2
+      @ace.scrollToLine lineCount, true, true
     if @firstEntryToScrollLine? and @ace?.renderer?.$cursorLayer?.config
       @ace.scrollToLine @firstEntryToScrollLine, true, true
       @firstEntryToScrollLine = undefined
-

   hideProblemAlert: ->
     return if @destroyed
@@ -857,6 +836,7 @@
     isCast = isCast or not _.isEmpty(aether.metrics) or _.some aether.getAllProblems(), {type: 'runtime'}
     annotations = @aceSession.getAnnotations()

+    # NOTE: this has crazy side-effects via new Problem()
     newProblems = @convertAetherProblems(aether, aether.getAllProblems(), isCast)
     annotations.push problem.annotation for problem in newProblems when problem.annotation
     if isCast
@@ -1238,7 +1218,6 @@

   resize: ->
     @ace?.resize true
-    @lastScreenLineCount = null
     @updateLines()

   onChangeEditorConfig: (e) ->


More improvements in message grouping

More improvements in message grouping

--- packages/rocketchat-ui-message/message/message.coffee
+++ packages/rocketchat-ui-message/message/message.coffee
@@ -147,17 +147,18 @@
 			else
 				$nextNode.removeClass('new-day')

-			if nextDataset.username isnt currentDataset.username or parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
-				$nextNode.removeClass('sequential')
+			if nextDataset.groupable isnt 'false'
+				if nextDataset.username isnt currentDataset.username or parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
+					$nextNode.removeClass('sequential')
+				else
+					$nextNode.addClass('sequential')
+
+		if not nextNode?
+			templateInstance = view.parentView.parentView.parentView.parentView.parentView.templateInstance?()
+
+			if currentNode.classList.contains('own') is true
+				templateInstance?.atBottom = true
 			else
-				$nextNode.addClass('sequential')
-
-			if not nextNode?
-				templateInstance = view.parentView.parentView.parentView.parentView.parentView.templateInstance?()
-
-				if currentNode.classList.contains('own') is true
-					templateInstance?.atBottom = true
-				else
-					if templateInstance?.atBottom isnt true
-						newMessage = templateInstance?.find(".new-message")
-						newMessage?.className = "new-message"
+				if templateInstance?.atBottom isnt true
+					newMessage = templateInstance?.find(".new-message")
+					newMessage?.className = "new-message"


Handle multiple projects at once. Sort JSON output.

--- quidoc.coffee
+++ quidoc.coffee
@@ -39,11 +39,14 @@

   # Return the comments found for members defined in the CoffeeScript source text.
   controlComments: ( source ) ->
-    comments = {}
+    comments = null
     match = @regexComments.exec source
     while match != null
       [ full, commentBlock, identifier ] = match
-      comments[ identifier ] = @commentText commentBlock
+      commentText = @commentText commentBlock
+      if commentText.length > 0
+        comments = comments || {}
+        comments[ identifier ] = commentText
       match = @regexComments.exec source
     comments

@@ -134,7 +137,7 @@
   ".qui": markupDocsExtractor

-# Return the documentation for all CoffeeScript files below the given root.
+# Return the documentation for all files below the given root.
 projectDocs = ( root ) ->
   docs = {}
   walk root, ( filePath ) ->
@@ -143,9 +146,29 @@
     if extractor?
       source = fs.readFileSync filePath, "utf8"
       { className: className, comments: comments } = extractor.extract source
-      if className?
+      if className? and comments?
         docs[ className ] = comments
   docs
+
+
+# Return the documenation for all files below the given roots.
+projectsDocs = ( paths ) ->
+  docs = {}
+  for project in projects
+    root = path.resolve project
+    for own key, value of projectDocs root
+      docs[ key ] = value
+  sortByKeys docs
+
+
+# Return the given object with its keys sorted.
+sortByKeys = ( obj ) ->
+  keys = ( key for own key, value of obj )
+  sortedKeys = keys.sort()
+  sorted = {}
+  for key in sortedKeys
+    sorted[ key ] = obj[ key ]
+  sorted

 printDocs = ( docs ) ->
@@ -158,7 +181,8 @@

 # Main
-args = process.argv.splice 2 # Ignore "node" and script name args
-root = if args[0]? then path.resolve args[0] else process.cwd()
-docs = projectDocs root
+projects = process.argv.splice 2 # Ignore "node" and script name args
+if projects.length == 0
+  projects = [ process.cwd() ] # Handle current folder by default
+docs = projectsDocs projects
 printDocs docs


Chat server broadcast fix

Chat server broadcast fix

--- uchicagohvz/chat/server/server.coffee
+++ uchicagohvz/chat/server/server.coffee
@@ -33,7 +33,7 @@
 	broadcast: (data) ->
 		if data.room?
 			for conn in @lobby
-				if data.room in conn.userObject.rooms
+				if conn.userObject.rooms.indexOf(data.room) isnt -1
 					conn.writeJSON data

 	auth: (conn, authData) ->


fixed conflict with updated typeahead package

--- client/coffee/helpers.coffee
+++ client/coffee/helpers.coffee
@@ -50,16 +50,19 @@
 share.updateValues = (form, obj) ->
     updates = {}
     for inp in $(form).find("select,input,textarea")
-        val = getValue(inp)
         key = inp.name
-        if obj[key] isnt val then updates[key] = val
+        if key.length>0
+            val = getValue(inp)
+            if obj[key] isnt val then updates[key] = val
     return updates

 share.newValues = (form) ->
-  obj = {}
-  for inp in $(form).find("select,input,textarea")
-    obj[inp.name] = getValue(inp)
-  return obj
+    obj = {}
+    for inp in $(form).find("select,input,textarea")
+        key = inp.name
+        if key.length>0
+            obj[key] = getValue(inp)
+    return obj

 share.returnExcelFile = (raw_data, fn) ->
     fn = fn or "download.xlsx"


Routes: Heavily refactored the route handling for new sidebar changes.

Commented codes are also committed because of reference. I will make another commit to remove them.

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -6,117 +6,127 @@
 remote = require('app/remote').getInstance()
 Machine = require 'app/providers/machine'
 lazyrouter = require 'app/lazyrouter'
-
-loadWorkspace = (workspace, options) ->
-
-  {machine, machineLabel, username} = options
-
-  username or= nick()
-  machine  or= getMachine machineLabel, username
-
-  loadIDE { machine, workspace, username }
-
-findWorkspace = (options, callback) ->
-
-  {machineLabel, workspaceSlug, username} = options
-
-  kallback = (workspaces) ->
-
-    username ?= nick()
-
-    for machine in globals.userMachines
-      unless machine instanceof Machine
-        machine = new Machine machine: remote.revive machine
-
-      sameLabel = (machine.slug or machine.label) is machineLabel
-      sameOwner = machine.getOwner() is username
-
-      if sameLabel and sameOwner
-        machineUId = machine.uid
-        break
-
-    for workspace in workspaces
-      continue  unless workspace.machineUId is machineUId
-      continue  unless workspace.slug is workspaceSlug
-
-      return callback workspace
-
-    return callback null
-
-  if username
-  then filterWorkspacesByUsername username, kallback
-  else filterOwnWorkspaces kallback
-
-
-filterWorkspacesByUsername = (username, callback) ->
-
-  remote.cacheable username, (err, [account]) ->
-    if err
-      console.error err
-      callback []
-
-    originId = account.getId()
-
-    callback globals.userWorkspaces.filter (workspace) ->
-      originId is workspace.originId
-
-
-filterOwnWorkspaces = (callback) ->
-
-  callback globals.userWorkspaces.filter (workspace) ->
-    workspace.originId is whoami()._id
+dataProvider = require 'app/userenvironmentdataprovider'
+
+
+# loadWorkspace = (workspace, options) ->
+
+#   {machine, machineLabel, username} = options
+
+#   username or= nick()
+#   machine  or= getMachine machineLabel, username
+
+#   loadIDE { machine, workspace, username }
+
+
+# findWorkspace = (options, callback) ->
+
+#   {machineLabel, workspaceSlug, username} = options
+
+#   kallback = (workspaces) ->
+
+#     username ?= nick()
+
+#     for machine in globals.userMachines
+#       unless machine instanceof Machine
+#         machine = new Machine machine: remote.revive machine
+
+#       sameLabel = (machine.slug or machine.label) is machineLabel
+#       sameOwner = machine.getOwner() is username
+
+#       if sameLabel and sameOwner
+#         machineUId = machine.uid
+#         break
+
+#     for workspace in workspaces
+#       continue  unless workspace.machineUId is machineUId
+#       continue  unless workspace.slug is workspaceSlug
+
+#       return callback workspace
+
+#     return callback null
+
+#   if username
+#   then filterWorkspacesByUsername username, kallback
+#   else filterOwnWorkspaces kallback
+
+
+# filterWorkspacesByUsername = (username, callback) ->
+
+#   remote.cacheable username, (err, [account]) ->
+#     if err
+#       console.error err
+#       callback []
+
+#     originId = account.getId()
+
+#     callback globals.userWorkspaces.filter (workspace) ->
+#       originId is workspace.originId
+
+
+# filterOwnWorkspaces = (callback) ->
+
+#   callback globals.userWorkspaces.filter (workspace) ->
+#     workspace.originId is whoami()._id

 selectWorkspaceOnSidebar = (data) ->
+
   mainView = kd.getSingleton 'mainView'
   mainView.activitySidebar.selectWorkspace data

-getMachine = (label, username = nick()) ->
-
-  for m in globals.userMachines
-
-    unless m instanceof Machine
-      m = new Machine machine: remote.revive m
-
-    sameUser  = m.getOwner() is username
-    sameLabel = (m.slug or m.label) is label
-
-    return m  if sameLabel and sameUser
+# getMachine = (label, username = nick()) ->
+
+#   for m in globals.userMachines
+
+#     unless m instanceof Machine
+#       m = new Machine machine: remote.revive m
+
+#     sameUser  = m.getOwner() is username
+#     sameLabel = (m.slug or m.label) is label
+
+#     return m  if sameLabel and sameUser

 getLatestWorkspace = ->

-  storage = kd.getSingleton('localStorageController').storage 'IDE'
+  storage   = kd.getSingleton('localStorageController').storage 'IDE'
   workspace = storage.getValue 'LatestWorkspace'

   return  unless workspace

-  {machineLabel, workspaceSlug, channelId} = workspace  if workspace
-
-  return if checkWorkspace machineLabel, workspaceSlug, channelId
-  then workspace
-  else null
+  { machineLabel, workspaceSlug, channelId } = workspace
+
+  ws = null
+
+  if checkWorkspace machineLabel, workspaceSlug, channelId
+   ws = workspace
+
+  return ws

 checkWorkspace = (machineLabel, workspaceSlug, channelId) ->

-  for workspace in globals.userWorkspaces
-    sameLabel = workspace.machineLabel is machineLabel
-    sameSlug = workspace.slug is workspaceSlug
-    sameChannelId = not channelId or workspace.channelId is channelId
-
-    if sameLabel and sameSlug and sameChannelId
-      return workspace
+  return dataProvider.validateCollaborationWorkspace machineLabel, workspaceSlug, channelId
+
+  # for workspace in globals.userWorkspaces
+  #   sameLabel = workspace.machineLabel is machineLabel
+  #   sameSlug = workspace.slug is workspaceSlug
+  #   sameChannelId = not channelId or workspace.channelId is channelId
+
+  #   if sameLabel and sameSlug and sameChannelId
+  #     return workspace

 loadIDE = (data) ->

   { machine, workspace, username, channelId } = data
+  selectWorkspaceOnSidebar data

   appManager = kd.getSingleton 'appManager'
   ideApps    = appManager.appControllers.IDE
-  machineUId = machine?.uid
+  machineUId = machine.uid
   callback   = ->
     appManager.open 'IDE', { forceNew: yes }, (app) ->
       app.mountedMachineUId   = machineUId
@@ -131,7 +141,6 @@
         app.amIHost           = yes

       appManager.tell 'IDE', 'mountMachineByMachineUId', machineUId
-      selectWorkspaceOnSidebar data

   return callback()  unless ideApps?.instances

@@ -141,65 +150,108 @@

     if isSameMachine
       if isSameWorkspace then ideInstance = instance
+      # should not be the case anymore since 'my-workspace' deprecated.
       else if workspace.slug is 'my-workspace'
         if instance.workspaceData?.isDefault
           ideInstance = instance

   if ideInstance
     appManager.showInstance ideInstance
-    selectWorkspaceOnSidebar data
+    selectWorkspaceOnSidebar data # should not be required
   else
     callback()

 routeToFallback = ->

-  router = kd.getSingleton 'router'
-
-  for machine in globals.userMachines when machine.isMine()
-    return routeToMachineWorkspace machine
-
-  kd.singletons.computeController.fetchMachines (err, machines) ->
-
-    if err or not machines.length
-    then router.handleRoute "/IDE/koding-vm-0/my-workspace"
-    else routeToMachineWorkspace machines.first
+  machines = dataProvider.getMyMachines()
+  router   = kd.getSingleton 'router'
+  [ obj ]  = machines
+
+  if obj.machine
+    routeToMachineWorkspace obj.machine
+  else
+    router.handleRoute "/IDE/koding-vm-0/my-workspace"
+
+
+  # router = kd.getSingleton 'router'
+
+  # for machine in globals.userMachines when machine.isMine()
+  #   return routeToMachineWorkspace machine
+
+  # kd.singletons.computeController.fetchMachines (err, machines) ->
+
+  #   if err or not machines.length
+  #   then router.handleRoute "/IDE/koding-vm-0/my-workspace"
+  #   else routeToMachineWorkspace machines.first

 routeToMachineWorkspace = (machine) ->

-  if latestWorkspace = getLatestWorkspace()
-    {machineLabel} = latestWorkspace
+  latestWorkspace = getLatestWorkspace()
+  workspaceSlug   = 'my-workspace'
+
+  if latestWorkspace
+    { machineLabel } = latestWorkspace
     if machineLabel is machine.label
       workspaceSlug = latestWorkspace.workspaceSlug

-  workspaceSlug ?= 'my-workspace'
-
   kd.getSingleton('router').handleRoute "/IDE/#{machine.slug}/#{workspaceSlug}"

+  # if latestWorkspace = getLatestWorkspace()
+  #   {machineLabel} = latestWorkspace
+  #   if machineLabel is machine.label
+  #     workspaceSlug = latestWorkspace.workspaceSlug
+
+  # workspaceSlug ?= 'my-workspace'
+
+  # kd.getSingleton('router').handleRoute "/IDE/#{machine.slug}/#{workspaceSlug}"
+

 routeToLatestWorkspace = ->

-  router = kd.getSingleton 'router'
-
-  if latestWorkspace = getLatestWorkspace()
-    {machineLabel, workspaceSlug, channelId} = latestWorkspace
-  else
-    return routeToFallback()
+  router          = kd.getSingleton 'router'
+  latestWorkspace = getLatestWorkspace()
+
+  return routeToFallback()  unless latestWorkspace
+
+  { machineLabel, workspaceSlug, channelId } = latestWorkspace

   if channelId
-    for workspace in globals.userWorkspaces when workspace.channelId is channelId
-      return router.handleRoute "/IDE/#{channelId}"
-    routeToFallback()
+    dataProvider.getMachineAndWorkspaceByChannelId channelId, (machine, ws) =>
+      if machine and ws then router.handleRoute "/IDE/#{channelId}"
+      else routeToFallback()

   else if machineLabel and workspaceSlug
-    return routeToFallback()  unless machine = getMachine machineLabel
-
-    findWorkspace {workspaceSlug, machineLabel}, (workspace) ->
-
-      if workspace
-      then router.handleRoute "/IDE/#{machineLabel}/#{workspaceSlug}"
-      else routeToMachineWorkspace machine
+    dataProvider.getMachineByLabel machineLabel, (machine, workspace) =>
+      if machine and workspace
+        router.handleRoute "/IDE/#{machineLabel}/#{workspaceSlug}"
+      else if machine
+        routeToMachineWorkspace machine
+      else
+        routeToFallback()
+
+
+  # router = kd.getSingleton 'router'
+
+  # if latestWorkspace = getLatestWorkspace()
+  #   {machineLabel, workspaceSlug, channelId} = latestWorkspace
+  # else
+  #   return routeToFallback()
+
+  # if channelId
+  #   for workspace in globals.userWorkspaces when workspace.channelId is channelId
+  #     return router.handleRoute "/IDE/#{channelId}"
+  #   routeToFallback()
+
+  # else if machineLabel and workspaceSlug
+  #   return routeToFallback()  unless machine = getMachine machineLabel
+
+  #   findWorkspace {workspaceSlug, machineLabel}, (workspace) ->
+
+  #     if workspace
+  #     then router.handleRoute "/IDE/#{machineLabel}/#{workspaceSlug}"
+  #     else routeToMachineWorkspace machine

 loadCollaborativeIDE = (id) ->
@@ -210,68 +262,122 @@

     try

-      [workspace] = globals.userWorkspaces.filter (w) -> w.channelId is channel.id
-
-      return routeToLatestWorkspace()  unless workspace
-
-      [machine] = globals.userMachines.filter (m) -> m.uid is workspace.machineUId
-
-      query = socialApiId: channel.creatorId
-      remote.api.JAccount.some query, {}, (err, account) =>
-
-        return throw new Error err  if err
-
-        username  = account.first.profile.nickname
-        channelId = channel.id
-
-        return loadIDE { machine, workspace, username, channelId }
+      dataProvider.getMachineAndWorkspaceByChannelId id, (machine, workspace) =>
+        return routeToLatestWorkspace()  unless workspace
+
+        query = socialApiId: channel.creatorId
+
+        remote.api.JAccount.some query, {}, (err, account) =>
+          if err
+            routeToLatestWorkspace()
+            return throw new Error err
+
+          username  = account.first.profile.nickname
+          channelId = channel.id
+
+          return loadIDE { machine, workspace, username, channelId }

     catch e

-      console.error e
-      return routeToLatestWorkspace()
-
-
-refreshWorkspaces = (callback) ->
-
-  {mainView, computeController} = kd.singletons
-
-  computeController.ready ->
-    mainView.activitySidebar.fetchWorkspaces callback
+      routeToLatestWorkspace()
+      return console.error e
+
+
+  # kd.singletons.socialapi.cacheable 'channel', id, (err, channel) ->
+
+  #   return routeToLatestWorkspace()  if err
+
+  #   try
+
+  #     [workspace] = globals.userWorkspaces.filter (w) -> w.channelId is channel.id
+
+  #     return routeToLatestWorkspace()  unless workspace
+
+  #     [machine] = globals.userMachines.filter (m) -> m.uid is workspace.machineUId
+
+  #     query = socialApiId: channel.creatorId
+  #     remote.api.JAccount.some query, {}, (err, account) =>
+
+  #       return throw new Error err  if err
+
+  #       username  = account.first.profile.nickname
+  #       channelId = channel.id
+
+  #       return loadIDE { machine, workspace, username, channelId }
+
+  #   catch e
+
+  #     console.error e
+  #     return routeToLatestWorkspace()
+
+
+# refreshWorkspaces = (callback) ->
+
+#   {mainView, computeController} = kd.singletons
+
+#   computeController.ready ->
+#     mainView.activitySidebar.fetchWorkspaces callback
+

 module.exports = -> lazyrouter.bind 'ide', (type, info, state, path, ctx) ->

+  globals.userMachines = []
+  globals.userWorkspaces = []
+
   switch type
-    when 'home'
-      refreshWorkspaces -> routeToLatestWorkspace()
+
+    when 'home' then routeToLatestWorkspace()

     when 'machine'
-      {machineLabel} = info.params
+      { machineLabel } = info.params
+
       # we assume that if machineLabel is all numbers it is the channelId - SY
       if /^[0-9]+$/.test machineLabel
-        refreshWorkspaces -> loadCollaborativeIDE machineLabel
-      else if machine = getMachine machineLabel
-        routeToMachineWorkspace machine
+        loadCollaborativeIDE machineLabel
       else
-        routeToLatestWorkspace()
+        dataProvider.getMachineByLabel machineLabel, (machine) ->
+          if machine then routeToMachineWorkspace machine
+          else routeToLatestWorkspace()
+
+      # {machineLabel} = info.params
+      # # we assume that if machineLabel is all numbers it is the channelId - SY
+      # if /^[0-9]+$/.test machineLabel
+      #   refreshWorkspaces -> loadCollaborativeIDE machineLabel
+      # else if machine = getMachine machineLabel
+      #   routeToMachineWorkspace machine
+      # else
+      #   routeToLatestWorkspace()
+

     when 'workspace'
-      {params} = info
-      {machineLabel} = params
-      params.username = nick()
-
-      refreshWorkspaces ->
-
-        findWorkspace params, (workspace) ->
-
-          if workspace
-            return loadWorkspace workspace, params
-          else
-            for machine in globals.userMachines when machine.slug is machineLabel
-              break
-
-            if machine
-            then routeToMachineWorkspace machine
-            else loadWorkspace null, params
-
+      { params } = info
+      params.username = username = nick()
+
+      dataProvider.getMachineAndWorkspace params, (machine, workspace) ->
+
+        if machine and workspace
+          loadIDE { machine, workspace, username }
+        else if machine
+          routeToMachineWorkspace machine
+        else
           routeToLatestWorkspace()
+
+      # {params} = info
+      # {machineLabel} = params
+      # params.username = nick()
+
+      # refreshWorkspaces ->
+
+      #   findWorkspace params, (workspace) ->
+
+      #     if workspace
+      #       return loadWorkspace workspace, params
+      #     else
+      #       for machine in globals.userMachines when machine.slug is machineLabel
+      #         break
+
+      #       if machine
+      #       then routeToMachineWorkspace machine
+      #       else loadWorkspace null, params
+
+      #     routeToLatestWorkspace()


Don't assume window is the global scope.  Closes #119.

--- pace.coffee
+++ pace.coffee
@@ -174,7 +174,8 @@
         else
           i++

-window.Pace ?= {}
+Pace = window.Pace or {}
+window.Pace = Pace

 extend Pace, Evented::


- [grunt] read some data for `data` global object from `package.json`

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -61,8 +61,10 @@
         scripts: '/assets/scripts'
         thumbnails: '/assets/images/thumbnails'
       site:
-        name: 'Kotsu'
         lang: 'en'
+        name: '<%= pkg.name %>'
+        title: '<%= pkg.title %>'
+        version: '<%= pkg.version %>'
       data:
         currentYear: new Date().getFullYear()
         example: grunt.file.readJSON 'source/data/example.json'


Added template task + updated clean task + Split test and app sources

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -3,19 +3,23 @@
   grunt.initConfig
     pkg: grunt.file.readJSON 'package.json'
     date: grunt.template.today 'dd-mm-yyyy'
-    clean: ['dist']
+    clean:
+      dist: ['dist'],
+      test: ['test/**/*.js']
     coffee:
-      compile:
+      app:
         files: [{
           expand: true,
           cwd: 'src/'
           src: ['*.coffee'],
           dest: 'dist/',
           ext: '.js'
-        }, {
+        }]
+      test:
+        files: [{
           expand: true,
           cwd: 'test/',
-          src: ['*.coffee'],
+          src: ['**/*.coffee'],
           dest: 'test/',
           ext: '.js'
         }]
@@ -30,18 +34,34 @@
       options:
         reporter: 'spec'
       test:
-        src: ['test/**/*.js']
+        src: ['test/all/**/*.js', 'test/node/**/*.js']
     mocha:
       options:
         reporter: 'Spec'
         run: true
       all: ['test/browser/*.html']
     coffeelint:
-      app: ['src/*.coffee'],
-      tests: ['test/*.coffee'],
+      app: ['src/*.coffee']
+      tests: ['test/**/*.coffee']
       grunt: ['gruntfile.coffee']
+    template:
+      signature:
+        src: 'templates/browsertest.html'
+        dest: 'test/browser/signature.html'
+        engine: 'handlebars'
+        variables:
+          title: 'Signature'
+          script: '../all/signature.js'
+

   require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)

-  grunt.registerTask 'test', ['coffeelint', 'mochaTest', 'mocha']
-  grunt.registerTask 'default', 'Default task', ['coffee', 'uglify', 'test']+  grunt.registerTask 'test', [
+    'coffee:test'
+    'template'
+    'coffeelint'
+    'mochaTest'
+    'mocha'
+  ]
+
+  grunt.registerTask 'default', 'Default task', ['coffee:app', 'uglify', 'test']


Updating gruntfile header

Updating gruntfile header

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -36,7 +36,7 @@
           sourceMapIncludeSources: true
           drop_console: true
           banner: '/*! <%= pkg.name %> minified - v<%= pkg.version %> - ' +
-          '<%= grunt.template.today("yyyy-mm-dd") %> */' + "\n"
+          '<%= grunt.template.today("yyyy-mm-dd") %> */'
         files:
           'dist/moltin.builder.min.js': ['dist/moltin.js', 'dist/moltin.builder.js']
           'dist/moltin.min.js': 'dist/moltin.js'


Added Atoms.icons task

Added Atoms.icons task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -42,6 +42,8 @@
           'style/theme/organism.*.styl']
         templates: [
           'style/theme/template.*.styl']
+        icons: [
+          'bower_components/atoms_icons/*.styl']

     coffee:
       core: files: '<%=meta.build%>/<%=pkg.name%>.debug.js'         : '<%= source.core %>'
@@ -79,7 +81,9 @@
       templates:
         options: compress: false, import: [ '__init']
         files: '<%=meta.bower%>/<%=pkg.name%>.templates.css': '<%=source.stylus.templates%>'
-
+      icons:
+        options: compress: false
+        files: '<%=meta.bower%>_icons/<%=pkg.name%>.icons.css': '<%=source.stylus.icons%>'

     watch:
       core:
@@ -106,6 +110,9 @@
       stylus_templates:
         files: ['<%= source.stylus.templates %>']
         tasks: ['stylus:templates']
+      stylus_icons:
+        files: ['<%= source.stylus.icons %>']
+        tasks: ['stylus:icons']

   grunt.loadNpmTasks 'grunt-contrib-coffee'
   grunt.loadNpmTasks 'grunt-contrib-concat'


add todo

add todo

--- static/coffee/testutils.coffee
+++ static/coffee/testutils.coffee
@@ -312,7 +312,7 @@
       glyph.set(defaults)
       glyphs.push(glyph)
     plot_model = Collections('Plot').create(
-      x_range: xrange
+      x_range: xrange # TODO .ref() fails?
       y_range: yrange
       canvas_width: dims[0]
       canvas_height: dims[1]


:gift:  new keymap sugar when has-occurrence for visible area

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -351,6 +351,7 @@
 'atom-text-editor.vim-mode-plus.operator-pending-mode.has-occurrence':
   'p': 'vim-mode-plus:inner-paragraph'
   'r': 'vim-mode-plus:a-persistent-selection'
+  'v': 'vim-mode-plus:inner-visible-area'
   'f': 'vim-mode-plus:a-function'
   'l': 'vim-mode-plus:inner-current-line'
   'z': 'vim-mode-plus:a-fold'


update gulp help text

update gulp help text

--- gulp/help.coffee
+++ gulp/help.coffee
@@ -11,14 +11,6 @@
 		title ' TENPLATE '
 		header 'usage: '
 		'gulp [watch|build|clean] [options]'
-		''
-		header 'while gulp clean: '
-		'--all    clean all'
-		''
-		header 'while gulp build/watch: '
-		'--dev              set __DEV__ to true'
-		'--nodev            set __DEV__ to false'
-		''
 	].join '\n'
 	console.log text
 	return cb()


adding $ruleElement to hideShowQuestions to make that relative to the current rule questions, instead of accidentally hideShow all matches based on questionId. This comes up on repeater/multiple steps

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -15,7 +15,7 @@
         condition = this
         #console.log condition
         questionId = condition.question_id
-        $triggerContainer = $ruleElement.prev("[data-question-id=" + questionId + "]")
+        $triggerContainer = $ruleElement.closest('.panel').find("[data-question-id=" + questionId + "]")
         # getting element on edit and new page
         $triggerElement = $triggerContainer.find(".form-control")
         # getting element on show page
@@ -26,28 +26,21 @@
         ancestorId = rule.question_id
         # text, textarea, select
         if $triggerElement.length < 2
-          #self.setDefaultState(rule.question_id, $triggerElement, condition.operator, condition.answer)
-          self.hideShowQuestions(hideQuestions, ancestorId)
+          self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
           self.bindConditions(rule.question_id, $triggerElement, condition.operator, condition.answer)
         # radio buttons
         else
           if $triggerElement.is(":checkbox")
             # limit binding of each checkbox if data-label-value and answer are the same-kdh
             $triggerElement = $triggerContainer.find(".form-control[data-label-value=" + condition.answer + "]")
-            self.hideShowQuestions(hideQuestions, ancestorId)
+            self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
             self.bindConditions(rule.question_id, $triggerElement, condition.operator, condition.answer)
           else
             for element in $triggerElement
               do (element) ->
-                #self.setDefaultState(rule.question_id, $(element), condition.operator, condition.answer)
-                self.hideShowQuestions(hideQuestions, ancestorId)
+                self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
                 self.bindConditions(rule.question_id, $(element), condition.operator, condition.answer)
-
     return
-
-  # set the default hide show conditions
-  setDefaultState: (ancestor_id, $triggerElement, operator, answer) ->
-    self.hideShowQuestions(self.evaluateCondition(operator, answer, self.getValue($triggerElement)), ancestor_id)

   #bind conditions to question
   bindConditions: (ancestor_id, $triggerElement, operator, answer) ->
@@ -72,22 +65,22 @@
             # match type any (or)
             if matchType == "any"
               if _.indexOf(conditionMetTracker, true) > 0
-                self.hideShowQuestions(false, ancestorId)
+                self.hideShowQuestions(false, ancestorId, $ruleElement)
               else
-                self.hideShowQuestions(true, ancestorId)
+                self.hideShowQuestions(true, ancestorId, $ruleElement)
             # match type all
             if matchType == "all"
               if _.indexOf(conditionMetTracker, false) == -1
-                self.hideShowQuestions(false, ancestorId)
+                self.hideShowQuestions(false, ancestorId, $ruleElement)
               else
-                self.hideShowQuestions(true, ancestorId)
+                self.hideShowQuestions(true, ancestorId, $ruleElement)
           else
-            self.hideShowQuestions(self.evaluateCondition(operator, answer, self.getValue($triggerElement)), ancestor_id)
+            self.hideShowQuestions(self.evaluateCondition(operator, answer, self.getValue($triggerElement)), ancestor_id, $ruleElement)
     return

   #hide or show questions
-  hideShowQuestions: (hide_questions, ancestor_id) ->
-    container = $("[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]").closest(".form-entry-item-container")
+  hideShowQuestions: (hide_questions, ancestor_id, $ruleElement) ->
+    container = $ruleElement.closest('.panel').find("[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]")
     if hide_questions
       container.addClass("conditional-logic-hidden")
       self.clearQuestions(container)


Fix merging of participation and page views

the binning of page views and participation from the api into dates is
fixed.

closes ADMIN-1299

note: there was a change in this file from binning by day -> hour to fix
a bug that looks like was a timezone issue. See CNVS-16464 and
https://gerrit.instructure.com/#/c/44111/.  I believe this sort of
addressed the issue at the time, but was not correct.

test plan:
  - have a course with a student with page views and participation data
    on the same day (you could run @jeremy's fake_analytics.rb script to
    generate randomish data and tweak the results to get dupes)
  - go to /courses/:couse_id/analytics/users/:user_id
  - click the toggle to switch to table view
  > expect the activity by date table to have combined the views and
    participation into the same date. There should be no duplicate
    dates in the table.

Change-Id: Iff76a43c0a7c0a1d3dd5082a6bbde18407916f32
Reviewed-on: https://gerrit.instructure.com/161479
Tested-by: Jenkins
Reviewed-by: Mysti Sadler <431d062aff1e8f25416b3a7b732fe6effdb03f9a@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Reviewed-by: Carl Kibler <7c2b40129dd885a147d5b2dda56dda016b9a8f57@instructure.com>
QA-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Product-Review: Ed Schiebel <0ca8a83847a5889cf05cb219c0ab09f6b646243b@instructure.com>

--- app/coffeescripts/StudentInCourse/ParticipationData.coffee
+++ app/coffeescripts/StudentInCourse/ParticipationData.coffee
@@ -29,15 +29,15 @@

       # sort the page view data to the appropriate bins
       for date, views of data.page_views
-        # this date is the hour for the bin
+        # this date is the day for the bin
         bin = binFor(Date.parse date)
         bin.views += views

       # sort the participation date to the appropriate bins
       for event in data.participations
         event.createdAt = Date.parse event.created_at
-        # bin to the hour corresponding to event.createdAt, so that all
+        # bin to the day corresponding to event.createdAt, so that all
         # participations fall in the same bin as their respective page views.
-        bin = binFor(tz.parse(tz.format(event.createdAt, '%F %H:00:00')))
+        bin = binFor(new Date(event.createdAt.setHours(0,0,0,0)))
         bin.participation_events.push event
         bin.participations += 1


don't rely on entry id ordering on unthreaded discussion show page

fixes CNVS-17898

test plan
- have a discussion topic without threaded replies with a root
 entry that has a higher db id than one of its child entries (this
 requires fiddling around in the console)
- view the discussion topic show page
- ensure that the entries display correctly and no js error report
 is generated

Change-Id: I9a9238366ed15803b43b6d70e7bb8cd5adc93cbc
Reviewed-on: https://gerrit.instructure.com/46903
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Mark Severson <cdf1f90aee9d0d073a2a24141ebc71887f3ba533@instructure.com>
QA-Review: Steven Shepherd <e794458448443812fb2685675aab3bbd1a3891bc@instructure.com>
Product-Review: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>

--- app/coffeescripts/models/SideCommentDiscussionTopic.coffee
+++ app/coffeescripts/models/SideCommentDiscussionTopic.coffee
@@ -13,19 +13,19 @@
     parse: ->
       super
       flat = {}
-      for id, entry of @flattened
+      for id, entry of @flattened when not entry.root_entry_id
+        flat[entry.id] = entry
+        entry.replies = []
+
+      for id, entry of @flattened when entry.root_entry_id
         delete entry.replies
-        if entry.root_entry_id?
-          parent = flat[entry.root_entry_id]
-          parent.replies.push entry
-          entry.parent = parent
-          entry.parent_id = parent.id
-        else
-          flat[entry.id] = entry
-          entry.replies = []
+        parent = flat[entry.root_entry_id]
+        parent.replies.push entry
+        entry.parent = parent
+        entry.parent_id = parent.id
+
       @data.entries = for id, entry of flat
         entry.replies.sort (a, b) ->
           Date.parse(b.created_at) - Date.parse(a.created_at)
         entry
       @data
-


Simplify the file upload POST location

Simplify the file upload POST location

--- lib/layervault/nodes/file.coffee
+++ lib/layervault/nodes/file.coffee
@@ -37,14 +37,9 @@
         return @callback(err, null) if err

         location = resp.headers.location
-        parsedUrl = url.parse(location, true)
-        parsedUrl.query.access_token = @api.config.accessToken
-        parsedUrl.query.file_size = fs.statSync(@localPath).size
-
-        delete parsedUrl.search
-        parsedUrl = url.format(parsedUrl)
+        location += "&access_token=#{@api.config.accessToken}"

-        needle.post parsedUrl, {headers: {'Authorization': @api.config.accessToken}}, (err, resp, body) =>
+        needle.post location, {headers: {'Authorization': @api.config.accessToken}}, (err, resp, body) =>
           delete @localPath
           delete @contentType
           @callback(err, body)


Fix node PNG export

Fix node PNG export

--- lib/psd/image_exports/png.coffee
+++ lib/psd/image_exports/png.coffee
@@ -4,13 +4,11 @@

 module.exports =
   toPng: ->
-    return @png if @png
+    new RSVP.Promise (resolve, reject) =>
+      png = new Png(new Buffer(@pixelData), @width(), @height(), 'rgba')

-    @png = new Png(new Buffer(@pixelData), @width(), @height(), 'rgba')
-
-    new RSVP.Promise (resolve, reject) =>
       console.log "Encoding!"
-      @png.encode(resolve)
+      png.encode(resolve)

   saveAsPng: (output) ->
     new RSVP.Promise (resolve, reject) =>


improve tests for independent observables created while dispatching

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1703,59 +1703,40 @@
     expect(values).to.deep.equal(["foo"])

 describe "independent observables created while dispatching", ->
+  verifyIndependent = (f, expected) ->
+    values = []
+    Bacon.once(1).onValue ->
+      f().onValue (value) ->
+        values.push(value)
+      expect(values).to.deep.equal(expected)
+    expect(values).to.deep.equal(expected)
   it "with combineAsArray", ->
-    calls = 0
-    Bacon.once(1).onValue ->
-      Bacon.combineAsArray([Bacon.constant(1)]).onValue ->
-        calls++
-    expect(calls).to.equal(1)
+    verifyIndependent (-> Bacon.combineAsArray([Bacon.constant(1)])), [[1]]
   it "with combineAsArray.startWith", ->
-    result = null
-    Bacon.once(1).onValue ->
+    verifyIndependent (->
       a = Bacon.constant("lolbal")
-      s = Bacon.combineAsArray([a, a]).map("right").startWith("wrong");
-      s.onValue((x) -> result = x)
-    expect(result).to.equal("right")
+      Bacon.combineAsArray([a, a]).map("right").startWith("wrong")), ["right"]
   it "with stream.startWith", ->
-    result = null
-    Bacon.once(1).onValue ->
-      s = Bacon.later(1).startWith(0)
-      s.onValue((x) -> result = x)
-    expect(result).to.equal(0)
+    verifyIndependent (-> Bacon.later(1).startWith(0)), [0]
   it "with combineAsArray.changes.startWith", ->
-    result = null
-    Bacon.once(1).onValue ->
+    verifyIndependent (->
       a = Bacon.constant("lolbal")
-      s = Bacon.combineAsArray([a, a]).changes().startWith("right")
-      s.onValue((x) -> result = x)
-    expect(result).to.equal("right")
+      Bacon.combineAsArray([a, a]).changes().startWith("right")), ["right"]
   it "with flatMap", ->
-    result = null
-    Bacon.once(1).onValue ->
+    verifyIndependent (->
       a = Bacon.constant("lolbal")
-      s = a.flatMap((x) -> Bacon.once(x))
-      s.onValue((x) -> result = x)
-    expect(result).to.equal("lolbal")
+      a.flatMap((x) -> Bacon.once(x))), ["lolbal"]
   it "with awaiting", ->
-    result = null
-    Bacon.once(1).onValue ->
+    verifyIndependent (->
       a = Bacon.constant(1)
-      s = a.awaiting(a.map(->))
-      s.onValue((x) -> result = x)
-    expect(result).to.equal(true)
+      s = a.awaiting(a.map(->))), [true]
   it "with concat", ->
-    result = []
-    Bacon.once(1).onValue ->
-      s = Bacon.once(1).concat(Bacon.once(2))
-      s.onValue((x) -> result.push(x))
-    expect(result).to.deep.equal([1,2])
+    verifyIndependent (->
+      s = Bacon.once(1).concat(Bacon.once(2))), [1,2]
   it "with Property.delay", ->
-    result = []
-    Bacon.once(1).onValue ->
+    verifyIndependent (->
       c = Bacon.constant(1)
-      s = Bacon.combineAsArray([c, c]).delay(1).map(".0")
-      s.onValue((x) -> result.push(x))
-    expect(result).to.deep.equal([1])
+      Bacon.combineAsArray([c, c]).delay(1).map(".0")), [1]

 describe "when subscribing within the while dispatching", ->
   describe "single subscriber", ->


Modified ModelSpec since many of those tests are now relation tests, which are much more detailed anyway

--- spec/ModelSpec.coffee
+++ spec/ModelSpec.coffee
@@ -34,25 +34,6 @@
 		expect(model.get "id").toBe undefined
 		expect(model.get "foo").toBe "foo"
 		expect(model.get "bar").toBe "bar"
-
-	describe "default values", ->
-		class Test extends Model
-			fields:
-				foo:
-					"default": "FOO"
-				bar:
-					"default": "BAR"
-
-		it "should be applied when creating the model", ->
-			model = new Test
-			expect(model.get "foo").toBe "FOO"
-			expect(model.get "bar").toBe "BAR"
-
-		it "should be overridden by initially supplied values", ->
-			model = new Test
-				bar: "other"
-			expect(model.get "foo").toBe "FOO"
-			expect(model.get "bar").toBe "other"

 	it "should copy values when setting a whole model", ->
 		other = new Model
@@ -87,6 +68,25 @@
 			i++
 		expect(i).toBe 2

+	describe "default values", ->
+		class Test extends Model
+			fields:
+				foo:
+					"default": "FOO"
+				bar:
+					"default": "BAR"
+
+		it "should be applied when creating the model", ->
+			model = new Test
+			expect(model.get "foo").toBe "FOO"
+			expect(model.get "bar").toBe "BAR"
+
+		it "should be overridden by initially supplied values", ->
+			model = new Test
+				bar: "other"
+			expect(model.get "foo").toBe "FOO"
+			expect(model.get "bar").toBe "other"
+
 	describe "change events", ->
 		change = null
 		changeEvent = null
@@ -215,13 +215,14 @@
 				# Verify save was called correctly on the persistor.
 				saveCall = save.getCall 0
 				expect(saveCall.args[0]).toBe model
-				# Verify callback.
+				# Verify callback was supplied no error and the saved model.
 				doneCall = done.getCall 0
 				expect(doneCall.args[0]).toBe null # error
 				expect(doneCall.args[1]).toBe model

 	describe "relations", ->
 		class RelationalModel extends Model
+			persistor: RepoPersistor
 			fields:
 				foo:
 					relation: "HasOne"
@@ -229,95 +230,55 @@
 					relation: "HasMany"

 		beforeEach ->
-			model = new RelationalModel
-
-		it "should return relation definitions", ->
-			foo = model._getRelation "foo"
-			bar = model._getRelation "bar"
+			model = new RelationalModel id:99
+
+		it "should return relation handlers", ->
+			foo = model.getRelation "foo"
+			bar = model.getRelation "bar"
 			expect(typeof foo).toBe "object"
 			expect(typeof bar).toBe "object"
 			expect(foo instanceof HasOne).toBe true
 			expect(bar instanceof HasMany).toBe true

-	xdescribe "relations", ->
-		class RelationalModel extends Model
-			persistor: TestPersistor
-			relations:
-				foo:
-					model: Model
-				bar:
-					collection: Collection
-					model: Model
-		model = null
-		foo = null
-		bar = [null,null,null]
-		beforeEach ->
-			foo = new Model
-			foo.id "foo"
-			bar[0] = new Model
-			bar[1] = new Model
-			bar[2] = new Model
-			bar[0].id "bar:0"
-			bar[1].id "bar:1"
-			bar[2].id "bar:2"
-			TestPersistor.add foo, bar
-			model = new RelationalModel
-			model.set
-				foo: foo
-				bar: new Collection bar
-
-		afterEach ->
-			TestPersistor.reset()
-
-		it "should not fetch anything if all relations are loaded", ->
-			load = sinon.spy model.getPersistor(), "load"
-			done = sinon.spy()
-			model.fetch done
-			waitsFor (-> done.called), "Done never called", 100
-			runs ->
-				expect(load.callCount).toBe 0
-				call = done.getCall 0
-				expect(call.args[0]).toBe null
-
-		it "should serialize relations to ID", ->
+		it "should serialize to IDs", ->
+			model.getRelation("foo").set new Model id:1
+			model.getRelation("bar").add new Model id:1
+			model.getRelation("bar").add new Model id:2
 			serial = model.serialize()
-			expect(serial.foo).toBe "foo"
-			expect(JSON.stringify(serial.bar)).toBe JSON.stringify(["bar:0","bar:1","bar:2"])
-
-		it "should contain the raw IDs when deserializing", ->
-			model = new RelationalModel model.serialize()
-			expect(model.get "foo").toBe "foo"
-			expect(JSON.stringify(model.get "bar")).toBe JSON.stringify(["bar:0","bar:1","bar:2"])
-
-		it "should fetch all relations", ->
-			model = new RelationalModel model.serialize()
-			#load = sinon.spy model.getPersistor(), "load"
-			done = sinon.spy()
-			model.fetch done
-			waitsFor (-> done.called), "Done never called", 100
-			runs ->
-				#expect(load.callCount).toBe 4
-				expect(model.get "foo").toBe foo
-				collection = model.get "bar"
-				expect(collection.size()).toBe 3
-				expect(collection.get 0).toBe bar[0]
-				expect(collection.get 1).toBe bar[1]
-				expect(collection.get 2).toBe bar[2]
-
-#		it "should complain if model relations do not have an ID when saving", ->
-#			done = sinon.spy()
-#			model.set "foo", new Model
-#			model.save done
-#			waitsFor (-> done.called), "Done never called", 100
-#			runs ->
-#				err = done.getCall(0).args[0]
-#				expect(err.message).toBe "Relation \"foo\" does not have an ID when saving model"
-
-		it "should complain if collection relation entries do not have an ID when saving", ->
-			done = sinon.spy()
-			model.get("bar").add new Model
-			model.save done
-#			waitsFor (-> done.called), "Done never called", 100
-			#runs ->
-			#	err = done.getCall(0).args[0]
-			#	expect(err.message).toBe "Entry in collection relation \"bar\" does not have an ID when saving model"
+			expect(serial.foo).toBe 1
+			expect(serial.bar[0]).toBe 1
+			expect(serial.bar[1]).toBe 2
+
+		describe "persistence", ->
+			m1 = new Model id:1
+			m2 = new Model id:2
+			m3 = new Model id:3
+			done = null
+
+			beforeEach ->
+				done = sinon.spy()
+
+			it "should load through the persistor", ->
+				RepoPersistor.add m1, m2, m3
+				model.set "foo", 1
+				model.set "bar", [2,3]
+				model.fetchRelations done
+				waitsFor (-> done.called), "Done never called", 100
+				runs ->
+					expect(done.callCount).toBe 1
+					expect(model.get "foo").toBe m1
+					bar = model.get "bar"
+					expect(bar.get 0).toBe m2
+					expect(bar.get 1).toBe m3
+
+			it "should save through the persistor", ->
+				model.set "foo", m1
+				model.set "bar", [m1, m2]
+				model.save done
+				waitsFor (-> done.called), "Done never called", 100
+				runs ->
+					expect(done.callCount).toBe 1
+					repo = model.getPersistor().getRepo()
+					expect(repo.get 1).toBe m1
+					expect(repo.get 2).toBe m2
+					expect(repo.get 3).toBe m3


Fix settings logic.

Fix settings logic.

--- services/web/public/coffee/ide/FeatureOnboardingController.coffee
+++ services/web/public/coffee/ide/FeatureOnboardingController.coffee
@@ -6,12 +6,12 @@

 		$scope.turnCodeCheckOn = () ->
 			settings.saveSettings({ syntaxValidation: true })
-			scope.settings.syntaxValidation = true
+			$scope.settings.syntaxValidation = true
 			navToInnerStep2()

-		$scope.turnCodeCheckOn = () ->
+		$scope.turnCodeCheckOff = () ->
 			settings.saveSettings({ syntaxValidation: false })
-			scope.settings.syntaxValidation = false
+			$scope.settings.syntaxValidation = false
 			navToInnerStep2()

 		$scope.dismiss = () ->


:art: simplify test for `list --enabled`

--- spec/list-spec.coffee
+++ spec/list-spec.coffee
@@ -91,23 +91,9 @@
         expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0 (disabled)'

     it 'displays only enabled packages when --enabled is called', ->
-      atomPackages =
-        'test-module':
-          metadata:
-            name: 'test-module'
-            version: '1.0.0'
-        'test2-module':
-          metadata:
-            name: 'test2-module'
-            version: '1.0.0'
-
-      fs.writeFileSync(path.join(resourcePath, 'package.json'), JSON.stringify(_atomPackages: atomPackages))
-
       listPackages ['--enabled'], ->
-        expect(console.log.argsForCall[1][0]).toContain 'test2-module@1.0.0'
-        expect(console.log.argsForCall[4][0]).toContain 'dev-package@1.0.0'
-        expect(console.log.argsForCall[7][0]).toContain 'user-package@1.0.0'
-        expect(console.log.argsForCall[10][0]).toContain 'git-package@1.0.0'
+        expect(console.log.argsForCall[7][0]).toMatch /user-package@1\.0\.0$/
+        expect(console.log.argsForCall.toString()).not.toContain ['test-module']

   it 'lists packages in json format when --json is passed', ->
     listPackages ['--json'], ->


extend to .tree-view scope

extend to .tree-view scope

--- keymaps/atomizr.cson
+++ keymaps/atomizr.cson
@@ -1,9 +1,9 @@
-'.platform-darwin atom-text-editor':
+'.platform-darwin atom-text-editor, .platform-darwin .tree-view':
   'ctrl-c ctrl-c': 'atomizr:automatic-conversion'
   'ctrl-a ctrl-a': 'atomizr:toggle-atom-snippet-format'
   'ctrl-a ctrl-s': 'atomizr:convert-atom-to-sublime-text'
   'ctrl-s ctrl-a': 'atomizr:convert-sublime-text-to-atom'
-'.platform-linux atom-text-editor, .platform-win32 atom-text-editor':
+'.platform-linux atom-text-editor, .platform-linux .tree-view, .platform-win32 atom-text-editor, .platform-win32 .tree-view':
   'ctrl-alt-c ctrl-alt-c': 'atomizr:automatic-conversion'
   'ctrl-alt-a ctrl-alt-a': 'atomizr:toggle-atom-snippet-format'
   'ctrl-alt-a ctrl-alt-s': 'atomizr:convert-atom-to-sublime-text'


Only shift Article if required

Only shift Article if required

When moving from paragraph to paragraph with commenting open, we were
shifting the article further and further, this guards the shifting to
only occur if needed.

--- templates/embed/Article.coffee
+++ templates/embed/Article.coffee
@@ -22,8 +22,10 @@

   bindEvents: ->
     @element.addEventListener 'commenting', (event) =>
-      Carnival.addClass(@element, 'commenting')
-      @shiftArticle()
+      unless @element.classList.contains('commenting')
+        Carnival.addClass(@element, 'commenting')
+        @shiftArticle()
+
       @thread.displayForBlock(event.detail)
     @element.addEventListener 'doneCommenting', =>
       if @element.querySelectorAll('.commenting').length is 0