replace param lists by structures; remove hidden strings

--- intervals.coffee
+++ intervals.coffee
@@ -1,13 +1,7 @@
 fs = require('fs')
 Canvas = require('canvas')

-show_hidden_strings = false  # render invisible CF (between G and B)
-
 strings = [1..6]
-hidden_strings = []
-if show_hidden_strings
-  strings = [1..12]
-  hidden_strings = [3..8]
 string_count = strings.length

 frets = [0..4]  # includes nut
@@ -37,9 +31,6 @@
 ctx = null

 string_note_numbers = ((string_count - n) * 5 for n in strings)
-unless show_hidden_strings
-  string_note_numbers[0] -= 1
-  string_note_numbers[1] -= 1

 intervals = ['P8', 'm2', 'M2', 'm3', 'M3', 'P4', 'TT', 'P5', 'm6', 'M6', 'm7', 'M7']
 long_intervals_names = [
@@ -57,7 +48,6 @@
     ctx.moveTo h_gutter, y
     ctx.lineTo h_gutter + fret_count * fret_width + fret_overhang, y
     ctx.lineWidth = 1
-    ctx.lineWidth = 0.5 if hidden_strings.indexOf(n) >= 0
     ctx.stroke()

 draw_frets = ->
@@ -70,14 +60,15 @@
     ctx.stroke()
     ctx.lineWidth = 1

-draw_note = (string_number, fret_number, root, color) ->
-  x = h_gutter + (fret_number - 1) * fret_width + fret_width / 2
-  x = h_gutter if fret_number == 0
+draw_fingering = ({string, fret}, options) ->
+  {is_root, color} = options || {}
+  x = h_gutter + (fret - 1) * fret_width + fret_width / 2
+  x = h_gutter if fret == 0
   ctx.beginPath()
-  ctx.arc x, v_gutter + (string_number - 1) * string_spacing, 7, 0, 2 * Math.PI, false
+  ctx.arc x, v_gutter + (string - 1) * string_spacing, 7, 0, 2 * Math.PI, false
   ctx.fillStyle = 'red'
-  ctx.fillStyle = color or 'white' unless root
-  ctx.lineWidth = 2 unless root
+  ctx.fillStyle = color or 'white' unless is_root
+  ctx.lineWidth = 2 unless is_root
   ctx.fill()
   ctx.stroke()
   ctx.strokeStyle = 'black'
@@ -102,40 +93,39 @@
       ctx.stroke()
       ctx.lineWidth = 1

-  draw_note = (string_number, fret_number, root, color) ->
-    y = v_gutter + (fret_number - 1) * fret_height + fret_height / 2
-    ctx.fillStyle = color or (if root then 'red' else 'white')
-    ctx.strokeStyle = color or 'black'
+  draw_fingering = ({string, fret}, options) ->
+    {is_root, color} = options || {}
+    y = v_gutter + (fret - 1) * fret_height + fret_height / 2
+    ctx.fillStyle = color or (if is_root then 'red' else 'white')
+    ctx.strokeStyle = color or (if is_root then 'red' else 'black')
     ctx.lineWidth = 1
     ctx.beginPath()
-    ctx.arc h_gutter + (6 - string_number) * string_spacing, y, note_radius, 0, 2 * Math.PI, false
-    ctx.fill() if fret_number
+    ctx.arc h_gutter + (6 - string) * string_spacing, y, note_radius, 0, 2 * Math.PI, false
+    ctx.fill() if fret or is_root
     ctx.stroke()
     ctx.strokeStyle = 'black'
-

 draw_fingerboard = (fingerings) ->
   draw_strings()
   draw_frets()
   if fingerings
-    fretted_strings = (false for _ in [0..string_number])
-    for {string, fret, root, color} in fingerings
-      fretted_strings[string] = true
-      draw_note string, fret, root, color
-    for is_fretted, string_number in fretted_strings
-      continue if is_fretted or string_number < 1
+    fretted_strings = []
+    for fingering in fingerings
+      fretted_strings[fingering.string] = true
+      draw_fingering fingering, fingering
+    for string_number in strings
+      continue if fretted_strings[string_number]
       ctx.font = '4pt Helvetica'
       ctx.fillStyle = 'black'
       ctx.fillText "x", h_gutter + (6 - string_number) * string_spacing - 1, v_gutter - 2.5

-
-note_number_at = (string, fret) ->
+fingering_note_number = ({string, fret}) ->
   string_note_numbers[string - 1] + fret

-fingerings_each = (fn) ->
+finger_positions_each = (fn) ->
   for string in strings
     for fret in frets
-      fn string, fret
+      fn string: string, fret: fret

 save_canvas_to_png = (canvas, fname) ->
   out = fs.createWriteStream(build_directory + fname)
@@ -143,29 +133,28 @@
   stream.on 'data', (chunk) -> out.write(chunk)
   stream.on 'end', () -> console.log "Saved #{fname}"

-intervals_from = (root_string, root_fret, semitones) ->
-  root_note_number = note_number_at(root_string, root_fret)
+intervals_from = (fingering, semitones) ->
+  root_note_number = fingering_note_number(fingering)
   fingerings = []
-  for string in strings
-    for fret in frets
-      continue unless (note_number_at(string, fret) - root_note_number + 120) % 12 == semitones
-      fingerings.push string: string, fret: fret
+  finger_positions_each (fingering) ->
+    return unless (fingering_note_number(fingering) - root_note_number + 120) % 12 == semitones
+    fingerings.push fingering
   return fingerings

-draw_intervals_from = (semitones, root_string, root_fret, color) ->
-  root_note_number = note_number_at(root_string, root_fret)
-  draw_note root_string, root_fret, true, color
-  for {string, fret} in intervals_from(root_string, root_fret, semitones)
-    continue if string == root_string and fret == root_fret
-    draw_note string, fret, false, color
+draw_intervals_from = (root_fingering, semitones, color) ->
+  root_note_number = fingering_note_number(root_fingering)
+  draw_fingering root_fingering, is_root: true #, color: color
+  for fingering in intervals_from(root_fingering, semitones)
+    continue if fingering.string == root_fingering.string and fingering.fret == root_fingering.fret
+    draw_fingering fingering, color: color

 interval_cards = ->
-  fingerings_each (string, fret) ->
+  finger_positions_each (string, fret) ->
     canvas = new Canvas(padded_fretboard_width, padded_fretboard_height)
     ctx = canvas.getContext('2d')
     erase_background()
     draw_fingerboard()
-    draw_note string, fret
+    draw_note string_number: string, fret_number: fret
     filename = "#{string}-#{fret}.png"
     save_canvas_to_png canvas, filename

@@ -179,100 +168,58 @@
       filename = "#{string}-#{fret}-#{interval_long_name}.png"
       save_canvas_to_png canvas, filename

-intervals_from_note_sheet = (string, fret, pdf) ->
+intervals_from_position_page = (fingering) ->
   canvas_gutter = 20
-  header_height = 40
-  canvas_width = padded_fretboard_width * 3 + canvas_gutter * 3
-  canvas_height = (padded_fretboard_height + header_height) * 4 + canvas_gutter * 3
-
-  canvas = new Canvas(canvas_width, canvas_height, pdf and 'pdf') unless pdf and canvas
-  ctx = canvas.getContext('2d')
-  erase_background()
-
-  for interval_name, semitones in intervals
-    ix = (semitones + 11) % 12
-    dx = (ix % 3) * (padded_fretboard_width + canvas_gutter) + canvas_gutter / 2
-    dy = Math.floor(ix / 3) * (header_height + padded_fretboard_height + canvas_gutter)
-    ctx.save()
-    ctx.translate dx, dy
-    ctx.translate 0, header_height
-    draw_fingerboard()
-    draw_intervals_from semitones, string, fret
-    canvas_label = interval_name
-    ctx.font = '30px Impact'
-    ctx.fillStyle = 'rgb(10,20,30)'
-    ctx.fillText canvas_label, h_gutter, -3
-    ctx.restore()
-
-  filename = "#{string}-#{fret}-study-sheet.png"
-  save_canvas_to_png canvas, filename unless pdf
+  header_height = 20
+  grid 3, 4, padded_fretboard_width + canvas_gutter, padded_fretboard_height + header_height, 0, (cell) ->
+    for interval_name, semitones in intervals
+      cell ->
+        ctx.translate 0, header_height
+        draw_fingerboard()
+        draw_intervals_from fingering, semitones
+        canvas_label = interval_name
+        ctx.font = '10px Times'
+        ctx.fillStyle = 'rgb(10,20,30)'
+        ctx.fillText canvas_label, h_gutter, -3
+
+  # filename = "#{string}-#{fret}-study-sheet.png"
+  # save_canvas_to_png canvas, filename unless pdf

 intervals_from_note_sheets = ->
-  fingerings_each (string, fret) ->
+  finger_positions_each (string, fret) ->
     intervals_from_note_sheet string, fret

-intervals_from_root_book = ->
-  fingerings_each (string, fret) ->
-    intervals_from_note_sheet string, fret, true
-    ctx.addPage()
-  filename = "Fretboard Intervals by Root.pdf"
-  fs.writeFile build_directory + filename, canvas.toBuffer()
-
-make_interval_sheet = (semitones, pdf) ->
-  canvas_gutter = 20
+intervals_page = (semitones) ->
+  canvas_gutter = 5
   header_height = 40
   cols = fret_count + 1
-  rows = (string_count - hidden_strings.length)
-  canvas_width = padded_fretboard_width * cols + canvas_gutter * cols
-  canvas_height = header_height + padded_fretboard_height * rows + canvas_gutter * rows
-
-  canvas = new Canvas(canvas_width, canvas_height, pdf and 'pdf') unless pdf and canvas
-  ctx = canvas.getContext('2d')
-  ctx.textDrawingMode = 'glyph' if pdf
-  erase_background()
-
-  canvas_label = long_intervals_names[semitones] + " Intervals"
-  ctx.font = '30px Impact'
-  ctx.fillStyle = 'rgb(128, 128, 128)'
-  ctx.fillText canvas_label, canvas_gutter / 2, header_height
-
-  fingerings_each (string, fret) ->
-    return if hidden_strings.indexOf(string) >= 0
-    row = string - 1
-    row -= hidden_strings.length if show_hidden_strings and string > hidden_strings[0]
-    dx = fret * (padded_fretboard_width + canvas_gutter) + canvas_gutter / 2
-    dy = header_height + row * (padded_fretboard_height + canvas_gutter) + canvas_gutter / 2
-    ctx.save()
-    ctx.translate dx, dy
-    draw_fingerboard()
-    draw_intervals_from semitones, string, fret
-    if show_hidden_strings
-      ctx.fillStyle = 'rgba(255,255,255,0.75)'
-      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, padded_fretboard_width, hidden_strings.length * string_spacing
-      ctx.fillStyle = 'rgba(0,0,0,0.0125)'
-      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, padded_fretboard_width, hidden_strings.length * string_spacing
-    ctx.restore()
-
-  unless pdf
-    long_interval_name = intervals[semitones].replace(/^m/, 'min').replace(/^M/, 'Maj')
-    filename = "interval-#{long_interval_name}-study-sheet.pdf"
-    fs.writeFile build_directory + filename, canvas.toBuffer()
-
-intervals_book = (pages) ->
-  for _, semitones in intervals
-    make_interval_sheet semitones, true
-    break if pages
-    ctx.addPage()
-  filename = "Fretboard Intervals.pdf"
-  fs.writeFile build_directory + filename, canvas.toBuffer()
-
-# intervals_from_note_sheets()
-# for _, semitones in intervals
-#   make_interval_sheet semitones
-
-# intervals_book()
-
-# intervals_from_root_book()
+  rows = string_count
+
+  grid cols, rows, padded_fretboard_width + canvas_gutter, padded_fretboard_height + canvas_gutter, header_height, (cell) ->
+    title = long_intervals_names[semitones] + " Intervals"
+    ctx.font = '25px Impact'
+    ctx.fillStyle = 'rgb(128, 128, 128)'
+    ctx.fillText title, canvas_gutter / 2, 30
+
+    finger_positions_each (fingering) ->
+      cell ->
+        draw_fingerboard()
+        draw_intervals_from fingering, semitones
+
+  # unless pdf
+  #   long_interval_name = intervals[semitones].replace(/^m/, 'min').replace(/^M/, 'Maj')
+  #   filename = "interval-#{long_interval_name}-study-sheet.pdf"
+  #   fs.writeFile build_directory + filename, canvas.toBuffer()
+
+intervals_book = ({by_root}) ->
+  if by_root
+    book "Fretboard Intervals by Root.pdf", (page) ->
+      finger_positions_each (fingering) ->
+        page -> intervals_from_position_page fingering
+  else
+    book "Fretboard Intervals.pdf", (page) ->
+      for _, semitones in intervals
+        page -> intervals_page semitones

 mode = null
 pdf = false
@@ -290,16 +237,16 @@
 grid = (cols, rows, cell_width, cell_height, header_height, draw_page) ->
   page cols * cell_width, header_height + rows * cell_height, (ctx) ->
     i = 0
-    draw_page ctx, (draw_cell) ->
+    draw_page (draw_cell) ->
       ctx.save()
       ctx.translate (i % cols) * cell_width, header_height + Math.floor(i / cols) * cell_height
       draw_cell()
       ctx.restore()
       i += 1

-chord_page = (chord_name, abbr, intervals, outervals, pdf) ->
-  outervals ||= []
-
+NoteNames = "E F F# G G# A A# B C C# D D#".split(/\s/)
+
+chord_page = (chord) ->
   diagram_gutter = 20
   header_height = 40
   diagram_title_height = 30
@@ -310,39 +257,31 @@
     diagram_gutter = 10

   pitch_fingers = []
-  fingerings_each (string, fret) ->
-    pitch_number = note_number_at(string, fret) % 12
-    pitch_fingers[pitch_number] = [string, fret]
-
-  note_names = "E F F# G G# A A# B C C# D D#".split(/\s/)
+  finger_positions_each (fingering) ->
+    pitch_number = fingering_note_number(fingering) % 12
+    pitch_fingers[pitch_number] = fingering
+
   colors = ['red', 'blue', 'green', 'orange']
   other_colors = ['rgba(255,0,0 ,.1)', 'rgba(0,0,255, 0.1)', 'rgba(0,255,0, 0.1)', 'rgba(255,0,255, 0.1)']

-  grid cols, rows, padded_fretboard_width + diagram_gutter, padded_fretboard_height + diagram_gutter, diagram_title_height, (ctx, draw_cell) ->
+  grid cols, rows, padded_fretboard_width + diagram_gutter, padded_fretboard_height + diagram_gutter, diagram_title_height, (cell) ->
     ctx.font = '20px Impact'
     ctx.fillStyle = 'rgb(128, 128, 128)'
-    ctx.fillText "#{chord_name} Chords", diagram_gutter / 2, header_height / 2
+    ctx.fillText "#{chord.name} Chords", diagram_gutter / 2, header_height / 2

     for ix in [0...12]
       pitch_number = (ix * 5 + 7) % 12
-      fingering = pitch_fingers[pitch_number]
-      root_string = fingering[0]
-      root_fret = fingering[1]
-      draw_cell ->
+      root_fingering = pitch_fingers[pitch_number]
+      cell ->
         ctx.font = '20px Impact'
         ctx.font = '5pt Times' if draw_diagrams
         ctx.fillStyle = 'rgb(10,20,30)'
-        ctx.fillText note_names[pitch_number] + abbr, h_gutter, -3
+        ctx.fillText "#{NoteNames[pitch_number]}#{chord.abbr}", h_gutter, -3
         fingerings = []
-        # for semitones, si in outervals
-        #   continue if semitones in intervals
-        #   draw_intervals_from semitones, string, fret, other_colors[si]
-        for semitones, si in intervals
-          for {string, fret} in intervals_from(root_string, root_fret, semitones)
-            fingerings.push string: string, fret: fret, color: colors[si]
+        for semitones, si in chord.offsets
+          for {string, fret} in intervals_from(root_fingering, semitones)
+            fingerings.push {string, fret, color: colors[si]}
         draw_fingerboard fingerings
-
-# chord_page 'Major', [0, 4, 7]

 book = (filename, draw_book) ->
   pdf = true
@@ -352,27 +291,30 @@
     ctx.addPage()
   fs.writeFile build_directory + filename, canvas.toBuffer()

+Chords = [
+  {name: 'Major', abbr: '', offsets: [0, 4, 7]},
+  {name: 'Minor', abbr: 'm', offsets: [0, 3, 7]},
+  {name: 'Aug', abbr: '+', offsets: [0, 4, 8]},
+  {name: 'Dim', abbr: '°', offsets: [0, 3, 6]},
+  {name: 'Sus2', abbr: 'sus2', offsets: [0, 2, 7]},
+  {name: 'Sus4', abbr: 'sus4', offsets: [0, 5, 7]},
+  {name: 'Dom 7th', abbr: '7', offsets: [0, 4, 7, 10]},
+  {name: 'Major 7th', abbr: 'maj7', offsets: [0, 4, 7, 11]},
+  {name: 'Minor 7th', abbr: 'min7', offsets: [0, 3, 7, 10]},
+  {name: 'Dim 7th', abbr: '°7', offsets: [0, 3, 6, 9]},
+  {name: 'Dom 7 b5', abbr: '7b5', offsets: [0, 4, 6, 10]},
+  {name: 'Min 7th b5', abbr: 'm7b5', offsets: [0, 3, 6, 10]},
+  # {name: 'Aug 7th', abbr: '7aug', offsets: [0, 4, 8, 10]},
+  {name: 'Dim Maj 7th', abbr: '°maj7', offsets: [0, 3, 6, 11]},
+  {name: 'Min Maj 7th', abbr: 'min-maj7', offsets: [0, 3, 7, 11]},
+  # {name: '6th', abbr: '6', offsets: [0, 4, 7, 9]},
+]
+
 chord_book = ->
-  chords = [
-    {name: 'Major', abbr: '', offsets: [0, 4, 7], relative: [0, 3, 7]},
-    {name: 'Minor', abbr: 'm', offsets: [0, 3, 7], relative: [0, 4, 7]},
-    {name: 'Aug', abbr: '+', offsets: [0, 4, 8], relative: [0, 4, 7]},
-    {name: 'Dim', abbr: '°', offsets: [0, 3, 6], relative: [0, 3, 7]},
-    {name: 'Sus2', abbr: 'sus2', offsets: [0, 2, 7], relative: [0, 4, 7]},
-    {name: 'Sus4', abbr: 'sus4', offsets: [0, 5, 7], relative: [0, 4, 7]},
-    {name: 'Dom 7th', abbr: '7', offsets: [0, 4, 7, 10]},
-    {name: 'Major 7th', abbr: 'maj7', offsets: [0, 4, 7, 11]},
-    {name: 'Minor 7th', abbr: 'min7', offsets: [0, 3, 7, 10], relative: [0, 4, 7, 10]},
-    {name: 'Dim 7th', abbr: '°7', offsets: [0, 3, 6, 9]},
-    {name: 'Dom 7 b5', abbr: '7b5', offsets: [0, 4, 6, 10]},
-    {name: 'Min 7th b5', abbr: 'm7b5', offsets: [0, 3, 6, 10]},
-    # {name: 'Aug 7th', abbr: '7aug', offsets: [0, 4, 8, 10]},
-    {name: 'Dim Maj 7th', abbr: '°maj7', offsets: [0, 3, 6, 11]},
-    {name: 'Min Maj 7th', abbr: 'min-maj7', offsets: [0, 3, 7, 11]},
-    # {name: '6th', abbr: '6', offsets: [0, 4, 7, 9]},
-  ]
   book "Combined Fretboard Chords.pdf", (page) ->
-    for {name, abbr, offsets, relative} in chords
-      page -> chord_page name, abbr, offsets, relative
-
-chord_book()
+    for chord in Chords
+      page -> chord_page chord
+
+# chord_page Chords[0]
+# chord_book()
+intervals_book by_root: false


Added encodeURIComponent in ajax.serializeParameters to fix the upload of some characters like (+, &, ? ...)

--- src/quo.ajax.coffee
+++ src/quo.ajax.coffee
@@ -106,7 +106,7 @@
         for parameter of parameters
             if parameters.hasOwnProperty(parameter)
                 serialize += "&" if serialize isnt character
-                serialize += parameter + "=" + parameters[parameter]
+                serialize += "#{encodeURIComponent parameter}=#{encodeURIComponent parameters[parameter]}"
         (if (serialize is character) then "" else serialize)

     _xhrStatus = (xhr, settings) ->


Fixed highlighting

Fixed highlighting

--- grammars/perl.cson
+++ grammars/perl.cson
@@ -1516,9 +1516,9 @@
   {
     'match': '\\b(x)\\s*(\\d+)\\b'
     'captures':
-      '0':
+      '1':
         'name': 'support.function.perl'
-      '1':
+      '2':
         'name': 'entity.name.function.perl'
   }
   {


add support to WCTFs rp

add support to WCTFs rp

--- views/importTab.cjsx
+++ views/importTab.cjsx
@@ -13,6 +13,7 @@
 handleSlot = (slots) ->
   slotId = []
   slotLv = []
+  slotAlv = []
   for i in [1..4]
     slot = 'i' + i.toString()
     if slots.hasOwnProperty(slot)
@@ -21,12 +22,17 @@
         slotLv.push slots[slot].rf
       else
         slotLv.push null
+      if slots[slot].rp? > 0
+        slotAlv.push slots[slot].rp
+      else
+        slotAlv.push null
   if slots.ix?.id?
     slotId.push slots.ix.id
     slotLv.push null

   ids: slotId
   lvs: slotLv
+  alvs: slotAlv

 handleShip = (ship) ->
   shipDetail = Object.clone emptyShip
@@ -36,6 +42,7 @@
   slots = handleSlot ship.items
   shipDetail[2] = slots.ids
   shipDetail[3] = slots.lvs
+  shipDetail[4] = slots.alvs

   shipDetail


SocialWorker: Tests: add craete group functionality to withConvertedUser

--- workers/social/testhelper/index.coffee
+++ workers/social/testhelper/index.coffee
@@ -78,8 +78,9 @@

 withConvertedUser = (opts, callback) ->

-  [opts, callback]          = [callback, opts]  unless callback
-  { context, userFormData } = opts  if opts
+  [opts, callback] = [callback, opts]  unless callback
+  opts ?= {}
+  { context, userFormData } = opts

   context      ?= { group : 'koding' }
   userFormData ?= generateDummyUserFormData()
@@ -91,7 +92,7 @@
       client.sessionToken         = newToken
       client.connection.delegate  = account

-      fetchGroup client, (group) ->
+      fetchOrCreateGroup client, opts, (group) ->
         if opts?.userFormData?
         then callback { client, user, account, group, sessionToken : newToken }
         else callback { client, user, account, group, sessionToken : newToken, userFormData }
@@ -111,7 +112,7 @@
       expect(err).to.not.exist
       [user, account] = [user_, account_]

-      fetchGroup client, (group) ->
+      fetchOrCreateGroup client, opts, (group) ->
         callback { client, user, account, group }

@@ -198,11 +199,27 @@
     callback relationship

-fetchGroup = (client, callback) ->
-
-  JGroup.one { slug : client?.context?.group }, (err, group) ->
+fetchOrCreateGroup = (client, opts, callback) ->
+
+  [opts, callback] = [callback, opts]  unless callback
+  opts ?= {}
+
+  slug = client?.context?.group
+  JGroup.one { slug }, (err, group) ->
     expect(err).to.not.exist
-    callback group
+    return callback group  if group or not opts.createGroup
+
+    # no group found, let's create one
+    groupData         =
+      slug           : slug
+      title          : slug
+      visibility     : 'visible'
+      allowedDomains : [ 'koding.com' ]
+
+    account = client?.connection?.delegate
+    JGroup.create client, groupData, account, (err, group) ->
+      expect(err).to.not.exist
+      callback group

 expectRelation = {
@@ -226,7 +243,6 @@
   expect
   KONFIG
   ObjectId
-  fetchGroup
   expectRelation
   withDummyClient
   withCreatedUser


Workspace: Disable opening image files in CollaborativeWorkspace editors.

--- client/CollaborativeWorkspace/panes/collaborativefinderpane.coffee
+++ client/CollaborativeWorkspace/panes/collaborativefinderpane.coffee
@@ -56,6 +56,13 @@

     @finderController.reset()  unless @workspace.getOptions().playground

+    @finderController.on "CannotOpenImageFiles", =>
+      new KDNotificationView
+        type      : "mini"
+        title     : "You cannot open image files in #{@workspace.getOptions().name}"
+        container : @workspace
+        duration  : 4200
+
     @finderController.treeController.on "HistoryItemCreated", (historyItem) =>
       @workspace.addToHistory historyItem

@@ -111,9 +118,16 @@

   openFile: (nodeView) ->
     return unless nodeView
-    file = nodeView.getData()
+
+    file      = nodeView.getData()
+    extension = FSItem.getFileExtension file.path
+    fileType  = FSItem.getFileType extension
+    delegate  = @getDelegate()
+
+    return delegate.emit "CannotOpenImageFiles"  if fileType is "image"
+
     file.fetchContents (err, contents) =>
-      @getDelegate().emit "OpenedAFile", file, contents
+      delegate.emit "OpenedAFile", file, contents

   selectNode: (node) ->
     super


"palceholder" should be replaced by some strings

the input add members tips ( auto-complete --"placeholder") should be replaced by some strings when create a private group

--- packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee
+++ packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee
@@ -1,4 +1,7 @@
 Template.privateGroupsFlex.helpers
+	tRoomMembers: ->
+		return t('Members_placeholder')
+
 	selectedUsers: ->
 		return Template.instance().selectedUsers.get()


Verbose shell

Verbose shell

--- modules/admin/index.coffee
+++ modules/admin/index.coffee
@@ -45,7 +45,7 @@
     childProcess.exec 'git pull origin master', (error, stdout, stderr)->
       bot.sendMessage msg.channel, """
                        ```diff
-                       + $ git pull origin master
+                       + focabot@thebit.link ~ $ git pull origin master

                        """+stdout+"""
                        ```
@@ -56,7 +56,7 @@
     childProcess.exec args, (error, stdout, stderr)->
       bot.sendMessage msg.channel, """
                        ```diff
-                       + $ #{args}
+                       + focabot@thebit.link ~ $ #{args}

                        #{stdout}
                        ```


DYK no longer shows if empty. Default interval set to 1800s.

--- views/services/doyouknow.cjsx
+++ views/services/doyouknow.cjsx
@@ -1,7 +1,7 @@
 __ = window.i18n.others.__.bind(i18n.others)
 __n = window.i18n.others.__n.bind(i18n.others)

-INTERVAL = config.get('poi.doyouknow.interval', 600) # seconds, time for a new tip
+INTERVAL = config.get('poi.doyouknow.interval', 1800) # seconds, time for a new tip
 STICKY_TIME = 10 # seconds, time that the tip can't be refreshed by a "default"
 PREFIX = __ "doyouknow-prefix"

@@ -17,7 +17,7 @@
 firstUpdate = ->
   document.removeEventListener 'DOMContentLoaded', firstUpdate
   list = (text for text in __ 'doyouknow-contents' when text)
-  if list
+  if list.length != 0
     # Delay for a period b/c the alert region may not properly detect its width
     setTimeout update, 1000, list


still need to remove fancybox when navigating away from page :|

--- resources/assets/lib/store-centili.coffee
+++ resources/assets/lib/store-centili.coffee
@@ -24,10 +24,16 @@
   # while the Centili script loads EVEN MORE SCRIPTS
   ####################################################
   clicked = false
+  fancyboxes = []
+  hasWidget = ->
+    document.querySelector(widget)
+
   observer = new MutationObserver (mutations) ->
     mutations.forEach (mutation) ->
       $nodes = $(mutation.addedNodes)
-      console.log($nodes)
+      for node in mutation.addedNodes
+        node.id?.startsWith('fancybox-') && fancyboxes.push(node)
+
       frame = $.grep $nodes, (elem) ->
         elem.id == 'fancybox-frame'

@@ -41,8 +47,14 @@
           window.centiliJQuery(widget).trigger('click')

   $(document).on 'turbolinks:load', ->
-    if document.querySelector(widget)
-      observer.observe document.body, childList: true, subtree: true
+    hasWidget() && observer.observe document.body, childList: true, subtree: true
+
+  $(document).on 'turbolinks:before-cache', ->
+    hasWidget() && window.deleteFancyBoxes()
+
+  window.deleteFancyBoxes = ->
+    $(fancyboxes).remove()
+    fancyboxes = []
   ####################################################
   # End stupid
   ####################################################


sublime text took out trailing whitespace

--- web/scripts/gameVisual.coffee
+++ web/scripts/gameVisual.coffee
@@ -111,7 +111,7 @@

 	###
 	#pixMove accepts a character index and an x and y coordinate referencing pixels.  This is a more powerful function than gridMove allowing for freer movement
-	#for potential use in other gametypes.  Can break things if used in improper conjunction with gridMove.
+	#for potential use in other gametypes.  Can break things if used in improper conjunction with gridMove.
 	###
 	pixMove: (char,x,y) ->
 		objArray[char].absPos(x,y)
@@ -143,7 +143,7 @@
 			if (anticker % (2 * ar)) >= ar
 				num = 1
 			num = num + (2 * @dir)
-			console.log("dir="+@dir)
+			# console.log("dir="+@dir)
 			return @animarray[num]

 		imFace: (@dir) ->


Regenerate entire row when slot.top changes

Fixes #4571

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -712,7 +712,7 @@
           row

         createSlot = (info) ->
-          saveRun info.cell, volume.createContainer(false).then (slot) ->
+          saveRun info.cell, volume.createContainer().then (slot) ->
             arr(slot, 'records')
             addRow(populateSlot(slot))

@@ -793,7 +793,11 @@
         updateDatum = (info, v) ->
           info.v = v
           info.d[info.metric.id] = v
-          if info.category.fixed
+          if info.metric.id == 'top'
+            collapse()
+            generateRow(info.i)
+            expand(info)
+          else if info.category.fixed
             generateText(info)
           else
             for li in TBody.getElementsByClassName(info.p + info.d.id + '_' + info.metric.id)
@@ -914,6 +918,7 @@
             return unless value
             createNew(info).then (row) ->
               saveDatum(new Info(row.tr.firstChild), value)
+              return
           else
             saveDatum(info, value)
           return


Fix test failures on iOS

Fix test failures on iOS

--- test/src/system/level_2_input_test.coffee
+++ test/src/system/level_2_input_test.coffee
@@ -1,4 +1,4 @@
-{assert, clickToolbarButton, defer, insertString, isToolbarButtonActive, selectAll, test, testIf, testGroup, triggerEvent, triggerInputEvent} = Trix.TestHelpers
+{assert, after, clickToolbarButton, defer, insertString, isToolbarButtonActive, selectAll, test, testIf, testGroup, triggerEvent, triggerInputEvent} = Trix.TestHelpers

 test = ->
   testIf(Trix.config.input.getLevel() is 2, arguments...)
@@ -160,4 +160,4 @@
   inputEvent = createEvent("beforeinput", inputType: "insertFromPaste", dataTransfer: dataTransfer)
   if document.activeElement.dispatchEvent(pasteEvent)
     document.activeElement.dispatchEvent(inputEvent)
-  requestAnimationFrame(callback)
+  after(60, callback)


nicety

nicety

--- src/lib/client.iced
+++ src/lib/client.iced
@@ -85,7 +85,9 @@
     await @package_args defer args

     # don'e bother logging in again if we're already there
-    args = null if @_session? and @_login_args? and @same_args args
+    if @_session? and @_login_args? and @same_args args
+      @doc().set_sync_status true, "Signed in"
+      args = null

     if args?
       @doc().set_sync_status true, "Logging in...." unless bgloop


defined cleanurl method for file

defined cleanurl method for file

--- src/lib/file.coffee
+++ src/lib/file.coffee
@@ -20,6 +20,10 @@
 	extension: ->
 		path.extname @full_path

+	cleanurl: ->
+		file_with_ext = path.join(File::URL_PREFIX, _s.strRight(@full_path, process.cwd()))
+		"#{_s.strLeft(file_with_ext, path.extname(@full_path))}"
+
 	url: ->
 		file_with_ext = path.join(File::URL_PREFIX, _s.strRight(@full_path, process.cwd()))
 		"#{_s.strLeft(file_with_ext, path.extname(@full_path))}?e=#{_s.strRight(this.extension(), ".")}"
@@ -32,6 +36,5 @@

 	isValid: ->
 		_.contains(File::ALLOWED_EXTENSIONS, this.extension())
-

 module.exports = File


set some codemirror options

set some codemirror options


[modularization] Add post method

[modularization] Add post method

--- app.coffee
+++ app.coffee
@@ -24,6 +24,7 @@
   app.use express.errorHandler()

 app.get '/', routes.index
+app.post '/api', routes.api
 app.get '/api/:name', routes.api

 module.exports = do =>


I want the spacing on the proto version to match the jq version

--- coffee/chosen.proto.coffee
+++ coffee/chosen.proto.coffee
@@ -240,7 +240,6 @@

   set_label_behavior: ->
     @form_field_label = @form_field.up("label") # first check for a parent label
-
     if not @form_field_label?
       @form_field_label = $$("label[for=#{@form_field.id}]").first() #next check for a for=#{id}


Add null command

Add null command

--- lib/Shell.coffee
+++ lib/Shell.coffee
@@ -28,7 +28,10 @@
         @settings.stdin ?= process.stdin
         @settings.stdout ?= process.stdout
         @set 'env', @settings.env ? process.env.NODE_ENV ? 'development'
-        @set 'command', @settings.command ? process.argv.slice(2).join(' ')
+        @set 'command',
+            if typeof settings.command isnt 'undefined'
+            then settings.command
+            else process.argv.slice(2).join(' ')
         @stack = []
         @styles = styles {stdout: @settings.stdout}
         process.on 'beforeExit', =>
@@ -57,7 +60,7 @@
                     @prompt()
             else
                 command = @set 'command'
-                @run command
+                @run command if command
         return @

     # Return the readline interface and create it if not yet initialized
@@ -188,4 +191,4 @@
     quit: (params) ->
         @emit 'quit'
         @interface().close()
-        process.stdin.destroy()
+        @settings.stdin.destroy()


21st Commit

21st Commit

--- protractor.conf.coffee
+++ protractor.conf.coffee
@@ -8,7 +8,7 @@
   @onPrepare: ->
     SpecReporter = require 'jasmine-spec-reporter'
     jasmine.getEnv().addReporter new SpecReporter displayStacktrace: true
-    browser.ignoreSynchronization = false
+    browser.ignoreSynchronization = true
     browser.driver.manage().window().maximize()
   @framework: 'jasmine2'
   @jasmineNodeOpts:


fixed components folder name

fixed components folder name

--- lib/build.coffee
+++ lib/build.coffee
@@ -105,7 +105,7 @@
               metaDataType = 'ApexTrigger'
             when 'pages'
               metaDataType = 'ApexPage'
-            when 'component'
+            when 'components'
               metaDataType = 'ApexComponent'
             when 'staticresources'
               metaDataType = 'StaticResource'


Show how many days left on user badge

Show how many days left on user badge

--- client/components/UserBadge.coffee
+++ client/components/UserBadge.coffee
@@ -142,7 +142,12 @@
                                 size="20"
                                 onChange={@handlePaidTillChange}
                                 onKeyPress={@handleKeyPressed} />
-                            {" (сейчас: "}{if @props.user.paidTill then moment(@props.user.paidTill).format("YYYY-MM-DD")})
+                            {if @props.user.paidTill
+                                paidTill = moment(@props.user.paidTill).hours(23).minutes(59).seconds(59)
+                                paidTillDate = paidTill.format("YYYY-MM-DD")
+                                timeLeft = Math.floor(paidTill.diff(moment(), 'days', true))
+                                " (на сервере: #{paidTillDate} = #{timeLeft} дней)"
+                            }
                         </div>
                     </form> }
                 { @props.me?.admin && <GroupSelector user={@props.user} handleReload={@props.handleReload}/> }


Add global click handler to close popovers/tooltips

--- src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
+++ src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
@@ -1,7 +1,12 @@
 define (require) ->
+  $ = require('jquery')
   BaseView = require('cs!helpers/backbone/views/base')
   template = require('hbs!helpers/backbone/views/attached/tooltip/tooltip-template')
   require('less!helpers/backbone/views/attached/tooltip/tooltip')
+
+  # Close tooltips and popovers when clicking outside of them
+  $(document).click () ->
+    $('.popover, .tooltip').hide().removeClass('in')

   return class Tooltip extends BaseView
     containerTemplate: template
@@ -20,23 +25,31 @@
       super()
       @$owner = $(options.owner)

+    stopPropagation: (e) ->
+      e.stopPropagation()
+
     onShow: () ->
+      # Don't close due to clicks on this element
+      @$el.on "click.#{@type}.#{@cid}", @stopPropagation
+
       switch @trigger
         when 'click'
-          @$owner.on "click.#{@type}.#{@cid}", () => @toggle()
+          @$owner.on "click.#{@type}.#{@cid}", (e) => @toggle(e)
         when 'hover'
-          @$owner.on "mouseenter.#{@type}.#{@cid}", () => @show()
-          @$owner.on "mouseleave.#{@type}.#{@cid}", () => @hide()
+          @$owner.on "mouseenter.#{@type}.#{@cid}", (e) => @show(e)
+          @$owner.on "mouseleave.#{@type}.#{@cid}", (e) => @hide(e)

-    show: () ->
+    show: (e) ->
+      e.stopPropagation()
       @reposition()
       @$el.children(".#{@type}").show().addClass('in')

-    hide: () ->
-      @reposition()
+    hide: (e) ->
+      e.stopPropagation()
       @$el.children(".#{@type}").hide().removeClass('in')

-    toggle: () ->
+    toggle: (e) ->
+      e.stopPropagation()
       @reposition()
       @$el.children(".#{@type}").toggle().toggleClass('in')

@@ -65,6 +78,7 @@
             'right': Math.floor($(document).outerWidth(true) - @$owner.offset().left)

     onBeforeClose: () ->
+      @$el.off "click.#{@type}.#{@cid}"
       @$owner.off "click.#{@type}.#{@cid}"
       @$owner.off "mouseenter.#{@type}.#{@cid}"
       @$owner.off "mouseleave.#{@type}.#{@cid}"


JMachine::some$ filter machines by groups information

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -428,6 +428,7 @@

       selector             ?= {}
       selector['users.id']  = user.getId()
+      selector['groups.id'] = group.getId()

       JMachine.some selector, limit: 30, (err, machines)->
         callback err, machines


Implementando PL/0

Implementando PL/0

--- public/scripts/coffee/main.coffee
+++ public/scripts/coffee/main.coffee
@@ -32,20 +32,29 @@

   tokens =
     WHITES: /\s+/g
+    ONELINECOMMENT: /\/\/.*/g
+    MULTIPLELINECOMMENT: /\/[*](.|\n)*?[*]\//g
     ID: /[a-zA-Z_]\w*/g
     NUM: /\b\d+(\.\d*)?([eE][+-]?\d+)?\b/g
     STRING: /('(\\.|[^'])*'|"(\\.|[^"])*")/g
-    ONELINECOMMENT: /\/\/.*/g
-    MULTIPLELINECOMMENT: /\/[*](.|\n)*?[*]\//g
-    ONECHAROPERATORS: /([=()&|;:,<>{}[\]])/g
-    ADDSUBOPERATORS: /[+-]/
-    MULTDIVOPERATORS: /[*\/]/
-    COMPARISONOPERATOR: /[<>=!]=|[<>]/g
+    ASSIGNOPERATOR: /:=/g
+    COMPARISONOPERATOR: /[<>]=|[<>=#]/g
+    ADDSUBOPERATOR: /[+-]/g
+    MULTDIVOPERATOR: /[*\/]/g
+    ONECHAROPERATOR: /([()&|;:,<>{}[\]])/g

   RESERVED_WORD =
-    p: "P"
+    const: "CONST"
+    "var": "VAR"
+    procedure: "PROCEDURE"
+    call: "CALL"
+    begin: "BEGIN"
+    end: "END"
     "if": "IF"
     then: "THEN"
+    "while": "WHILE"
+    "do": "DO"
+    odd: "ODD"

   make = (type, value) ->
     type: type
@@ -66,29 +75,48 @@

     from = i

+    # Espacios en blanco y comentarios se ignoran
     if m = tokens.WHITES.bexec(this) or (m = tokens.ONELINECOMMENT.bexec(this)) or (m = tokens.MULTIPLELINECOMMENT.bexec(this))
       getTok()
+
+    # Identificadores y palabras reservadas
     else if m = tokens.ID.bexec(this)
       rw = RESERVED_WORD[m[0]]
       if rw
         result.push make(rw, getTok())
       else
         result.push make("ID", getTok())
+
+    # Números
     else if m = tokens.NUM.bexec(this)
       n = +getTok()
       if isFinite(n)
         result.push make("NUM", n)
       else
         make("NUM", m[0]).error "Bad number"
+
+    # Cadenas de literales
     else if m = tokens.STRING.bexec(this)
       result.push make("STRING", getTok().replace(/^["']|["']$/g, ""))
+
+    # Operador de asignación
+    else if m = tokens.ASSIGNOPERATOR.bexec(this)
+      result.push make("ASSIGN", getTok())
+
+    # Operador de comparación
     else if m = tokens.COMPARISONOPERATOR.bexec(this)
       result.push make("COMPARISON", getTok())
-    else if m = tokens.ADDSUBOPERATORS.bexec(this)
+
+    # Operadores de adición y substracción
+    else if m = tokens.ADDSUBOPERATOR.bexec(this)
       result.push make("ADDSUB", getTok())
-    else if m = tokens.MULTDIVOPERATORS.bexec(this)
+
+    # Operadores de multiplicación y división
+    else if m = tokens.MULTDIVOPERATOR.bexec(this)
       result.push make("MULTDIV", getTok())
-    else if m = tokens.ONECHAROPERATORS.bexec(this)
+
+    # Operadores de un solo símbolo
+    else if m = tokens.ONECHAROPERATOR.bexec(this)
       result.push make(m[0], getTok())
     else
       throw "Syntax error near '" + (@substr(i)) + "'"
@@ -105,118 +133,192 @@
       throw ("Syntax Error. Expected " + t + " found '") + lookahead.value + "' near '" + input.substr(lookahead.from) + "'"
     return

-  statements = ->
-    result = [statement()]
-    while lookahead and lookahead.type is ";"
-      match ";"
-      result.push statement()
-    if result.length is 1
-      result[0]
+  # PL/0
+  program = ->
+    result = block()
+    if lookahead and lookahead.type is "."
+      match "."
     else
-      result
+      throw "Error. Expecting '.' to end program"
+    result
+
+  block = ->
+    result = []
+    if lookahead and lookahead.type is "CONST"
+      match "CONST"
+
+      if lookahead and lookahead.type is "ID"
+        varname = lookahead.value
+        match "ID"
+
+        if lookahead and lookahead.type is "ASSIGN"
+          match "ASSIGN"
+
+          if lookahead and lookahead.type is "NUMBER"
+            numval = lookahead.value
+            match "NUMBER"
+            result.push (
+              type: "CONSTVAR"
+              value: numval
+            )
+            match ";"
+          else
+            throw "Error. Expecting a number"
+        else
+          throw "Error. Constants must be initialized"
+      else
+        throw "Error. Expecting an identifier"
+
+
+    if lookahead and lookahead.type is "VAR"
+      match "VAR"
+
+      if lookahead and lookahead.type is "ID"
+        varname = lookahead.value
+        match "ID"
+        match ";"
+        result.push (
+          type: "VAR"
+          name: varname
+        )
+      else
+        throw "Error. Expecting ID"
+
+    if lookahead and lookahead.type is "PROCEDURE"
+      match "PROCEDURE"
+
+      if lookahead and lookahead.type is "ID"
+        nvalue = lookahead.value
+        match "ID"
+        match ";"
+        result.push (
+          type: "PROCEDURE"
+          name: nvalue
+          content: block()
+        )
+        match ";"
+      else
+        throw "Error. Expecting ID"
+
+    statement()

   statement = ->
-    result = null
-    if lookahead and lookahead.type is "ID"
-      left =
-        type: "ID"
-        value: lookahead.value
-
-      match "ID"
-      match "="
-      right = expression()
-      result =
-        type: "="
-        left: left
-        right: right
-    else if lookahead and lookahead.type is "P"
-      match "P"
-      right = expression()
-      result =
-        type: "P"
-        value: right
-    else if lookahead and lookahead.type is "IF"
-      match "IF"
-      left = condition()
-      match "THEN"
-      right = statement
-      result =
-        type: "IF"
-        left: left
-        right: right
-    else
-      throw "Syntax Error. Expected identifier but found " + ((if lookahead then lookahead.value else "end of input")) + (" near '" + (input.substr(lookahead.from)) + "'")
-    result
-
-  condition = ->
-    left = expression()
-    type = lookahead.value
-    match "COMPARISON"
-    right = expression()
-    result =
-      left: left
-      right: right
-      type: type
-    result
-
-  expression = ->
-    result = term()
-    if lookahead and lookahead.type is "+"
-      match "+"
-      right = expression()
-      result =
-        type: "+"
-        left: result
-        right: right
-    else if lookahead and lookahead.type is "-"
-      match "-"
-      right = expression()
-      result =
-        type: "-"
-        left: result
-        right: right
-    result
-
-  term = ->
-    result = factor()
-    if lookahead and lookahead.type is "*"
-      match "*"
-      right = term()
-      result =
-        type: "*"
-        left: result
-        right: right
-    else if lookahead and lookahead.type is "/"
-      match "/"
-      right = term()
-      result =
-        type: "/"
-        left: result
-        right: right
-    result
-
-  factor = ->
-    result = null
-    if lookahead.type is "NUM"
-      result =
-        type: "NUM"
-        value: lookahead.value
-
-      match "NUM"
-    else if lookahead.type is "ID"
-      result =
-        type: "ID"
-        value: lookahead.value
-
-      match "ID"
-    else if lookahead.type is "("
-      match "("
-      result = expression()
-      match ")"
-    else
-      throw "Syntax Error. Expected number or identifier or '(' but found " + ((if lookahead then lookahead.value else "end of input")) + " near '" + input.substr(lookahead.from) + "'"
-    result
-
-  tree = statements(input)
-  throw "Syntax Error parsing statements. " + "Expected 'end of input' and found '" + input.substr(lookahead.from) + "'"  if lookahead?
-  tree+    result = []
+
+
+  # Anterior
+  #statements = ->
+  #  result = [statement()]
+  #  while lookahead and lookahead.type is ";"
+  #    match ";"
+  #    result.push statement()
+  #  if result.length is 1
+  #    result[0]
+  #  else
+  #    result
+
+  #statement = ->
+  #  result = null
+  #  if lookahead and lookahead.type is "ID"
+  #    left =
+  #      type: "ID"
+  #      value: lookahead.value
+
+  #    match "ID"
+  #    match "="
+  #    right = expression()
+  #    result =
+  #      type: "="
+  #      left: left
+  #      right: right
+  #  else if lookahead and lookahead.type is "P"
+  #    match "P"
+  #    right = expression()
+  #    result =
+  #      type: "P"
+  #      value: right
+  #  else if lookahead and lookahead.type is "IF"
+  #    match "IF"
+  #    left = condition()
+  #    match "THEN"
+  #    right = statement
+  #    result =
+  #      type: "IF"
+  #      left: left
+  #      right: right
+  #  else
+  #    throw "Syntax Error. Expected identifier but found " + ((if lookahead then lookahead.value else "end of input")) + (" near '" + (input.substr(lookahead.from)) + "'")
+  #  result
+
+  #condition = ->
+  #  left = expression()
+  #  type = lookahead.value
+  #  match "COMPARISON"
+  #  right = expression()
+  #  result =
+  #    left: left
+  #    right: right
+  #    type: type
+  #  result
+
+  #expression = ->
+  #  result = term()
+  #  if lookahead and lookahead.type is "+"
+  #    match "+"
+  #    right = expression()
+  #    result =
+  #      type: "+"
+  #      left: result
+  #      right: right
+  #  else if lookahead and lookahead.type is "-"
+  #    match "-"
+  #    right = expression()
+  #    result =
+  #      type: "-"
+  #      left: result
+  #      right: right
+  #  result
+
+  #term = ->
+  #  result = factor()
+  #  if lookahead and lookahead.type is "*"
+  #    match "*"
+  #    right = term()
+  #    result =
+  #      type: "*"
+  #      left: result
+  #      right: right
+  #  else if lookahead and lookahead.type is "/"
+  #    match "/"
+  #    right = term()
+  #    result =
+  #      type: "/"
+  #      left: result
+  #      right: right
+  #  result
+
+  #factor = ->
+  #  result = null
+  #  if lookahead.type is "NUM"
+  #    result =
+  #      type: "NUM"
+  #      value: lookahead.value
+
+  #    match "NUM"
+  #  else if lookahead.type is "ID"
+  #    result =
+  #      type: "ID"
+  #      value: lookahead.value
+
+  #    match "ID"
+  #  else if lookahead.type is "("
+  #    match "("
+  #    result = expression()
+  #    match ")"
+  #  else
+  #    throw "Syntax Error. Expected number or identifier or '(' but found " + ((if lookahead then lookahead.value else "end of input")) + " near '" + input.substr(lookahead.from) + "'"
+  #  result
+
+  #tree = statements(input)
+  #throw "Syntax Error parsing statements. " + "Expected 'end of input' and found '" + input.substr(lookahead.from) + "'"  if lookahead?
+  #tree


Fix config.schema layout, add email.notificationCron

--- config/config.schema.coffee
+++ config/config.schema.coffee
@@ -1,185 +1,185 @@
 config =
-  _selfConfig :
-    defaultConfig : "dev-new"
-  kite :
-    applications :
-      name              : 1
-      pidPath           : 1
-      logFile           : 1
-      amqp            :
-        host            : 1
-        login           : 1
-        password        : 1
-        heartbeat       : 1
-      apiUri            : 1
-      usersPath         : 1
-      vhostDir          : 1
-      defaultDomain     : 1
-      minAllowedUid     : 1
-      debugApi          : 1
-    databases    :
-      name              : 1
-      pidPath           : 1
-      logFile           : 1
-      port              : 1
-      amqp                  :
-        host                : 1
-        login               : 1
-        password            : 1
-        heartbeat           : 1
-      apiUri            : 1
-      mysql             :
-        usersPath       : 1
-        backupDir       : 1
-        databases       :
-          mysql         : [{ host : 1, user : 1, password:1}]
-      mongo             :
-        databases       :
-          mongodb       : [{ host : 1, user : 1, password:1}]
-    sharedHosting :
-      name                  : 1
-      numberOfWorkers       : 1
-      pidPath               : 1
-      logFile               : 1
-      amqp                  :
-        host                : 1
-        login               : 1
-        password            : 1
-        heartbeat           : 1
-      apiUri                : 1
-      usersPath             : 1
-      vhostDir              : 1
-      suspendDir            : 1
-      defaultVhostFiles     : 1
-      freeUsersGroup        : 1
-      liteSpeedUser         : 1
-      defaultDomain         : 1
-      minAllowedUid         : 1
-      debugApi              : 1
-      processBaseDir        : 1
-      cagefsctl             : 1
-      baseMountDir          : 1
-      maxAllowedRemotes     : 1
-      usersMountsFile       : 1
-      encryptKey            : 1
-      ftpfs                 :
-        curlftpfs           : 1
-        opts                : 1
-      sshfs                 :
-        sshfscmd            : 1
-        opts                : 1
-        optsWithKey         : 1
-      lsws                  :
-        baseDir             : 1
-        controllerPath      : 1
-        lsMasterConfig      : 1
-        configFilePath      : 1
-        minRestartInterval  : 1
-      ldap                  :
-        ldapUrl             : 1
-        rootUser            : 1
-        rootPass            : 1
-        groupDN             : 1
-        userDN              : 1
-        freeUID             : 1
-        freeGroup           : 1
-      FileSharing           :
-        baseSharedDir       : 1
-        baseDir             : 1
-        setfacl             : 1
-  main :
-    uri           :
-      address     : 1
-    projectRoot   : 1
-    version       : 1
-    webserver     :
-      login       : 1
-      port        : []
-      clusterSize : 1
-      queueName   : 1
-    mongo         : 1
-    runGoBroker   : 1
-    buildClient   : 1
-    misc          :
-      claimGlobalNamesForUsers: 1
-      updateAllSlugs : 1
-      debugConnectionErrors: 1
-    uploads       :
-      enableStreamingUploads: 1
-      distribution: 1
-      s3          :
-        awsAccountId        : 1
-        awsAccessKeyId      : 1
-        awsSecretAccessKey  : 1
-        bucket              : 1
-    librato :
-      push      : 1
-      email     : 1
-      token     : 1
-      interval  : 1
-    bitly :
-      username  : 1
-      apiKey    : 1
-    authWorker    :
-      login       : 1
-      queueName   : 1
-      authResourceName: 1
-      numberOfWorkers: 1
-    social        :
-      login       : 1
-      numberOfWorkers: 1
-      watch       : 1
-      queueName   : 1
-    feeder        :
-      queueName   : 1
-      exchangePrefix: 1
-      numberOfWorkers: 1
-    presence      :
-      exchange    : 1
-    client        :
-      pistachios  : 1
-      version     : 1
-      minify      : 1
-      watch       : 1
-      js          : 1
-      css         : 1
-      indexMaster: 1
-      index       : 1
-      includesFile: 1
-      useStaticFileServer: 1
-      staticFilesBaseUrl: 1
-      runtimeOptions:
-        resourceName: 1
-        suppressLogs: 1
-        version   : 1
-        mainUri   : 1
-        broker    :
-          sockJS  : 1
-        apiUri    : 1
-        appsUri   : 1
-    mq            :
-      host        : 1
-      login       : 1
-      password    : 1
-      heartbeat   : 1
-      vhost       : 1
-    kites:
-      disconnectTimeout: 1
-      vhost       : 1
-    email         :
-      host        : 1
-      protocol    : 1
-      defaultFromAddress: 1
-    guests        :
-      poolSize        : 1
-      batchSize       : 1
-      cleanupCron     : 1
-    logger            :
-      mq              :
-        host          : 1
-        login         : 1
-        password      : 1
-    pidFile       : 1
-
+  _selfConfig                  :
+    defaultConfig              : "dev-new"
+  kite                         :
+    applications               :
+      name                     : 1
+      pidPath                  : 1
+      logFile                  : 1
+      amqp                     :
+        host                   : 1
+        login                  : 1
+        password               : 1
+        heartbeat              : 1
+      apiUri                   : 1
+      usersPath                : 1
+      vhostDir                 : 1
+      defaultDomain            : 1
+      minAllowedUid            : 1
+      debugApi                 : 1
+    databases                  :
+      name                     : 1
+      pidPath                  : 1
+      logFile                  : 1
+      port                     : 1
+      amqp                     :
+        host                   : 1
+        login                  : 1
+        password               : 1
+        heartbeat              : 1
+      apiUri                   : 1
+      mysql                    :
+        usersPath              : 1
+        backupDir              : 1
+        databases              :
+          mysql                : [{ host:1, user:1, password:1}]
+      mongo                    :
+        databases              :
+          mongodb              : [{ host:1, user:1, password:1}]
+    sharedHosting              :
+      name                     : 1
+      numberOfWorkers          : 1
+      pidPath                  : 1
+      logFile                  : 1
+      amqp                     :
+        host                   : 1
+        login                  : 1
+        password               : 1
+        heartbeat              : 1
+      apiUri                   : 1
+      usersPath                : 1
+      vhostDir                 : 1
+      suspendDir               : 1
+      defaultVhostFiles        : 1
+      freeUsersGroup           : 1
+      liteSpeedUser            : 1
+      defaultDomain            : 1
+      minAllowedUid            : 1
+      debugApi                 : 1
+      processBaseDir           : 1
+      cagefsctl                : 1
+      baseMountDir             : 1
+      maxAllowedRemotes        : 1
+      usersMountsFile          : 1
+      encryptKey               : 1
+      ftpfs                    :
+        curlftpfs              : 1
+        opts                   : 1
+      sshfs                    :
+        sshfscmd               : 1
+        opts                   : 1
+        optsWithKey            : 1
+      lsws                     :
+        baseDir                : 1
+        controllerPath         : 1
+        lsMasterConfig         : 1
+        configFilePath         : 1
+        minRestartInterval     : 1
+      ldap                     :
+        ldapUrl                : 1
+        rootUser               : 1
+        rootPass               : 1
+        groupDN                : 1
+        userDN                 : 1
+        freeUID                : 1
+        freeGroup              : 1
+      FileSharing              :
+        baseSharedDir          : 1
+        baseDir                : 1
+        setfacl                : 1
+  main                         :
+    uri                        :
+      address                  : 1
+    projectRoot                : 1
+    version                    : 1
+    webserver                  :
+      login                    : 1
+      port                     : []
+      clusterSize              : 1
+      queueName                : 1
+    mongo                      : 1
+    runGoBroker                : 1
+    buildClient                : 1
+    misc                       :
+      claimGlobalNamesForUsers : 1
+      updateAllSlugs           : 1
+      debugConnectionErrors    : 1
+    uploads                    :
+      enableStreamingUploads   : 1
+      distribution             : 1
+      s3                       :
+        awsAccountId           : 1
+        awsAccessKeyId         : 1
+        awsSecretAccessKey     : 1
+        bucket                 : 1
+    librato                    :
+      push                     : 1
+      email                    : 1
+      token                    : 1
+      interval                 : 1
+    bitly                      :
+      username                 : 1
+      apiKey                   : 1
+    authWorker                 :
+      login                    : 1
+      queueName                : 1
+      authResourceName         : 1
+      numberOfWorkers          : 1
+    social                     :
+      login                    : 1
+      numberOfWorkers          : 1
+      watch                    : 1
+      queueName                : 1
+    feeder                     :
+      queueName                : 1
+      exchangePrefix           : 1
+      numberOfWorkers          : 1
+    presence                   :
+      exchange                 : 1
+    client                     :
+      pistachios               : 1
+      version                  : 1
+      minify                   : 1
+      watch                    : 1
+      js                       : 1
+      css                      : 1
+      indexMaster              : 1
+      index                    : 1
+      includesFile             : 1
+      useStaticFileServer      : 1
+      staticFilesBaseUrl       : 1
+      runtimeOptions           :
+        resourceName           : 1
+        suppressLogs           : 1
+        version                : 1
+        mainUri                : 1
+        broker                 :
+          sockJS               : 1
+        apiUri                 : 1
+        appsUri                : 1
+    mq                         :
+      host                     : 1
+      login                    : 1
+      password                 : 1
+      heartbeat                : 1
+      vhost                    : 1
+    kites                      :
+      disconnectTimeout        : 1
+      vhost                    : 1
+    email                      :
+      host                     : 1
+      protocol                 : 1
+      defaultFromAddress       : 1
+      notificationCron         : 1
+    guests                     :
+      poolSize                 : 1
+      batchSize                : 1
+      cleanupCron              : 1
+    logger                     :
+      mq                       :
+        host                   : 1
+        login                  : 1
+        password               : 1
+    pidFile                    : 1

 module.exports = config


first backdrop

first backdrop

--- client/client.coffee
+++ client/client.coffee
@@ -27,7 +27,7 @@
         @viewport.append @renderer.view

         loader = new PIXI.AssetLoader @assetPaths
-        loader.onComplete = => @startGameLoop()
+        loader.onComplete = => @setupTextures()
         loader.load()

     setupConnection: ->
@@ -39,6 +39,90 @@
 #        @socket.on 'server-event', (msg) ->
 #            console.log "message received", msg

+    setupTextures: ->
+        # Shore
+        @stage.addChild @setPosition 4, 0, @animatedTexture "A24", "K24"
+        @stage.addChild @setPosition 0, 1, @animatedTexture "A23", "K23"
+        @stage.addChild @setPosition 1, 1, @animatedTexture "B23", "L23"
+        @stage.addChild @setPosition 2, 1, @animatedTexture "B23", "L23"
+        @stage.addChild @setPosition 3, 1, @animatedTexture "B23", "L23"
+        @stage.addChild @setPosition 4, 1, @animatedTexture "I25", "S25"
+        @stage.addChild @setPosition 0, 2, @animatedTexture "A24", "K24"
+        @stage.addChild @setPosition 0, 3, @animatedTexture "A27", "K27"
+        @stage.addChild @setPosition 1, 3, @animatedTexture "E25", "O25"
+        @stage.addChild @setPosition 2, 3, @animatedTexture "I20", "S20"
+        @stage.addChild @setPosition 2, 4, @animatedTexture "D25", "N25"
+        @stage.addChild @setPosition 3, 4, @animatedTexture "E25", "O25"
+        @stage.addChild @setPosition 4, 4, @animatedTexture "E25", "O25"
+        @stage.addChild @setPosition 5, 4, @animatedTexture "E25", "O25"
+        # Water
+        @stage.addChild @setPosition 5, 0, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 5, 1, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 1, 2, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 2, 2, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 3, 2, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 4, 2, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 5, 2, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 3, 3, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 4, 3, @animatedTexture "D22", "N22"
+        @stage.addChild @setPosition 5, 3, @animatedTexture "D22", "N22"
+        # Grass
+        @stage.addChild @setPosition 0, 0, @texture "B03"
+        @stage.addChild @setPosition 1, 0, @texture "B03"
+        @stage.addChild @setPosition 2, 0, @texture "B03"
+        @stage.addChild @setPosition 3, 0, @texture "B03"
+        @stage.addChild @setPosition 0, 6, @texture "B03"
+        @stage.addChild @setPosition 1, 6, @texture "B03"
+        @stage.addChild @setPosition 2, 6, @texture "B03"
+        @stage.addChild @setPosition 3, 6, @texture "B03"
+        @stage.addChild @setPosition 4, 6, @texture "B03"
+        @stage.addChild @setPosition 5, 6, @texture "B03"
+        # Sand Grass
+        @stage.addChild @setPosition 5, 5, @texture "E08"
+        @stage.addChild @setPosition 4, 5, @texture "E08"
+        @stage.addChild @setPosition 3, 5, @texture "E08"
+        @stage.addChild @setPosition 2, 5, @texture "E08"
+        @stage.addChild @setPosition 1, 5, @texture "E08"
+        @stage.addChild @setPosition 0, 5, @texture "D08"
+        @stage.addChild @setPosition 0, 4, @texture "D07"
+        # Sand
+        @stage.addChild @setPosition 1, 4, @texture "E07"
+
+
+        @startGameLoop()
+
+#        slice1 = PIXI.Sprite.fromFrame("edge_01").texture
+#        slice2 = PIXI.Sprite.fromFrame("step_01").texture
+#        clip = new PIXI.MovieClip [slice1, slice2]
+#        clip.gotoAndPlay 0
+#        clip.animationSpeed = .1
+#
+#        clip.position.x = 32
+#        clip.position.y = 64
+#
+#        grass1 = PIXI.Sprite.fromFrame "U14"
+#        grass1.position.x = 200
+#        grass1.position.y = 64
+#
+#        @stage.addChild clip
+#        @stage.addChild grass1
+
+    setPosition: (x, y, texture) ->
+        texture.position.x = x * 32
+        texture.position.y = y * 32
+        texture
+
+    animatedTexture: (texture1, texture2) ->
+        frame1 = PIXI.Sprite.fromFrame(texture1).texture
+        frame2 = PIXI.Sprite.fromFrame(texture2).texture
+        texture = new PIXI.MovieClip [frame1, frame2]
+        texture.gotoAndPlay 0
+        texture.animationSpeed = .1
+        texture
+
+    texture: (texture) ->
+        PIXI.Sprite.fromFrame texture
+
     startGameLoop: ->
         browserFrameHook = =>
             @nextAnimationFrame()
@@ -46,22 +130,6 @@

         requestAnimationFrame browserFrameHook

-        slice1 = PIXI.Sprite.fromFrame("edge_01").texture
-        slice2 = PIXI.Sprite.fromFrame("step_01").texture
-        clip = new PIXI.MovieClip [slice1, slice2]
-        clip.gotoAndPlay 0
-        clip.animationSpeed = .1
-
-        clip.position.x = 32
-        clip.position.y = 64
-
-        grass1 = PIXI.Sprite.fromFrame "U14"
-        grass1.position.x = 200
-        grass1.position.y = 64
-
-        @stage.addChild clip
-        @stage.addChild grass1
-
     nextAnimationFrame: ->
         @renderer.render @stage
 #        @slice1.position.x += 1


render a sprite

render a sprite

--- client/client.coffee
+++ client/client.coffee
@@ -1,4 +1,5 @@
 window.AdventureBattle = class AdventureBattle
+    assetPaths: ['static/assets/wall.json']
     viewport: null
     player: null

@@ -19,12 +20,15 @@
     setupGame: ->
         @setupPixi()
         @setupConnection()
-        @startGameLoop()

     setupPixi: ->
         @stage = new PIXI.Stage 0xf5f5f5
         @renderer = new PIXI.WebGLRenderer 1024, 576
         @viewport.append @renderer.view
+
+        loader = new PIXI.AssetLoader @assetPaths
+        loader.onComplete = => @startGameLoop()
+        loader.load()

     setupConnection: ->
         @socket = io.connect 'http://localhost:5000'
@@ -42,5 +46,10 @@

         requestAnimationFrame browserFrameHook

+        slice1 = PIXI.Sprite.fromFrame("edge_01")
+        slice1.position.x = 32
+        slice1.position.y = 64
+        this.stage.addChild(slice1)
+
     nextAnimationFrame: ->
-        @renderer.render @stage
+        @renderer.render @stage


check all files are present in BinaryData collection

--- client/config.coffee
+++ client/config.coffee
@@ -9,6 +9,23 @@
   my_context = coll.simpleSchema().namedContext()
   my_context.validate(doc)
   invalid_keys = my_context.invalidKeys()
+  if @name=="BinaryData"
+    console.log "checking archives for",doc._id
+    # check existance of files where they should exist.
+    fileObj = get_fileObj(doc, "archive")
+    if !fileObj?
+      console.error "for BinaryData doc",doc._id,"archive",doc.archiveId,"should exist, but it does not."
+
+    if doc.cacheId?
+      fileObjCache = get_fileObj(doc, "thumb")
+      if !fileObjCache?
+        console.error "for BinaryData doc",doc._id,"cache",doc.cacheId,"should exist, but it does not."
+
+    if doc.thumbId?
+      fileObjThumb = get_fileObj(doc, "thumb")
+      if !fileObjThumb?
+        console.error "for BinaryData doc",doc._id,"thumb",doc.thumbId,"should exist, but it does not."
+
   if invalid_keys.length > 0
     console.log "for doc",doc
     console.warn "invalid keys",invalid_keys


don't put cursor and eol when editing existing fns

--- client/editor.coffee
+++ client/editor.coffee
@@ -224,8 +224,8 @@
   open: (ee) ->
     originalValue = ee.text; cm.setValue ee.text; cm.focus()
     line = ee.currentRow; col = ee.currentColumn || 0
-    cm.setCursor line, if line || col then col else cm.getLine(0).length
-    cm.scrollIntoView null, $e.height() / 2
+    if line == col == 0 && ee.text.indexOf('\n') < 0 then col = ee.text.length
+    cm.setCursor line, col; cm.scrollIntoView null, $e.height() / 2
     for l in ee.lineAttributes?.stop then cm.setGutterMarker l, 'breakpoints', createBreakpointElement()
     if opener then $('title').text ee.name
     return


editor.coffee: small touches and more comments

--- client/editor.coffee
+++ client/editor.coffee
@@ -1,53 +1,55 @@
 autocompletion = require './autocompletion'

-module.exports = (e, opts = {}) ->
+module.exports = (e, opts = {}) -> # opts contains callbacks to ide.coffee
   b = (cssClasses, description) -> ""
   ($e = $ e).html """

-      #{[ # the first two buttons are placed on the right-hand side through CSS; in a floating window they are hidden
-        b 'tb-EP  last',    'Quit this function'
+      #{[
+        # The first two buttons are placed on the right-hand side through CSS.  In a floating window they are hidden.
+        # CSS classes "first" and "last" indicate button grouping.
+        b 'tb-EP  last',      'Quit this function'
         b 'tb-pop first',     'Edit in a floating window'
-        b 'tb-ER  first',    'Execute line'
-        b 'tb-TC',           'Trace into expression'
-        b 'tb-BK',           'Go back one line'
-        b 'tb-FD',           'Skip current line'
-        b 'tb-BH',           'Continue trace'
-        b 'tb-RM',           'Continue execution'
-        b 'tb-MA',           'Restart all threads'
-        b 'tb-ED',           'Edit name'
-        b 'tb-WI',           'Interrupt'
-        b 'tb-CBP',          'Clear trace/stop/monitor for this object'
-        b 'tb-LN  last',     'Toggle line numbers'
+        b 'tb-ER  first',     'Execute line'
+        b 'tb-TC',            'Trace into expression'
+        b 'tb-BK',            'Go back one line'
+        b 'tb-FD',            'Skip current line'
+        b 'tb-BH',            'Continue trace'
+        b 'tb-RM',            'Continue execution'
+        b 'tb-MA',            'Restart all threads'
+        b 'tb-ED',            'Edit name'
+        b 'tb-WI',            'Interrupt'
+        b 'tb-CBP',           'Clear trace/stop/monitor for this object'
+        b 'tb-LN  last',      'Toggle line numbers'
         ''
         '<input class="tb-search text-field">'
-        b 'tb-NX first',     'Search for next match'
-        b 'tb-PV',           'Search for previous match'
-        b 'tb-case last',    'Match case'
+        b 'tb-NX first',      'Search for next match'
+        b 'tb-PV',            'Search for previous match'
+        b 'tb-case last',     'Match case'
       ].join ''}

       #{[
-        b 'tb-EP  last',    'Save changes and return'
+        b 'tb-EP  last',      'Save changes and return'
         b 'tb-pop first',     'Edit in a floating window'
-        b 'tb-LN  first',    'Toggle line numbers'
-        b 'tb-AO',           'Comment selected text'
-        b 'tb-DO  last',     'Uncomment selected text'
+        b 'tb-LN  first',     'Toggle line numbers'
+        b 'tb-AO',            'Comment selected text'
+        b 'tb-DO  last',      'Uncomment selected text'
         ''
         '<input class="tb-search text-field">'
-        b 'tb-NX  first',    'Search for next match'
-        b 'tb-PV',           'Search for previous match'
-        b 'tb-case last',    'Match case'
+        b 'tb-NX  first',     'Search for next match'
+        b 'tb-PV',            'Search for previous match'
+        b 'tb-case last',     'Match case'
         ''
-        b 'tb-refac-m first','Refactor text as method'
-        b 'tb-refac-f',      'Refactor text as field'
-        b 'tb-refac-p last', 'Refactor text as property'
+        b 'tb-refac-m first', 'Refactor text as method'
+        b 'tb-refac-f',       'Refactor text as field'
+        b 'tb-refac-p last',  'Refactor text as property'
       ].join ''}

   """
   b = null

-  volatileLine = null
+  volatileLine = null # the line number of the empty line inserted when cursor is at eof and you press <down>

   cm = CodeMirror $e.find('.cm')[0],
     fixedGutter: false, firstLineNumber: 0, lineNumberFormatter: (i) -> "[#{i}]"
@@ -57,10 +59,8 @@
       Tab: -> c = cm.getCursor(); opts.autocomplete cm.getLine(c.line), c.ch; return
       Down: ->
         l = cm.getCursor().line
-        if l == cm.lineCount() - 1 && !/^\s*$/.test cm.getLine l
-          cm.execCommand 'goDocEnd'; cm.execCommand 'newlineAndIndent'; volatileLine = l + 1
-        else
-          cm.execCommand 'goLineDown'
+        if l != cm.lineCount() - 1 || /^\s*$/.test cm.getLine l then cm.execCommand 'goLineDown'
+        else cm.execCommand 'goDocEnd'; cm.execCommand 'newlineAndIndent'; volatileLine = l + 1
         return

   cm.on 'cursorActivity', ->
@@ -80,7 +80,7 @@
     SC: -> $tb.find('.tb-search:visible').focus(); return # Search
     EP: -> # Exit (and save changes)
       v = cm.getValue()
-      if v != originalValue || breakpoints.join() != originalBreakpoints
+      if v != originalText || breakpoints.join() != originalBreakpoints
         for l in breakpoints then cm.setGutterMarker l, 'breakpoint', null
         opts.save cm.getValue(), breakpoints[..].sort (x, y) -> x - y
       else
@@ -156,7 +156,7 @@
   ###

   createBreakpointElement = -> $('
●
')[0]
-  breakpoints = []
+  breakpoints = [] # array of line numbers
   cm.on 'gutterClick', (cm, l) ->
     if breakpoints.indexOf(l) >= 0 then breakpoints.splice l, 1; cm.setGutterMarker l, 'breakpoints', null
     else breakpoints.push l; cm.setGutterMarker l, 'breakpoints', createBreakpointElement()
@@ -200,8 +200,8 @@
           s = stream.string[stream.pos..]; if ic then s = s.toLowerCase()
           i = s.indexOf q
           if !i then         stream.pos += q.length; 'searching'
-          else if i > 0 then stream.pos += i;        undefined
-          else               stream.skipToEnd();     undefined
+          else if i > 0 then stream.pos += i;        return
+          else               stream.skipToEnd();     return
     [q, ic]
   search = (backwards) ->
     [q, ic] = highlightSearch()
@@ -217,8 +217,8 @@
         cm.setSelection cm.posFromIndex(j), cm.posFromIndex j + q.length; scrollCursorIntoProminentView()
     false

-  originalValue = null # remember it so that on <esc> we can detect if anything changed
-  originalBreakpoints = '' # comma-separated string
+  # remember original text and breakpoints (comma-separated line numbers) to avoid pointless saving on EP
+  originalText = originalBreakpoints = ''

   hll = null # currently highlighted line

@@ -238,12 +238,11 @@

   scrollCursorIntoProminentView = -> # approximately to 1/3 of editor height; this might not work near the top or bottom
     h = $e.height(); {left, top} = cm.cursorCoords true, 'local'
-    cm.scrollIntoView left: left, right: left, top: top - h / 3, bottom: top + 2 * h / 3
-    return
-
-  updateSize: -> cm.setSize $e.width(), $e.parent().height() - $e.position().top - 28
+    cm.scrollIntoView left: left, right: left, top: top - h / 3, bottom: top + 2 * h / 3; return
+
+  updateSize: -> cm.setSize $e.width(), $e.parent().height() - $e.position().top - 28; return
   open: (ee) ->
-    cm.setValue originalValue = ee.text; cm.focus()
+    cm.setValue originalText = ee.text; cm.focus()
     line = ee.currentRow; col = ee.currentColumn || 0; if line == col == 0 && ee.text.indexOf('\n') < 0 then col = ee.text.length
     cm.setCursor line, col; cm.scrollIntoView null, $e.height() / 2
     breakpoints = ee.lineAttributes.stop[..]
@@ -252,12 +251,12 @@
     if opener then $('title').text ee.name
     return
   hasFocus: -> cm.hasFocus()
-  focus: -> cm.focus()
+  focus: -> cm.focus(); return
   insert: (ch) -> (if !cm.getOption 'readOnly' then c = cm.getCursor(); cm.replaceRange ch, c, c); return
   getValue: -> cm.getValue()
   getCursorIndex: -> cm.indexFromPos cm.getCursor()
-  setValue: (x) -> cm.setValue x
-  setCursorIndex: (i) -> cm.setCursor cm.posFromIndex i
+  setValue: (x) -> cm.setValue x; return
+  setCursorIndex: (i) -> cm.setCursor cm.posFromIndex i; return
   highlight: highlight
   getHighlightedLine: -> hll
   setDebugger: setDebugger


Warning that unique ID is necessary

Warning that unique ID is necessary

--- SingularityUI/app/components/common/input/DateEntry.cjsx
+++ SingularityUI/app/components/common/input/DateEntry.cjsx
@@ -15,6 +15,8 @@
                 timeZone: moment().format('zz')
             }).on('dp.change', changeFn) # value will be in event.date

+    # MUST pass in UNIQUE id in props.
+    # Otherwise the datetime picker will break in ways that aren't even very interesting
     render: ->


make more idiomatic

make more idiomatic

--- coffeescript/wordcount_test.coffee
+++ coffeescript/wordcount_test.coffee
@@ -1,20 +0,0 @@
-should = require 'should'
-exec = require('child_process').exec
-
-describe 'wordcount', ->
-  context 'given an empty file', ->
-    it 'does not output anything', (done) ->
-      exec 'echo "" | coffee wordcount.coffee', (err, stdout, stderr) ->
-        lines = stdout.split /\r?\n/
-        line.should.match /^$/ for line in lines
-        done()
-
-  context 'given a simple test case', ->
-    it 'outputs the proper word counts', (done) ->
-      exec 'echo "QQQ A a a aBC HA:qQq" | coffee wordcount.coffee', (err, stdout, stderr) ->
-        lines = stdout.split /\r?\n/
-        lines[0].should.match /^a 3\s*/
-        lines[1].should.match /^abc 1\s*/
-        lines[2].should.match /^ha 1\s*/
-        lines[3].should.match /^qqq 2\s*/
-        done()


Added docs describing how you can instantiate livedb clients.

--- lib/index.coffee
+++ lib/index.coffee
@@ -11,6 +11,41 @@
 # I'm not really sure how the memory store should be exported...
 exports.memory = require './memory'

+# The client is created using either an options object or a database backend
+# which is used as both oplog and snapshot.
+#
+# Eg:
+#  db = require('livedb-mongo')('localhost:27017/test?auto_reconnect', {safe:true});
+#  livedb = require('livedb').client(db);
+#
+# Or using an options object:
+#
+#  db = require('livedb-mongo')('localhost:27017/test?auto_reconnect', {safe:true});
+#  livedb = require('livedb').client({db:db});
+#
+# If you want, you can use a different database for both snapshots and operations:
+#
+#  snapshotdb = require('livedb-mongo')('localhost:27017/test?auto_reconnect', {safe:true});
+#  oplog = {writeOp:..., getVersion:..., getOps:...}
+#  livedb = require('livedb').client({snapshotDb:snapshotdb, oplog:oplog});
+#
+# Other options:
+#
+# - redis:<redis client>. This can be specified if there is any further
+#     configuration of redis that you want to perform. The obvious examples of
+#     this are when redis is running on a remote machine, redis requires
+#     authentication or you want to use something other than redis db 0.
+#
+# - redisObserver:<redis client>. Livedb actually needs 2 redis connections,
+#     because redis doesn't let you use a connection with pubsub subscriptions
+#     to edit data. Livedb will automatically try to clone the first connection
+#     to make the observer connection, but if you want to do anything
+#     particularly fancy, you can provide it explicitly.
+#
+# - extraDbs:{}  This is used to register extra database backends which will be
+#     notified whenever operations are submitted. They can also be used in
+#     queries.
+#
 exports.client = (options) ->
   # Database which stores the documents.
   snapshotDb = options.snapshotDb or options.db or options


protocol: Send empty component source when failing

A bit of a workaround for fbp-spec...

--- src/protocol.coffee
+++ src/protocol.coffee
@@ -96,8 +96,11 @@
       # Regular component
       proto.coordinator.getComponentSource payload.name, (err, source) ->
         if err
+          # return empty component response, as clients handle errors badly...
+          source =
+            language: 'javascript'
+            code: ""
           proto.transport.send 'component', 'error', { name: payload.name, error: err.message }, ctx
-          return
         source.name = payload.name
         proto.transport.send 'component', 'source', source, ctx


abc note parser

abc note parser

--- lib/notes.coffee
+++ lib/notes.coffee
@@ -189,3 +189,25 @@
     pitch = new RelativePitch name, getInt(2), REFERENCE_NOTE if name?
     new Note duration, pitch

+class @AbcNoteParser
+  constructor: (@_noteLength) ->
+
+  parse: (abcNote) ->
+    if abcNote.note != 'rest'
+      noteName = abcNote.note[0].toLowerCase()
+      if abcNote.note.length == 1
+        octave = if abcNote.note == noteName then 5 else 4
+      else
+        octave = if abcNote.note[2] == ',' then 3 else 6
+
+      if (accidental = abcNote.accidental)?
+        if accidental == 'sharp'
+          noteName = "#{noteName}#"
+        else if accidental == 'flat'
+          noteName = "#{noteName}b"
+        else
+          throw "Unknown accidental: #{accidental}"
+      pitch = new RelativePitch noteName, octave, REFERENCE_NOTE if noteName?
+    duration = new Duration abcNote.duration, @_noteLength
+    new Note duration, pitch
+


scale bubbles onClick

scale bubbles onClick

--- coffee/resume.coffee
+++ coffee/resume.coffee
@@ -217,8 +217,8 @@

 					r = size.width*item.timespan/(max*2*Math.PI)
 					r += max/(5*r) # scale up small bubbles
-					x = accumulator + r + 5
-					y = if not n then r+5 else (size.height - height - r)/2 # .active stroke-width = 5px
+					x = accumulator + r + 5 + .2*r
+					y = if not n then 1.2*r+5 else (size.height - height - r)/2 + .2*r # .active stroke-width = 5px

 					item.timespan = x

@@ -354,6 +354,7 @@

 				# animate
 				element.animate @animations.inactive
+				element.transform 's1'

 				# update model
 				@model.set 'active', null
@@ -378,7 +379,10 @@
 			classList.add 'active'

 			# animate
-			element.animate @animations.active
+			element
+			.toFront()
+			.animate(@animations.active)
+			.transform('s1.1')

 			# store in model
 			@model.set 'active', element


Standardise language in pull request.

Standardise language in pull request.

--- app/frontend.coffee
+++ app/frontend.coffee
@@ -23,10 +23,10 @@
 respond500 = (req, res, err) ->
 	res.writeHead(500, {"Content-Type": "text/plain"})
 	res.write('HTTP 500, Paraplyen lekker. \n\n')
-	res.write('Viss du ser ein av kodemonkeyene bak prosjektet \n')
+	res.write('Hvis du ser en av kodeapene bak prosjektet \n')
 	res.write('så kan du gi dem denne feilmeldingen: \n')
 	res.write(new Buffer(err.message).toString('base64'))
-	res.write("\n takk for hjelpen. Ha ein fin dag. :)")
+	res.write("\n takk for hjelpen. Ha en fin dag.")
 	res.end()

 exports.handle = (request, response) ->


Update base_cell.coffee

Update base_cell.coffee

--- app/cells/base_cell.coffee
+++ app/cells/base_cell.coffee
@@ -2,7 +2,7 @@
 # ovo probat

 Sequelize = require('sequelize');
-sequelize = new Sequelize 'stash_buckets_new', 'dux', '!Netlife',
+sequelize = new Sequelize 'stash_buckets_new', 'dux', 'bla',
   host: 'localhost',
   dialect: 'postgres',
   pool:


vagrant: disable kontrol

vagrant: disable kontrol

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -46,7 +46,7 @@
     port        : 7474
   runNeo4jFeeder: yes
   runGoBroker   : yes
-  runKontrol    : yes
+  runKontrol    : no
   runRerouting  : yes
   runUserPresence: yes
   runPersistence: no


Config: add missing kodingme region

Config: add missing kodingme region

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -20,6 +20,7 @@
   sj            : "sj"
   aws           : "aws"
   premium       : "vagrant"
+  kodingme	: "kodingme"

 cookieMaxAge = 1000 * 60 * 60 * 24 * 14 # two weeks
 cookieSecure = no


rm ANOTHER stray console

rm ANOTHER stray console

--- components/feed/client/feed.coffee
+++ components/feed/client/feed.coffee
@@ -167,7 +167,6 @@
     return unless @lastItem?.length

     top = if @lastItem.offset() then @lastItem.offset().top else 0
-    console.log 'in range', @scrollTop + 1500, top
     if @scrollTop + 1500 > top
       if @$el.is(':visible')
         @fetchMoreItems()


Fix bug

Fix bug

--- lib/proxy.coffee
+++ lib/proxy.coffee
@@ -114,6 +114,8 @@
                 res.end body
                 # Emit response events to plugins
                 resolvedBody = yield resolveBody response.headers['content-encoding'], body
+                if !resolvedBody?
+                  throw new Error('Empty Body')
                 if response.statusCode == 200
                   self.emit 'game.on.response', req.method, parsed.pathname, resolvedBody, querystring.parse reqBody.toString()
                 else


Issue #795 -- clean up low precedence levels

--- grammar.coffee
+++ grammar.coffee
@@ -569,10 +569,10 @@
   ["left",      'LOGIC']
   ["left",      '.']
   ["nonassoc",  'INDENT', 'OUTDENT']
+  ["right",     '=', ':', 'COMPOUND_ASSIGN', 'RETURN']
   ["right",     'WHEN', 'LEADING_WHEN', 'FORIN', 'FOROF', 'FROM', 'TO', 'BY', 'THROW']
-  ["right",     'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS']
-  ["right",     '=', ':', 'COMPOUND_ASSIGN', 'RETURN']
-  ["right",     'POST_IF', 'POST_UNLESS']
+  ["right",     'IF', 'UNLESS', 'POST_IF', 'POST_UNLESS', 'ELSE', 'FOR', 'WHILE',
+                'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS']
 ]

 # Wrapping Up


Add support for parsing logout responses.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -143,6 +143,8 @@
 # appear to be valid.
 parse_response_header = (dom) ->
   response = dom.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'Response')
+  if response.length is 0
+    response = dom.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'LogoutResponse')
   throw new Error("Expected 1 Response; found #{response.length}") unless response.length is 1

   response_header = {}
@@ -235,6 +237,29 @@
     .object()
     .value()

+parse_authn_response = (saml_response, sp_private_key, idp_certificate, cb) ->
+  user = {}
+  decrypted_assertion = null
+
+  async.waterfall [
+    (cb_wf) ->
+      decrypt_assertion saml_response, sp_private_key, cb_wf
+    (result, cb_wf) ->
+      decrypted_assertion = (new xmldom.DOMParser()).parseFromString(result)
+      check_saml_signature result, idp_certificate, cb_wf
+    (cb_wf) -> async.lift(get_name_id) decrypted_assertion, cb_wf
+    (name_id, cb_wf) ->
+      user.name_id = name_id
+      async.lift(get_session_index) decrypted_assertion, cb_wf
+    (session_index, cb_wf) ->
+      user.session_index = session_index
+      async.lift(parse_assertion_attributes) decrypted_assertion, cb_wf
+    (assertion_attributes, cb_wf) ->
+      user = _.extend user, pretty_assertion_attributes(assertion_attributes)
+      user = _.extend user, attributes: assertion_attributes
+      cb_wf null, { user }
+  ], cb
+
 module.exports.ServiceProvider =
   class ServiceProvider
     constructor: (@issuer, @private_key, @certificate) ->
@@ -248,6 +273,7 @@
         uri = url.parse identity_provider.sso_login_url
         uri.query =
           SAMLRequest: deflated.toString 'base64'
+        console.log url.format(uri)
         cb null, url.format(uri), id

     # Returns user object, if the login attempt was valid.
@@ -262,7 +288,7 @@
       saml_response = null
       decrypted_assertion = null

-      user = {}
+      response = {}

       async.waterfall [
         (cb_wf) ->
@@ -271,27 +297,22 @@
           if (get_request)
             return zlib.inflateRaw raw, cb_wf
           setImmediate cb_wf, null, raw
-        (response, cb_wf) ->
-          saml_response = (new xmldom.DOMParser()).parseFromString(response.toString())
+        (response_buffer, cb_wf) ->
+          saml_response = (new xmldom.DOMParser()).parseFromString(response_buffer.toString())
           async.lift(parse_response_header) saml_response, cb_wf
         (response_header, cb_wf) =>
-          user = { response_header }
+          response = { response_header }
           cb_wf new Error("SAML Response was not success!") unless check_status_success(saml_response)
-          decrypt_assertion saml_response, @private_key, cb_wf
+          switch
+            when saml_response.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'Response').length is 1
+              response.type = 'authn_response'
+              parse_authn_response saml_response, @private_key, identity_provider.certificate, cb_wf
+            when saml_response.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'LogoutResponse').length is 1
+              response.type = 'logout_response'
+              setImmediate cb_wf, null, {}
         (result, cb_wf) ->
-          decrypted_assertion = (new xmldom.DOMParser()).parseFromString(result)
-          check_saml_signature result, identity_provider.certificate, cb_wf
-        (cb_wf) -> async.lift(get_name_id) decrypted_assertion, cb_wf
-        (name_id, cb_wf) ->
-          user.name_id = name_id
-          async.lift(get_session_index) decrypted_assertion, cb_wf
-        (session_index, cb_wf) ->
-          user.session_index = session_index
-          async.lift(parse_assertion_attributes) decrypted_assertion, cb_wf
-        (assertion_attributes, cb_wf) ->
-          user = _.extend user, pretty_assertion_attributes(assertion_attributes)
-          user = _.extend user, attributes: assertion_attributes
-          cb_wf null, user
+          _.extend response, result
+          cb_wf null, response
       ], cb

     # -- Optional


Changed meta tag.

Changed meta tag.

--- server/lib/crawler/staticpages/graphmeta.coffee
+++ server/lib/crawler/staticpages/graphmeta.coffee
@@ -10,7 +10,7 @@
   <meta property="og:title" content="Koding - A new way for developers to work."/>
   <meta property="og:url" content="https://koding.com"/>
   <meta property="og:type" content="website" />
-  <meta property="og:image" content="http://koding.com/images/kd-fluid-icon512.png"/>
+  <meta property="og:image" content="http://koding.com/images/koding_share.jpg"/>
   <meta property="og:image:secure_url" content="https://koding.com/images/kd-fluid-icon512.png"/>
   <meta property="og:image:type" content="JPG">
   <meta property="og:image:width" content="160">


allows show_tour to come down with user attributes

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -105,7 +105,8 @@
       'shortcuts_id',
       'avatar_image',
       'registered',
-      'post_address'
+      'post_address',
+      'show_tour'
     ]

   app.use artsyError.helpers


add turning effect for starred

add turning effect for starred

--- app/assets/javascripts/app/controllers/messages/article.js.coffee
+++ app/assets/javascripts/app/controllers/messages/article.js.coffee
@@ -13,6 +13,9 @@
   events:
     'click .star': 'toggleStarred'

+  elements:
+    '.star': 'star'
+
   constructor: ->
     super
     throw 'record required' unless @record
@@ -24,6 +27,7 @@

   toggleStarred: ->
     @record.toggleStarred()
+    @star.gfx(rotate: if @record.starred then '360deg' else '-360deg')

 class Compose extends Spine.Controller
   className: 'item me'


Using 'small,jpg' avatars to decrease loadingtime

--- app/assets/javascripts/controllers/dashboard_controller.js.coffee
+++ app/assets/javascripts/controllers/dashboard_controller.js.coffee
@@ -44,7 +44,7 @@
         users.push
           link:  "http://forums.hummingbird.me/users/"+thisName+"/activity"
           name:  thisName
-          image: listUserOrdr[user.user_id]['avatar_template'].replace("{size}", "thumb")
+          image: listUserOrdr[user.user_id]['avatar_template'].replace("{size}", "small").replace(".gif?", ".jpg?")
           title: user.description

       listElements.push


`reRender` -> `rerender`.

`reRender` -> `rerender`.

--- app/assets/javascripts/dot_ledger/views/goals/list_item.js.coffee
+++ app/assets/javascripts/dot_ledger/views/goals/list_item.js.coffee
@@ -30,10 +30,10 @@
     events:
       'click button.save': 'save'
       'submit form': 'save'
-      'change input': 'reRender'
-      'change select': 'reRender'
+      'change input': 'rerender'
+      'change select': 'rerender'

-    reRender: ->
+    rerender: ->
       @update()
       @render()


add possibilities to add permissions for all docTypes

--- lib/token.coffee
+++ lib/token.coffee
@@ -46,6 +46,10 @@
                 # Check if application can manage docType
                 if permissions[name][docType]?
                     console.log "#{name} is authorized to manage #{docType} "
+                    callback null, true, true
+                else if permissions[name]["all"]?
+                    console.log "#{name} is authorized to manage all docTypes" +
+                        " so, also #{docType} "
                     callback null, true, true
                 else
                     console.log "#{name} is NOT authorized to manage #{docType}"


Revert "Revert "screenshot addition""

Revert "Revert "screenshot addition""

This reverts commit 18cd21b9d8d30b9da2866d0e19ec79dffe3d705a.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -91,7 +91,9 @@
       try
         buf = image.toPng()
         if process.platform == "darwin"
-          filename = path.join path.homedir(), 'Desktop', "#{Date.now()}.png"
+          darwinPath = path.join path.homedir(), "Pictures", "Poi"
+          fs.ensureDirSync darwinPath
+          filename = path.join path.homedir(), "Pictures", "Poi", "#{Date.now()}.png"
         else
           fs.ensureDirSync path.join global.EXROOT, 'screenshots'
           filename = path.join global.EXROOT, 'screenshots', "#{Date.now()}.png"


Fixed jquery 1.9 attr() vs. prop() issue.

--- app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee
@@ -86,15 +86,15 @@
     )

     @el.delegate('[name="master_ticket_number"]', 'focus', (e) ->
-      $(e.target).parents().find('[name="radio"]').attr( 'checked', false )
+      $(e.target).parents().find('[name="radio"]').prop( 'checked', false )
     )

     @el.delegate('[name="radio"]', 'click', (e) ->
-        if $(e.target).attr('checked')
-          ticket_id = $(e.target).val()
-          ticket    = App.Collection.find( 'Ticket', ticket_id )
-          $(e.target).parents().find('[name="master_ticket_number"]').val( ticket.number )
-      )
+      if $(e.target).prop('checked')
+        ticket_id = $(e.target).val()
+        ticket    = App.Collection.find( 'Ticket', ticket_id )
+        $(e.target).parents().find('[name="master_ticket_number"]').val( ticket.number )
+    )

     @modalShow()


Add error domain to catch ECONNRESET from campfire

--- lifeguard.coffee
+++ lifeguard.coffee
@@ -153,31 +153,40 @@

   class @Campfire
     constructor: (@lifeguard,@opts) ->
-      Campfire = (require "campfire").Campfire
+      @d = require("domain").create()

-      @campfire_room = false
-      @campfire_queue = []
+      @d.on "error", (err) =>
+        if err.code == "ECONNRESET"
+          # ignore
+        else
+          console.log "Campfire error: ", err
+
+      @d.run =>
+        Campfire = (require "campfire").Campfire
+
+        @campfire_room = false
+        @campfire_queue = []

-      @campfire = new Campfire
-        account:  @opts.account
-        token:    @opts.token
-        ssl:      true
+        @campfire = new Campfire
+          account:  @opts.account
+          token:    @opts.token
+          ssl:      true

-      @campfire.join @opts.room, (err,room) =>
-        @campfire_room = room
+        @campfire.join @opts.room, (err,room) =>
+          @campfire_room = room

-        for msg in @campfire_queue
-          @campfire_room.speak msg
+          for msg in @campfire_queue
+            @campfire_room.speak msg

-        @campfire_queue = []
+          @campfire_queue = []

-      # -- monitor for notifies -- #
+        # -- monitor for notifies -- #

-      @lifeguard.on "notify", (msg) =>
-        if @campfire_room
-          @campfire_room.speak msg
-        else
-          @campfire_queue.push msg
+        @lifeguard.on "notify", (msg) =>
+          if @campfire_room
+            @campfire_room.speak msg
+          else
+            @campfire_queue.push msg


Don't correct artboard positions

Don't correct artboard positions

--- framer/Importer.coffee
+++ framer/Importer.coffee
@@ -170,16 +170,16 @@
 				layer.visible = true
 				points.push(layer.point)

-		# Calculate the artboard positions to always be 0,0.
-		pointOffset =
-			x: Math.min.apply(@, points.map (p) -> p.x)
-			y: Math.min.apply(@, points.map (p) -> p.y)
-
-		# Correct the layers
-		for layer in layers
-			if layer._info.kind is "artboard"
-				layer.x -= pointOffset.x
-				layer.y -= pointOffset.y
+		# # Calculate the artboard positions to always be 0,0.
+		# pointOffset =
+		# 	x: Math.min.apply(@, points.map (p) -> p.x)
+		# 	y: Math.min.apply(@, points.map (p) -> p.y)
+
+		# # Correct the layers
+		# for layer in layers
+		# 	if layer._info.kind is "artboard"
+		# 		layer.x -= pointOffset.x
+		# 		layer.y -= pointOffset.y

 	_correctLayer: (layer) ->


Remove listener that doesn't make sense since we have Universe

--- admin/static/coffee/namespaces/index.coffee
+++ admin/static/coffee/namespaces/index.coffee
@@ -2,7 +2,7 @@
 # This file extends the namespace module with functionality to present
 # the index of namespaces.
 module 'NamespaceView', ->
-    # Show a list of databases
+    # Show a list of databases, this view extends AbstractList
     class @DatabaseList extends UIComponents.AbstractList
         # Use a namespace-specific template for the namespace list
         template: Handlebars.compile $('#database_list-template').html()
@@ -18,18 +18,20 @@
         initialize: ->
             log_initial '(initializing) namespace list view'

+            # Create the modal we may need
             @add_database_dialog = new NamespaceView.AddDatabaseModal
             @add_namespace_dialog = new NamespaceView.AddNamespaceModal
             @remove_namespace_dialog = new NamespaceView.RemoveNamespaceModal

+
             super databases, NamespaceView.DatabaseListElement, '.collapsible-list', {}, 'database', 'cluster'

             @datacenters_length = -1
             @databases_length = -1
-            datacenters.on 'all', @update_button_create_namespace
             databases.on 'all', @update_button_create_namespace
             @can_create_namespace = true

+        # User cannot create a namespace if there is no database
         update_button_create_namespace: =>
             if databases.length is 0 and @can_create_namespace is true
                 @.$('.add-namespace').prop 'disabled', 'disabled'
@@ -97,7 +99,6 @@

         destroy: =>
             super
-            datacenters.off 'all', @update_button_create_namespace
             databases.off 'all', @update_button_create_namespace
             @add_database_dialog.destroy()
             @add_namespace_dialog.destroy()


renable

renable

--- app/assets/javascripts/app/models/conversation.js.coffee
+++ app/assets/javascripts/app/models/conversation.js.coffee
@@ -33,7 +33,7 @@
     }

   open: ->
-    return if @read
+    return if @read is true
     @read = true
     @save()


Added scheduled job to ask for weekly achievements at 4pm Eastern time.

--- scripts/awesome.team.coffee
+++ scripts/awesome.team.coffee
@@ -16,6 +16,7 @@
 module.exports = (robot) ->

   $baseurl = process.env.AWESOME_API_URL || "http://localhost:"+process.env.PORT
+  CronJob = require('cron').CronJob

   youre_awesome = [
     "http://who-is-awesome.com/who-is-awesome.jpg",
@@ -25,6 +26,12 @@
     "https://sheerepiphany.files.wordpress.com/2015/04/a.jpg",
     "http://i.imgur.com/aimsF.jpg"
   ]
+
+  achievementReminder = new CronJob '00 00 16 * * 5', ->
+    for room, board of robot.brain.data._private.board
+      robot.messageRoom room, "Hello " + robot.brain.data._private.team[room].name + "!"
+      robot.messageRoom room, "Are there any achievements from the week that need to be added to your board '" + board.name + "'?"
+  , null, true, 'America/New_York'

   robot.hear /who(’|'| i)s awesome/i, (msg) ->
     msg.reply msg.random youre_awesome