Begin working on package sync within Atom

--- atom/init.coffee
+++ atom/init.coffee
@@ -4,7 +4,12 @@
 path = require 'path'
 VIRTUAL_ENV_NAME = '.virtualenv'

-# Activate Python virtualenv for this project directory if it exists
+LOCAL_PKG_DIR_PATH = atom.packages.getPackageDirPaths()[0]
+REMOTE_PKG_LIST_PATH = fs.readlinkSync(
+  path.join(path.dirname(LOCAL_PKG_DIR_PATH), 'packages.txt'))
+PKG_SYNC_DELAY = 1000
+
+# Activates Python virtualenv for this project directory if it exists
 activateVirtualenv = ->

   project = atom.project.getPaths()[0]
@@ -17,8 +22,43 @@
           virtualenv_bin = path.join(virtualenv, 'bin')
           process.env.PATH = [virtualenv_bin, process.env.PATH].join ':'
           process.env.VIRTUAL_ENV = virtualenv
-          console.log "Activated virtualenv at #{virtualenv}"
+          console.log("Activated virtualenv at #{virtualenv}")
       )
+
+
+# Retrieves the list of all user-installed local packages
+getLocalPkgList = ->
+  pkgList = fs.readdirSync LOCAL_PKG_DIR_PATH
+  return pkgList.filter (pkg) -> (pkg.indexOf('.') == -1)
+
+
+# Retrueves the list of all synced packages
+getRemotePkgList = ->
+    pkgList = fs.readFileSync(REMOTE_PKG_LIST_PATH, {
+      encoding: 'utf8'
+    }).trimRight().split('\n')
+    return pkgList
+
+
+# Pushes local package list to remote when packages are installed/uninstalled
+pushPkgList = ->
+  localPkgList = getLocalPkgList()
+  remotePkgList = getRemotePkgList()
+  if localPkgList.join(',') isnt remotePkgList.join(',')
+    console.log('Pushing local package list to remote...')
+    fs.writeFile(REMOTE_PKG_LIST_PATH, localPkgList.join('\n') + '\n')
+  else
+    console.log('Already up-to-date')
+
+# Initializes package sync within Atom
+initializePackageSync = ->
+  timeout = null
+  atom.packages.onDidActivatePackage (pkg) ->
+    clearTimeout(timeout)
+    timeout = setTimeout(pushPkgList, PKG_SYNC_DELAY)
+  atom.packages.onDidDeactivatePackage (pkg) ->
+    clearTimeout(timeout)
+    timeout = setTimeout(pushPkgList, PKG_SYNC_DELAY)

 # Explicitly define PATH and detect project virtualenvs
@@ -28,3 +68,4 @@
 process.env.PYTHONIOENCODING = 'utf_8'
 # Prevent Python from generating bytecode (.pyc, .pyo) files
 process.env.PYTHONDONTWRITEBYTECODE = '1'
+initializePackageSync()


Fix Atom bug where tree-sitter grammars were ignored

See <https://github.com/atom/atom/issues/17029#issuecomment-457084440>.

--- atom/init.coffee
+++ atom/init.coffee
@@ -31,6 +31,17 @@
     exec("open #{projectPaths[0]}")
 )

+# Override getGrammars to work around bug with tree-sitter grammars never
+# applying; see
+# (https://github.com/atom/atom/issues/17029#issuecomment-457084440)
+atom.grammars.getGrammars = ->
+  allGrammars = atom.grammars.textmateRegistry.getGrammars()
+  tsGrammars = Object.values(atom.grammars.treeSitterGrammarsById)
+  combinedGrammars = tsGrammars.concat(allGrammars)
+  combinedGrammarNames = combinedGrammars.map (grammar) -> grammar.name
+  return combinedGrammars.filter (grammar, g) ->
+    return (grammar.constructor.name is 'TreeSitterGrammar') or (grammar.constructor.name isnt 'TreeSitterGrammar' and combinedGrammarNames.indexOf(grammar.name) is g)
+
 # Add command to copy to clipboard an array of the editor's current cursor
 # scopes
 atom.commands.add('atom-text-editor:not([mini])', 'editor:copy-cursor-scope', ->


More robust mdate filter

More robust mdate filter

--- clients/antranet/app/coffee/filters.coffee
+++ clients/antranet/app/coffee/filters.coffee
@@ -31,5 +31,8 @@
     ])
     .filter('mdate', ['version', (date, format) ->
         return (date, format) ->
-            return moment(date).format(format)
+            if date
+                return moment(date).format(format)
+            else
+                return ""
     ])


remove job status test

remove job status test

--- server/job.coffee
+++ server/job.coffee
@@ -605,16 +605,7 @@
       cluster: job_result.terms('_cid')
   res.limits = {} # may not be worth reporting on limit index in new structure
   job_limit.each 'NOT last:*', (lm) -> res.limits[lm.group ? lm._id] = {date:lm.created_date,limit:lm.limit}
-  job_job.each 'NOT done:true', {_source:['_id','count','processed']}, (j) ->
-    console.log j
-    console.log (j.count - (j.processed ? 0))
-    if j.processed? and typeof j.processed isnt 'number'
-      j.processed = j.processed.length
-      job_job.update j._id, {processed: j.processed}
-      res.fixprocessed ?= 0
-      res.fixprocessed += 1
-    res.jobs.waiting += (j.count - (j.processed ? 0))
-    console.log res.jobs.waiting
+  job_job.each 'NOT done:true', {_source:['_id','count','processed']}, (j) -> res.jobs.waiting += (j.count - (j.processed ? 0))
   return res

 API.job.orphans = (remove=false,types=['process','result']) ->


Hide warning

Hide warning

--- core/KD.coffee
+++ core/KD.coffee
@@ -96,7 +96,7 @@
     @subscriptions

   registerInstance : (anInstance)->
-    warn "Instance being overwritten!!", anInstance if @instances[anInstance.id]
+    # warn "Instance being overwritten!!", anInstance if @instances[anInstance.id]
     @instances[anInstance.id] = anInstance
     @classes[anInstance.constructor.name] ?= anInstance.constructor


fix https://github.com/sagemathinc/smc/issues/116, since so easy...

--- src/smc-webapp/project_miniterm.cjsx
+++ src/smc-webapp/project_miniterm.cjsx
@@ -36,6 +36,14 @@
 {ErrorDisplay, Icon} = require('./r_misc')

 {salvus_client} = require('./salvus_client')  # used to run the command -- could change to use an action and the store.
+
+output_style =
+    position  : 'absolute'
+    zIndex    : 1
+    width     : '93%'
+    boxShadow : '0px 0px 7px #aaa'
+    maxHeight : '450px'
+    overflow  : 'auto'

 exports.MiniTerminal = MiniTerminal = rclass
     displayName : 'MiniTerminal'
@@ -116,7 +124,7 @@

                 e.preventDefault(); @setState(stdout:'', error:'')}
                    href=''
-                   style={right:'5px', top:'0px', color:'#666', fontSize:'14pt', position:'absolute'}>
+                   style={right:'10px', top:'0px', color:'#666', fontSize:'14pt', position:'absolute'}>
                        <Icon name='times' />

                 {x}
@@ -151,7 +159,7 @@
                     </InputGroup>
                 </FormGroup>
             </form>
-

+

                 {@render_output(@state.error, {color:'darkred', margin:0})}
                 {@render_output(@state.stdout, {margin:0})}


Fixed logout issue, chooseItem method was blocking logout method from executing

--- choosifyer.coffee
+++ choosifyer.coffee
@@ -74,7 +74,7 @@

     Meteor.startup(() ->

-        Meteor.methods(
+        Meteor.methods(
             unselectAll: (id) ->
                 things = Things.find({createdBy: Meteor.userId()}, {sort:{text:1}});

@@ -110,7 +110,7 @@
                 min = arrayOfVictims.length * 4 ; max = arrayOfVictims.length * 7
                 spinLength =  Math.random() * (max - min) + min
                 prev = null
-                timeToSleep = 1000/30
+                timeToSleep = 1000/30

                 selectNext = (array, index, timeOut, iterationsLeft, firstCall) ->
@@ -135,9 +135,10 @@

                         nextIndex = if index is array.length - 1  then 0 else index + 1

-                        Meteor.setTimeout(( () -> selectNext(array, nextIndex, timeOut, --iterationsLeft, false) ), timeOut)
+                        Meteor.setTimeout(( () -> selectNext(array, nextIndex, timeOut, --iterationsLeft, false)), timeOut)
                     else 0
                 selectNext(arrayOfVictims, 0, timeToSleep, spinLength, true)
+                this.unblock()
             insertItem: (text) ->
                 Things.insert(
                     text: text,


Fix error messages.

Fix error messages.

--- tasks/lib/liquid-ext.coffee
+++ tasks/lib/liquid-ext.coffee
@@ -1,11 +1,10 @@
-
 Q = require('q')
 module.exports = Liquid = require('liquid-node')

 Liquid.Template.registerTag "block", do ->
   class BlockBlock extends Liquid.Block
     Syntax = /(\w+)/
-    SyntaxHelp = "Syntax Error in 'extends' - Valid syntax: extends [templateName]"
+    SyntaxHelp = "Syntax Error in 'block' - Valid syntax: block [templateName]"

     constructor: (tagName, markup, tokens, template) ->
       match = Syntax.exec(markup)
@@ -37,7 +36,7 @@
 Liquid.Template.registerTag "include", do ->
   class IncludeTag extends Liquid.Tag
     Syntax = /([a-z0-9\/\\_-]+)/i
-    SyntaxHelp = "Syntax Error in 'include' - Valid syntax: extends [templateName]"
+    SyntaxHelp = "Syntax Error in 'include' - Valid syntax: include [templateName]"

     constructor: (tagName, markup, tokens, template) ->
       match = Syntax.exec(markup)


added convert function * -> rgb -> *

added convert function * -> rgb -> *

--- client/app.coffee
+++ client/app.coffee
@@ -39,274 +39,283 @@
         result[key] = Math.max(bounds.min, Math.min(bounds.max, values[key]))
       result

-  # ----------------- #
-  # -- rgb <-> rgb -- #
-  # ----------------- #
-  #rgb -> rgb
-  "rgb-to-rgb": (values) -> converter.bounds.validate "rgb", values # validate
-
-  # ----------------- #
-  # -- hsl <-> rgb -- #
-  # ----------------- #
-  #hsl -> rgb
-  "hsl-to-rgb": (values) -> # see: http://en.wikipedia.org/wiki/HSV_color_space
-    # validate input
-    values.h = values.h % 1
-    hsl = converter.bounds.validate "hsl", values
-    # logic
-    H = hsl.h * 6.0                           # H ∊ [0, 6[    hue * 6
-    C = (1 - Math.abs(2 * hsl.l - 1)) * hsl.s # C ∊ [0, 1]    chroma
-    X = C * (1 - Math.abs(H % 2 - 1))         # X ∊ [0, 1]    intermediate
-    r = g = b = 0
-    r = C if (0 <= H < 1) or (5 <= H < 6)
-    r = X if (1 <= H < 2) or (4 <= H < 5)
-    g = C if (1 <= H < 3)
-    g = X if (0 <= H < 1) or (3 <= H < 4)
-    b = C if (3 <= H < 5)
-    b = X if (2 <= H < 3) or (5 <= H < 6)
-    m = hsl.l - 0.5 * C # match lightness
-    # validate output
-    converter.bounds.validate "rgb", {r: r + m, g: g + m, b: b + m}
-
-  #rgb -> hsl
-  "rgb-to-hsl": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=18#text18
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    a = Math.min(rgb.r, rgb.g, rgb.b) # a ∊ [0, 1]    min value
-    z = Math.max(rgb.r, rgb.g, rgb.b) # z ∊ [0, 1]    max value
-    d = (z - a)                       # d ∊ [0, 1]    delta value
-    l = (z + a) / 2                   # l ∊ [0, 1]    lightness
-    h = s = 0
-    if d > 0 # color isn't gray
-      s = d / if l < 0.5 then z + a else 2 - z - a # saturation
-      d2 = d / 2 # delta half
-      dr = (((z - rgb.r) / 6) + d2) / d # delta red
-      dg = (((z - rgb.g) / 6) + d2) / d # delta green
-      db = (((z - rgb.b) / 6) + d2) / d # delta blue
-      if      rgb.r is z then h = (0 / 3) + db - dg
-      else if rgb.g is z then h = (1 / 3) + dr - db
-      else if rgb.b is z then h = (2 / 3) + dg - dr
-      h += 1 if h < 0
-      h -= 1 if h >= 1
-    # validate output
-    converter.bounds.validate "hsl", {h: h, s: s, l: l}
-
-  # ----------------- #
-  # -- hsv <-> rgb -- #
-  # ----------------- #
-  #hsv -> rgb
-  "hsv-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=21#text21
-    # validate input
-    values.h = values.h % 1
-    hsv = converter.bounds.validate "hsv", values
-    # logic
-    r = g = b = hsv.v
-    if hsv.s > 0
-      H = hsv.h * 6
-      v1 = hsv.v * (1 - hsv.s)
-      v2 = hsv.v * (1 - hsv.s * (H - vi))
-      v3 = hsv.v * (1 - hsv.s * (1 - (H - vi)))
-
-      r = hsv.v if (0 <= H < 1) or (5 <= H < 6)
-      r = v1    if (2 <= H < 4)
-      r = v2    if (1 <= H < 2)
-      r = v3    if (4 <= H < 5)
-      g = hsv.v if (1 <= H < 3)
-      g = v1    if (4 <= H < 6)
-      g = v2    if (3 <= H < 4)
-      g = v3    if (0 <= H < 1)
-      b = hsv.v if (3 <= H < 5)
-      b = v1    if (0 <= H < 2)
-      b = v2    if (5 <= H < 6)
-      b = v3    if (2 <= H < 3)
-    # validate output
-    converter.bounds.validate "rgb", {r: r, g: g, b: b}
-
-  #rgb -> hsv
-  "rgb-to-hsv": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=20#text20
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    a = Math.min(rgb.r, rgb.g, rgb.b) # a ∊ [0, 1]    min value
-    z = Math.max(rgb.r, rgb.g, rgb.b) # z ∊ [0, 1]    max value
-    d = (z - a)                       # d ∊ [0, 1]    delta value
-
-    v = z
-    h = s = 0
-    if d > 0 # color isn't gray
-      s = d / z # saturation
-      d2 = d / 2 # delta half
-      dr = (((z - rgb.r) / 6) + d2) / d # delta red
-      dg = (((z - rgb.g) / 6) + d2) / d # delta green
-      db = (((z - rgb.b) / 6) + d2) / d # delta blue
-      if      rgb.r is z then h = (0 / 3) + db - dg
-      else if rgb.g is z then h = (1 / 3) + dr - db
-      else if rgb.b is z then h = (2 / 3) + dg - dr
-      h += 1 if h < 0
-      h -= 1 if h >= 1
-    # validate output
-    converter.bounds.validate "hsv", {h: h, s: s, v: v}
-
-  # ----------------- #
-  # -- cmy <-> rgb -- #
-  # ----------------- #
-  #cmy -> rgb
-  "cmy-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
-    # validate input
-    cmy = converter.bounds.validate "cmy", values
-    # logic
-    r = 1 - cmy.c
-    g = 1 - cmy.m
-    b = 1 - cmy.y
-    # validate output
-    converter.bounds.validate "rgb", {r: r, g: g, b: b}
-
-  #rgb -> cmy
-  "rgb-to-cmy": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=11#text11
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    y = 1 - rgb.r
-    m = 1 - rgb.g
-    c = 1 - rgb.b
-    # validate output
-    converter.bounds.validate "cmy", {c: c, m: m, y: y}
-
-  # ------------------ #
-  # -- cmyk <-> rgb -- #
-  # ------------------ #
-  #cmyk -> rgb
-  "cmyk-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=21#text21
-    # validate input
-    cmyk = converter.bounds.validate "cmyk", values
-    # logic
-    c = (cmyk.c * (1 - cmyk.k) + cmyk.k)
-    m = (cmyk.m * (1 - cmyk.k) + cmyk.k)
-    y = (cmyk.y * (1 - cmyk.k) + cmyk.k)
-    # validate output
-    converter["cmy-to-rgb"] {c: c, m: m, y: y}
-
-  #rgb -> cmyk
-  "rgb-to-cmyk": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=13#text13
-    # validate input
-    cmy = converter["rgb-to-cmy"] values
-    # logic
-    k = 1
-    k = c if c < k
-    k = m if m < k
-    k = y if y < k
-
-    c = 0 if k is 1 # black
-    m = 0 if k is 1 # black
-    y = 0 if k is 1 # black
-
-    c = (c - k) / (1 - k) if k > 0
-    m = (m - k) / (1 - k) if k > 0
-    y = (y - k) / (1 - k) if k > 0
-    # validate output
-    converter.bounds.validate "cmyk", {c: c, m: m, y: y, k: k}
-
-  # ----------------- #
-  # -- XYZ <-> rgb -- #
-  # ----------------- #
-  #XYZ -> rgb
-  "XYZ-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
-    XYZ = converter.bounds.validate "XYZ", values
-    # logic
-    r = XYZ.X *  3.2406 + XYZ.Y * -1.5372 + XYZ.Z * -0.4986
-    g = XYZ.X * -0.9689 + XYZ.Y *  1.8758 + XYZ.Z *  0.0415
-    b = XYZ.X *  0.0557 + XYZ.Y * -0.2040 + XYZ.Z *  1.0570
-
-    r = if r > 0.0031308 then 1.055 * ( r ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * r
-    g = if g > 0.0031308 then 1.055 * ( g ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * g
-    b = if b > 0.0031308 then 1.055 * ( b ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * b
-    # validate output
-    converter.bounds.validate "rgb", {r: r, g: g, b: b}
-
-  #rgb -> XYZ
-  "rgb-to-XYZ": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=02#text2
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    r = if rgb.r > 0.04045 then ((rgb.r + 0.055) / 1.055) ^ 2.4 else rgb.r / 12.92
-    g = if rgb.g > 0.04045 then ((rgb.g + 0.055) / 1.055) ^ 2.4 else rgb.g / 12.92
-    b = if rgb.b > 0.04045 then ((rgb.b + 0.055) / 1.055) ^ 2.4 else rgb.b / 12.92
-
-    X = rgb.r * 0.4124 + rgb.g * 0.3576 + rgb.b * 0.1805
-    Y = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722
-    Z = rgb.r * 0.0193 + rgb.g * 0.1192 + rgb.b * 0.9505
-    # validate output
-    converter.bounds.validate "XYZ", {X: X, Y: Y, Z: Z}
-
-  # ----------------- #
-  # -- Yxy <-> rgb -- #
-  # ----------------- #
-  #Yxy -> rgb
-  "Yxy-to-rgb": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=04#text4
-    # validate input
-    Yxy = converter.bounds.validate "Yxy", values
-    # logic
-    X = Yxy.x * ( Yxy.Y / Yxy.y )
-    Y = Yxy.Y
-    Z = ( 1 - Yxy.x - Yxy.y ) * ( Yxy.Y / Yxy.y )
-    # validate output
-    converter["XYZ-to-rgb"] {X: X, Y: Y, Z: Z}
-
-  #rgb -> Yxy
-  "rgb-to-Yxy": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=03#text3
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    XYZ = converter["rgb-to-XYZ"] rgb
-    x = y = 1
-    if not (XYZ.X is XYZ.Y is XYZ.Z is 0)
-      x = X / ( XYZ.X + XYZ.Y + XYZ.Z )
-      y = Y / ( XYZ.X + XYZ.Y + XYZ.Z )
-    # validate output
-    converter.bounds.validate "Yxy", {Y: Y, x: x, y: y}
-
-  # ----------------- #
-  # -- lab <-> rgb -- #
-  # ----------------- #
-  #lab -> rgb
-  "lab-to-rgb": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=08#text8
-    # validate input
-    lab = converter.bounds.validate "lab", values
-    # logic
-    Y = (lab.l + 16 ) / 116
-    X = lab.a / 500 + Y
-    Z = Y - lab.b / 200
-
-    X = if X^3 > 0.008856 then X^3 else ( X - 16 / 116 ) / 7.787
-    Z = if Z^3 > 0.008856 then Z^3 else ( Z - 16 / 116 ) / 7.787
-    Y = if Y^3 > 0.008856 then Y^3 else ( Y - 16 / 116 ) / 7.787
-
-    X *= converter.bounds.XYZ.X
-    Y *= converter.bounds.XYZ.Y
-    Z *= converter.bounds.XYZ.Z
-    # validate output
-    converter["XYZ-to-rgb"] {X: X, Y: Y, Z: Z}
-
-  #rgb -> lab
-  "rgb-to-lab": (r, g, b) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=07#text7
-    # validate input
-    rgb = converter.bounds.validate "rgb", values
-    # logic
-    XYZ = converter["rgb-to-XYZ"] values
-    X = XYZ.X / converter.bounds.XYZ.X
-    Y = XYZ.Y / converter.bounds.XYZ.Y
-    Z = XYZ.Z / converter.bounds.XYZ.Z
-
-    X = if X > 0.008856 then X ^ ( 1 / 3 ) else ( 7.787 * X ) + ( 16 / 116 )
-    Y = if Y > 0.008856 then Y ^ ( 1 / 3 ) else ( 7.787 * Y ) + ( 16 / 116 )
-    Z = if Z > 0.008856 then Z ^ ( 1 / 3 ) else ( 7.787 * Z ) + ( 16 / 116 )
-
-    l = ( 116 * Y ) - 16
-    a = 500 * ( X - Y )
-    b = 200 * ( Y - Z )
-    # validate output
-    converter.bounds.validate "lab", {l: l, a: a, b: b}
+  # ----------------------------------- #
+  # -- base functions: * -> rgb -> * -- #
+  # ----------------------------------- #
+  base:
+
+    # ----------------- #
+    # -- rgb <-> rgb -- #
+    # ----------------- #
+    #rgb -> rgb
+    "rgb-to-rgb": (values) -> converter.bounds.validate "rgb", values # validate
+
+    # ----------------- #
+    # -- hsl <-> rgb -- #
+    # ----------------- #
+    #hsl -> rgb
+    "hsl-to-rgb": (values) -> # see: http://en.wikipedia.org/wiki/HSV_color_space
+      # validate input
+      values.h = values.h % 1
+      hsl = converter.bounds.validate "hsl", values
+      # logic
+      H = hsl.h * 6.0                           # H ∊ [0, 6[    hue * 6
+      C = (1 - Math.abs(2 * hsl.l - 1)) * hsl.s # C ∊ [0, 1]    chroma
+      X = C * (1 - Math.abs(H % 2 - 1))         # X ∊ [0, 1]    intermediate
+      r = g = b = 0
+      r = C if (0 <= H < 1) or (5 <= H < 6)
+      r = X if (1 <= H < 2) or (4 <= H < 5)
+      g = C if (1 <= H < 3)
+      g = X if (0 <= H < 1) or (3 <= H < 4)
+      b = C if (3 <= H < 5)
+      b = X if (2 <= H < 3) or (5 <= H < 6)
+      m = hsl.l - 0.5 * C # match lightness
+      # validate output
+      converter.bounds.validate "rgb", {r: r + m, g: g + m, b: b + m}
+
+    #rgb -> hsl
+    "rgb-to-hsl": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=18#text18
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      a = Math.min(rgb.r, rgb.g, rgb.b) # a ∊ [0, 1]    min value
+      z = Math.max(rgb.r, rgb.g, rgb.b) # z ∊ [0, 1]    max value
+      d = (z - a)                       # d ∊ [0, 1]    delta value
+      l = (z + a) / 2                   # l ∊ [0, 1]    lightness
+      h = s = 0
+      if d > 0 # color isn't gray
+        s = d / if l < 0.5 then z + a else 2 - z - a # saturation
+        d2 = d / 2 # delta half
+        dr = (((z - rgb.r) / 6) + d2) / d # delta red
+        dg = (((z - rgb.g) / 6) + d2) / d # delta green
+        db = (((z - rgb.b) / 6) + d2) / d # delta blue
+        if      rgb.r is z then h = (0 / 3) + db - dg
+        else if rgb.g is z then h = (1 / 3) + dr - db
+        else if rgb.b is z then h = (2 / 3) + dg - dr
+        h += 1 if h < 0
+        h -= 1 if h >= 1
+      # validate output
+      converter.bounds.validate "hsl", {h: h, s: s, l: l}
+
+    # ----------------- #
+    # -- hsv <-> rgb -- #
+    # ----------------- #
+    #hsv -> rgb
+    "hsv-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=21#text21
+      # validate input
+      values.h = values.h % 1
+      hsv = converter.bounds.validate "hsv", values
+      # logic
+      r = g = b = hsv.v
+      if hsv.s > 0
+        H = hsv.h * 6
+        v1 = hsv.v * (1 - hsv.s)
+        v2 = hsv.v * (1 - hsv.s * (H - vi))
+        v3 = hsv.v * (1 - hsv.s * (1 - (H - vi)))
+
+        r = hsv.v if (0 <= H < 1) or (5 <= H < 6)
+        r = v1    if (2 <= H < 4)
+        r = v2    if (1 <= H < 2)
+        r = v3    if (4 <= H < 5)
+        g = hsv.v if (1 <= H < 3)
+        g = v1    if (4 <= H < 6)
+        g = v2    if (3 <= H < 4)
+        g = v3    if (0 <= H < 1)
+        b = hsv.v if (3 <= H < 5)
+        b = v1    if (0 <= H < 2)
+        b = v2    if (5 <= H < 6)
+        b = v3    if (2 <= H < 3)
+      # validate output
+      converter.bounds.validate "rgb", {r: r, g: g, b: b}
+
+    #rgb -> hsv
+    "rgb-to-hsv": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=20#text20
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      a = Math.min(rgb.r, rgb.g, rgb.b) # a ∊ [0, 1]    min value
+      z = Math.max(rgb.r, rgb.g, rgb.b) # z ∊ [0, 1]    max value
+      d = (z - a)                       # d ∊ [0, 1]    delta value
+
+      v = z
+      h = s = 0
+      if d > 0 # color isn't gray
+        s = d / z # saturation
+        d2 = d / 2 # delta half
+        dr = (((z - rgb.r) / 6) + d2) / d # delta red
+        dg = (((z - rgb.g) / 6) + d2) / d # delta green
+        db = (((z - rgb.b) / 6) + d2) / d # delta blue
+        if      rgb.r is z then h = (0 / 3) + db - dg
+        else if rgb.g is z then h = (1 / 3) + dr - db
+        else if rgb.b is z then h = (2 / 3) + dg - dr
+        h += 1 if h < 0
+        h -= 1 if h >= 1
+      # validate output
+      converter.bounds.validate "hsv", {h: h, s: s, v: v}
+
+    # ----------------- #
+    # -- cmy <-> rgb -- #
+    # ----------------- #
+    #cmy -> rgb
+    "cmy-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
+      # validate input
+      cmy = converter.bounds.validate "cmy", values
+      # logic
+      r = 1 - cmy.c
+      g = 1 - cmy.m
+      b = 1 - cmy.y
+      # validate output
+      converter.bounds.validate "rgb", {r: r, g: g, b: b}
+
+    #rgb -> cmy
+    "rgb-to-cmy": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=11#text11
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      y = 1 - rgb.r
+      m = 1 - rgb.g
+      c = 1 - rgb.b
+      # validate output
+      converter.bounds.validate "cmy", {c: c, m: m, y: y}
+
+    # ------------------ #
+    # -- cmyk <-> rgb -- #
+    # ------------------ #
+    #cmyk -> rgb
+    "cmyk-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=21#text21
+      # validate input
+      cmyk = converter.bounds.validate "cmyk", values
+      # logic
+      c = (cmyk.c * (1 - cmyk.k) + cmyk.k)
+      m = (cmyk.m * (1 - cmyk.k) + cmyk.k)
+      y = (cmyk.y * (1 - cmyk.k) + cmyk.k)
+      # validate output
+      converter.base["cmy-to-rgb"] {c: c, m: m, y: y}
+
+    #rgb -> cmyk
+    "rgb-to-cmyk": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=13#text13
+      # validate input
+      cmy = converter.base["rgb-to-cmy"] values
+      # logic
+      k = 1
+      k = c if c < k
+      k = m if m < k
+      k = y if y < k
+
+      c = 0 if k is 1 # black
+      m = 0 if k is 1 # black
+      y = 0 if k is 1 # black
+
+      c = (c - k) / (1 - k) if k > 0
+      m = (m - k) / (1 - k) if k > 0
+      y = (y - k) / (1 - k) if k > 0
+      # validate output
+      converter.bounds.validate "cmyk", {c: c, m: m, y: y, k: k}
+
+    # ----------------- #
+    # -- XYZ <-> rgb -- #
+    # ----------------- #
+    #XYZ -> rgb
+    "XYZ-to-rgb": (values) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
+      XYZ = converter.bounds.validate "XYZ", values
+      # logic
+      r = XYZ.X *  3.2406 + XYZ.Y * -1.5372 + XYZ.Z * -0.4986
+      g = XYZ.X * -0.9689 + XYZ.Y *  1.8758 + XYZ.Z *  0.0415
+      b = XYZ.X *  0.0557 + XYZ.Y * -0.2040 + XYZ.Z *  1.0570
+
+      r = if r > 0.0031308 then 1.055 * ( r ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * r
+      g = if g > 0.0031308 then 1.055 * ( g ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * g
+      b = if b > 0.0031308 then 1.055 * ( b ^ ( 1 / 2.4 ) ) - 0.055 else 12.92 * b
+      # validate output
+      converter.bounds.validate "rgb", {r: r, g: g, b: b}
+
+    #rgb -> XYZ
+    "rgb-to-XYZ": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=02#text2
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      r = if rgb.r > 0.04045 then ((rgb.r + 0.055) / 1.055) ^ 2.4 else rgb.r / 12.92
+      g = if rgb.g > 0.04045 then ((rgb.g + 0.055) / 1.055) ^ 2.4 else rgb.g / 12.92
+      b = if rgb.b > 0.04045 then ((rgb.b + 0.055) / 1.055) ^ 2.4 else rgb.b / 12.92
+
+      X = rgb.r * 0.4124 + rgb.g * 0.3576 + rgb.b * 0.1805
+      Y = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722
+      Z = rgb.r * 0.0193 + rgb.g * 0.1192 + rgb.b * 0.9505
+      # validate output
+      converter.bounds.validate "XYZ", {X: X, Y: Y, Z: Z}
+
+    # ----------------- #
+    # -- Yxy <-> rgb -- #
+    # ----------------- #
+    #Yxy -> rgb
+    "Yxy-to-rgb": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=04#text4
+      # validate input
+      Yxy = converter.bounds.validate "Yxy", values
+      # logic
+      X = Yxy.x * ( Yxy.Y / Yxy.y )
+      Y = Yxy.Y
+      Z = ( 1 - Yxy.x - Yxy.y ) * ( Yxy.Y / Yxy.y )
+      # validate output
+      converter.base["XYZ-to-rgb"] {X: X, Y: Y, Z: Z}
+
+    #rgb -> Yxy
+    "rgb-to-Yxy": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=03#text3
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      XYZ = converter.base["rgb-to-XYZ"] rgb
+      x = y = 1
+      if not (XYZ.X is XYZ.Y is XYZ.Z is 0)
+        x = X / ( XYZ.X + XYZ.Y + XYZ.Z )
+        y = Y / ( XYZ.X + XYZ.Y + XYZ.Z )
+      # validate output
+      converter.bounds.validate "Yxy", {Y: Y, x: x, y: y}
+
+    # ----------------- #
+    # -- lab <-> rgb -- #
+    # ----------------- #
+    #lab -> rgb
+    "lab-to-rgb": (values) -> # see: http://easyrgb.com/index.php?X=MATH&H=08#text8
+      # validate input
+      lab = converter.bounds.validate "lab", values
+      # logic
+      Y = (lab.l + 16 ) / 116
+      X = lab.a / 500 + Y
+      Z = Y - lab.b / 200
+
+      X = if X^3 > 0.008856 then X^3 else ( X - 16 / 116 ) / 7.787
+      Z = if Z^3 > 0.008856 then Z^3 else ( Z - 16 / 116 ) / 7.787
+      Y = if Y^3 > 0.008856 then Y^3 else ( Y - 16 / 116 ) / 7.787
+
+      X *= converter.bounds.XYZ.X
+      Y *= converter.bounds.XYZ.Y
+      Z *= converter.bounds.XYZ.Z
+      # validate output
+      converter.base["XYZ-to-rgb"] {X: X, Y: Y, Z: Z}
+
+    #rgb -> lab
+    "rgb-to-lab": (r, g, b) -> # see: http://www.easyrgb.com/index.php?X=MATH&H=07#text7
+      # validate input
+      rgb = converter.bounds.validate "rgb", values
+      # logic
+      XYZ = converter.base["rgb-to-XYZ"] values
+      X = XYZ.X / converter.bounds.XYZ.X
+      Y = XYZ.Y / converter.bounds.XYZ.Y
+      Z = XYZ.Z / converter.bounds.XYZ.Z
+
+      X = if X > 0.008856 then X ^ ( 1 / 3 ) else ( 7.787 * X ) + ( 16 / 116 )
+      Y = if Y > 0.008856 then Y ^ ( 1 / 3 ) else ( 7.787 * Y ) + ( 16 / 116 )
+      Z = if Z > 0.008856 then Z ^ ( 1 / 3 ) else ( 7.787 * Z ) + ( 16 / 116 )
+
+      l = ( 116 * Y ) - 16
+      a = 500 * ( X - Y )
+      b = 200 * ( Y - Z )
+      # validate output
+      converter.bounds.validate "lab", {l: l, a: a, b: b}
+
+  convert: (srcType, targetType, values) ->
+    converter.base["rgb-to-#{targetType}"](converter.base["#{srcType}-to-rgb"](values))
+

 Meteor.startup () ->
   settings =


manyboxplots2: Don't hilight initial rectangle

--- manyboxplots2/manyboxplots2.coffee
+++ manyboxplots2/manyboxplots2.coffee
@@ -284,7 +284,6 @@
         .y((d) -> lowyScale(d))

   randomInd = indindex[Math.floor(Math.random()*data.ind.length)]
-  d3.select("rect#rect#{data.ind[randomInd]}").attr("opacity", "1")

   hist = lowsvg.append("path")
     .datum(data.counts[randomInd])


fix(login): unbinds profile after logout, to prevent repeated firebase permissions warning

--- src/app/user/login.coffee
+++ src/app/user/login.coffee
@@ -33,10 +33,11 @@
       throw new Error(error)

     $rootScope.$on "$firebaseSimpleLogin:login", (event, user)->
-
       # Ugly -- TODO - move this to a service
       profile = Profile.get(user.uid)
-      profile.$bind($rootScope, "userProfile")
+      profile.$bind($rootScope, "userProfile").then (unbind)->
+        $rootScope.$on "$firebaseSimpleLogin:logout", ()->
+          unbind()

       role = $firebase(firebaseRef().child("roles").child(user.uid))
       role.$on "loaded", ()->


No longer needed

No longer needed

--- src/atom-bootstrap.coffee
+++ src/atom-bootstrap.coffee
@@ -1,2 +0,0 @@
-Atom = require 'atom'
-window.atom = new Atom(window.resourcePath, $native)


ecobee - try to understand token failures

--- lib/adapters/ecobee/EcobeeAPI.coffee
+++ lib/adapters/ecobee/EcobeeAPI.coffee
@@ -101,9 +101,14 @@
       client_id: @get('key')
     @_request('token', 'POST', data)
       .then (body) =>
-        @log 'verbose', 'Succeeded refreshing tokens'
-        @set 'tokens', {access: body.access_token, refresh: body.refresh_token}
-        deferred.resolve()
+        if body.access_token?
+          @log 'verbose', 'Succeeded refreshing tokens'
+          @set 'tokens', {access: body.access_token, refresh: body.refresh_token}
+          deferred.resolve()
+        else
+          @log 'warn', 'Token refresh response did not contain access_token!'
+          @log 'verbose', "Token refresh response: #{JSON.stringify(body)}"
+          deferred.reject()
       .fail (err) =>
         @log 'warn', "Failed refreshing token: #{JSON.stringify(err)}"
         deferred.reject(err)


Temporarily deactivates depth tests

Temporarily deactivates depth tests

--- spec/index.coffee
+++ spec/index.coffee
@@ -167,7 +167,7 @@
             childFromParent.should.eql child
             done()

-describe 'specify the depth (recursively proven)', ->
+xdescribe 'specify the depth (recursively proven)', ->
   describe 'depth 0', ->
     it 'should be the root of the search when a directory', (done)->
       instance = fs2jsonModule()


consistent line breaks

consistent line breaks

--- test/collection.spec.coffee
+++ test/collection.spec.coffee
@@ -12,6 +12,7 @@

   it "sets the length", ->
     expect(@collection.length).to.eql(3)
+
   it "sets the length to zero when empty", ->
     @collection = new Serenade.Collection([])
     expect(@collection.length).to.eql(0)
@@ -27,7 +28,6 @@
     it "serializes the array", ->
       collection = new Serenade.Collection([{toJSON: -> "foo"}, "bar"])
       expect(collection.toJSON()).to.eql(["foo", "bar"])
-

   describe '#get', ->
     it 'gets an item from the collection', ->


[tests] Unskip green tests

[tests] Unskip green tests

--- test/css/filter/test.coffee
+++ test/css/filter/test.coffee
@@ -1,4 +1,4 @@
-describe.skip 'css/filter >>', ->
+describe 'css/filter >>', ->

   it '0', -> this.shouldBeOk()
   it '1', -> this.shouldBeOk()


Initial Session implementation

Initial Session implementation

--- src/wrathnet/sessions/session.coffee
+++ src/wrathnet/sessions/session.coffee
@@ -9,7 +9,23 @@
 # this library is licensed. See the LICENSE file for the full license.
 #

+# Denotes a session to be used as entry for the WrathNet foundation
 class WrathNet.sessions.Session
-
-  _userAgent: null
-  _expansion: null
+
+  # Creates a new session for given expansion
+  constructor: (expansion, userAgent) ->
+
+    # Ensure the expansion is an instance
+    unless expansion instanceof WrathNet.expansions.Expansion
+      expansion = new expansion()
+
+    # Holds the expansion this session uses
+    @expansion = expansion
+
+    # Holds user-agent information
+    @userAgent = userAgent
+
+    # Holds the various handlers
+    @auth = new expansion.authHandler()
+    @realms = new expansion.realmHandler()
+    @world = new expansion.worldHandler()


Fix error in DisplayMarker.isDestroyed

Fix error in DisplayMarker.isDestroyed

--- src/display-marker.coffee
+++ src/display-marker.coffee
@@ -144,7 +144,7 @@
   # undoing the invalidating operation would restore the marker. Once a marker
   # is destroyed by calling {DisplayMarker::destroy}, no undo/redo operation
   # can ever bring it back.
-  isDestroyed: -> @layer.isDestroyed() or @destroyed
+  isDestroyed: -> @destroyed or @layer.isDestroyed()

   # Essential: Returns a {Boolean} indicating whether the head precedes the tail.
   isReversed: ->


Removing comments

Removing comments

--- lib/elixir-quoted-view.coffee
+++ lib/elixir-quoted-view.coffee
@@ -10,11 +10,8 @@
     createEditor = ->
       element = document.createElement('atom-text-editor')
       element.setAttribute('tabIndex', 0)
-      # element.removeAttribute('tabindex')
       editor = element.getModel()
       editor.setLineNumberGutterVisible(true)
-      # editor.setSoftWrapped(true)
-      # editor.getDecorations(class: 'cursor-line', type: 'line')[0].destroy()
       editor.setGrammar(atom.grammars.grammarForScopeName('source.elixir'))
       atom.commands.add element,
         'core:move-up': =>
@@ -95,11 +92,6 @@
     @matchesEditor.getDecorations(class: 'cursor-line', type: 'line')[0].destroy()
     @matchesEditor.setLineNumberGutterVisible(false)

-    # Pattern  {var1, var2}
-    # Match
-    #   var1   [lksajdlf]
-    #   var2   {:fedd}
-
   handleEvents: ->
     @disposables.add atom.commands.add @element,
       'elixir-quoted-view:focus-next': => @focusNextElement(1)
@@ -133,12 +125,10 @@
   setQuotedCode:(quotedCode) ->
     @quotedCode = quotedCode
     @quotedCodeEditor.setText(@quotedCode)
-    # @refreshView()

   setCode:(code) ->
     @code = code
     @codeEditor.setText(@code)
-    # @refreshView()

   refreshView: ->
     return unless @quotedCode?
@@ -161,4 +151,3 @@
     focusedIndex = 0 if focusedIndex >= elements.length
     focusedIndex = elements.length - 1 if focusedIndex < 0
     elements[focusedIndex].focus()
-    # elements[focusedIndex].getModel?().selectAll()


:racehorse: Optimize realpath checks

:racehorse: Optimize realpath checks

* Use a cache
* Use async realpath

--- lib/load-paths-handler.coffee
+++ lib/load-paths-handler.coffee
@@ -10,6 +10,7 @@
 class PathLoader
   constructor: (@rootPath, ignoreVcsIgnores, @traverseSymlinkDirectories, @ignoredNames) ->
     @paths = []
+    @realPathCache = {}
     @repo = null
     if ignoreVcsIgnores
       repo = GitRepository.open(@rootPath, refreshOnWindowFocus: false)
@@ -45,19 +46,19 @@
     fs.lstat pathToLoad, (error, stats) =>
       return done() if error?
       if stats.isSymbolicLink()
-        return done() if @isInternalSymlink(pathToLoad)
-
-        fs.stat pathToLoad, (error, stats) =>
-          return done() if error?
-          if stats.isFile()
-            @pathLoaded(pathToLoad, done)
-          else if stats.isDirectory()
-            if @traverseSymlinkDirectories
-              @loadFolder(pathToLoad, done)
+        @isInternalSymlink pathToLoad, (isInternal) =>
+          return done() if isInternal
+          fs.stat pathToLoad, (error, stats) =>
+            return done() if error?
+            if stats.isFile()
+              @pathLoaded(pathToLoad, done)
+            else if stats.isDirectory()
+              if @traverseSymlinkDirectories
+                @loadFolder(pathToLoad, done)
+              else
+                done()
             else
               done()
-          else
-            done()
       else if stats.isDirectory()
         @loadFolder(pathToLoad, done)
       else if stats.isFile()
@@ -74,13 +75,12 @@
         done
       )

-  isInternalSymlink: (pathToLoad) ->
-    realPath = null
-    try
-      realPath = fs.realpathSync(pathToLoad)
-    catch err
-      ; # ignore a broken symlink
-    realPath?.search(@rootPath) is 0
+  isInternalSymlink: (pathToLoad, done) ->
+    fs.realpath pathToLoad, @realPathCache, (err, realPath) =>
+      if err
+        done(false)
+      else
+        done(realPath?.search(@rootPath) is 0)

 module.exports = (rootPaths, followSymlinks, ignoreVcsIgnores, ignores=[]) ->
   ignoredNames = []


USSD widget label translation

USSD widget label translation

--- static/coffee/flows/widgets.coffee
+++ static/coffee/flows/widgets.coffee
@@ -30,7 +30,7 @@
       if not item.category._autoName
         return

-      words = item.label.trim().split(/\b/)
+      words = item.label[Flow.flow.base_language].trim().split(/\b/)
       if words
         categoryName = words[0].toUpperCase()
         item.category._base = categoryName
@@ -45,7 +45,7 @@
           scope.ruleset.config.ussd_menu.splice length, 0,
             uuid: uuid()
             option: if length >= 9 then 0 else length + 1
-            label: ""
+            label: {}
             category:
               _autoName: true
               _base: ""
@@ -58,15 +58,15 @@
     scope.updateMenu = (item, index) ->
       scope.countCharacters()
       updateCategory(item)
-      if item.label.length == 1 and index == scope.ruleset.config.ussd_menu.length - 1
+      if item.label[Flow.flow.base_language].length == 1 and index == scope.ruleset.config.ussd_menu.length - 1
         insertEmpty()

     do scope.countCharacters = ->
       sum = (items) ->
         items.reduce (prev, current) ->
           current.option ?= ""
-          current.label ?= ""
-          prev + current.option.toString().length + current.label.length + 2 # 1 for space 1 for newline char
+          current.label[Flow.flow.base_language] ?= ""
+          prev + current.option.toString().length + current.label[Flow.flow.base_language].length + 2 # 1 for space 1 for newline char
         ,0

       textLength = scope.ruleset.config.ussd_text.length


1.修复不能上传模块的bug

1.修复不能上传模块的bug

--- lib/package/package-list-view.coffee
+++ lib/package/package-list-view.coffee
@@ -121,11 +121,11 @@
     .then (result) =>
       console.log "validate"
       throw new Error('upload fail') unless result.result is 'success'
-      @validateAttach result.id
+      @validateAttach(result.id).then (nResult) -> _.extend(nResult, boundle: result.id)
     .then (result) =>
       console.log "new module"
       throw new Error('validate fail') unless result.result is 'success'
-      @createModule _.extend result, {widget_id: '548698920cf2da4e8927bab5', boundle: id}
+      @newModule _.extend result, {widget_id: '548698920cf2da4e8927bab5'}
     .then (result) ->
       cell.changeState 'normal' if result.result is 'success'
     .catch (err) ->
@@ -141,18 +141,20 @@
       success JSON.parse(body) if success

   newModule: (data, success, error) ->
-    r = request.post "#{C_Server}/bsl-web/mam/widgetVersion/add", (err, res, body)=>
-      return error err if err
-      success JSON.parse(body) if success

-    form = r.form()
-    form.append 'name', data.name
-    form.append 'identify', data.identifier
-    form.append 'boundle', data.boundle
-    form.append 'build', data.build
-    form.append 'version', data.version
-    form.append 'release_not', data.releaseNote
-    form.append 'widget_id', data.widget_id
+    Q.Promise (resolve, reject, notify) ->
+
+      r = request.post "#{C_Server}/bsl-web/mam/widgetVersion/add", (err, res, body)=>
+        if err then reject(err) else resolve JSON.parse(body)
+
+      form = r.form()
+      form.append 'name', data.name
+      form.append 'identify', data.identifier
+      form.append 'boundle', data.boundle
+      form.append 'build', data.build
+      form.append 'version', data.version
+      form.append 'release_not', data.releaseNote
+      form.append 'widget_id', data.widget_id

   login: (tanant, username, password) ->
     console.log "login"
@@ -167,18 +169,10 @@
     request_get "#{C_Server}/bsl-web/mam/attachment/readfile/#{id}"

   uploadAttach: (buf) ->
-    # request_post url: "#{C_Server}/bsl-web/mam/attachment/upload",
-    #   form:
-    #     file: buf
-    #     filename: 'upload.zip'
-    #     contentType: 'application/zip'

     Q.Promise (resolve, reject, notify) ->
       r = request.post "#{C_Server}/bsl-web/mam/attachment/upload", (err, res, body)=>
-          if err
-            reject(err)
-          else
-            resolve JSON.parse(body)
+          if err then reject(err) else resolve JSON.parse(body)

       form = r.form()
       form.append "file", buf,


TEST - REMOVED depricated 'animate' tests

--- test/tests/Art/Engine/ZAnimation/Animator.coffee
+++ test/tests/Art/Engine/ZAnimation/Animator.coffee
@@ -39,32 +39,3 @@
       el.onNextReady()
     .then ->
       assert.eq el.currentLocation, point 10
-
-  test "Element Animator - Declarative - Explicit", ->
-    el = new Element animate:
-      from: location: 5
-      to:   location: 10
-
-    el.onNextReady()
-    .then ->
-      assert.eq el.currentLocation, point 5
-      log "TEST FOR _activeAnimator"
-      assert.isPresent el._activeAnimator
-      el._activeAnimator.finish()
-      el.onNextReady()
-    .then ->
-      assert.eq el.currentLocation, point 10
-
-  test "Animate Layout", ->
-    el = new Element animate:
-      from: size: 100
-      to:   size: 200
-
-    el.onNextReady()
-    .then ->
-      el._activeAnimator.updateValues .5
-      assert.eq el.pendingSize.layoutX(), 150
-      el._activeAnimator.finish()
-      el.onNextReady()
-    .then ->
-      assert.eq el.pendingSize.layoutX(), 200


Authorize storage motion to a host with different CPU config

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -440,7 +440,7 @@
     true # Live migration
     vdiMap
     vifMap
-    {} # Other parameters
+    {'force': 'true'} # Force migration even if CPUs are different
   )

   return true


Interpool migration

Interpool migration

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -370,6 +370,61 @@
   host_id: { type: 'string' }
 }

+exports.migrate_pool = ({
+  id
+  target_host_id
+  target_sr_id
+  target_network_id
+  migration_network_id
+}) ->
+  try
+    # TODO: map multiple VDI and VIF
+    host = @getObject target_host_id
+    migrationNetwork = @getObject migration_network_id
+    network = @getObject target_network_id
+    SR = @getObject target_sr_id
+    VM = @getObject id
+  catch
+    @throw 'NO_SUCH_OBJECT'
+
+  unless $isVMRunning VM
+    @throw 'INVALID_PARAMS', 'The VM can only be migrated when running'
+
+  token = $wait (@getXAPI host).call(
+    'host.migrate_receive'
+    host.ref
+    migrationNetwork.ref
+    {} # Other parameters
+  )
+
+  vdiMap = {}
+  for vbdId in VM.$VBDs
+    VBD = @getObject vbdId
+    continue if VBD.is_cd_drive
+    VDI = @getObject VBD.VDI
+    vdiMap[VDI.ref] = SR.ref
+
+  vifMap = {}
+  for vifId in VM.VIFs
+    VIF = @getObject vifId
+    vifMap[VIF.ref] = network.ref
+
+  $wait (@getXAPI VM).call(
+    'VM.migrate_send'
+    VM.ref
+    token
+    true # Live migration
+    vdiMap
+    vifMap
+    {} # Other parameters
+  )
+
+  return true
+exports.migrate.permission = 'admin'
+exports.migrate.params = {
+  # FIXME
+}
+
 exports.set = (params) ->
   try
     VM = @getObject params.id


Add tests for accessing public apps while unauthenticated

Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -749,3 +749,66 @@
 			it 'should throw when an application id is not provided', ->
 				m.chai.expect( -> balena.models.application.getDashboardUrl())
 				.to.throw()
+
+	describe 'given public apps', ->
+
+		publicApp = undefined
+
+		before ->
+			balena.pine.get
+				resource: 'application'
+				options:
+					$top: 1
+					$select: ['id', 'app_name', 'slug', 'is_public']
+					$filter: is_public: true
+			.then ([app]) ->
+				m.chai.expect(app).to.have.property('is_public', true)
+				publicApp = app
+
+		describe 'when not being logged in', ->
+			before ->
+				balena.auth.logout()
+
+			describe 'arbitrary pinejs queries', ->
+
+				it 'should be able to retrieve the available public apps', ->
+					if !publicApp
+						this.skip()
+						return
+
+					balena.pine.get
+						resource: 'application'
+						options:
+							$select: ['id', 'app_name', 'slug', 'is_public']
+					.then (apps) ->
+						m.chai.expect(apps.length).to.be.gte(1)
+
+						appIds = apps.map (app) -> app.id
+						m.chai.expect(appIds.includes(publicApp.id)).to.be.true
+
+						apps.forEach (app) ->
+							m.chai.expect(app).to.have.property('id').that.is.a('number')
+							m.chai.expect(app).to.have.property('app_name').that.is.a('string')
+							m.chai.expect(app).to.have.property('slug').that.is.a('string')
+							m.chai.expect(app).to.have.property('is_public', true)
+
+
+			describe 'balena.models.application.get()', ->
+
+				[
+					'id'
+					'app_name'
+					'slug'
+				].forEach (prop) ->
+
+					it "should be able to get a public application by #{prop}", ->
+						if !publicApp
+							this.skip()
+							return
+
+						balena.models.application.get(publicApp[prop])
+						.then (app) ->
+							m.chai.expect(app).to.have.property('id').that.is.a('number')
+							m.chai.expect(app).to.have.property('app_name').that.is.a('string')
+							m.chai.expect(app).to.have.property('slug').that.is.a('string')
+							m.chai.expect(app).to.have.property('is_public', true)


adding the vanguard badge

adding the vanguard badge

--- src/badges.coffee
+++ src/badges.coffee
@@ -47,6 +47,7 @@
   'translator1', 'translator2', 'translator3', 'translator4',
   'translator5',
   'verified'
+  'vandguard1', 'vandguard2', 'vandguard3', 'vandguard4', 'vandguard5'
 ]

 colorList= {
@@ -88,7 +89,8 @@
   'specops': 5,
   'trekker': 5,
   'translator': 5,
-  'verified': 1
+  'verified': 1,
+  'vandguard' : 5
 }

 module.exports = (robot) ->


[grunt] better way to pass arguments from `|template()` filter of Nunjucks to `printf()`

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -374,8 +374,7 @@
            * @return {string} String with replaced placeholders
           ###
           env.addFilter 'template', (string, placeholders...) ->
-            placeholders.unshift(string)
-            printf.apply null, placeholders
+            printf(string, placeholders...)

           ###*
            * Pluralize string based on count. For situations, where full i18n is too much


[grunt] rename `localeSrc` to fit general naming pattern

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -31,7 +31,7 @@

   buildDir   = grunt.template.process('<%= path.build.root %>')
   layoutsDir = grunt.template.process('<%= path.source.layouts %>')
-  localeSrc  = grunt.template.process('<%= path.source.locales %>')
+  localesDir  = grunt.template.process('<%= path.source.locales %>')

   # Helpers
@@ -47,9 +47,9 @@
   #       file will be actually used
   locales.forEach (locale) ->

-    grunt.file.expand({ cwd: localeSrc + '/' + locale, filter: 'isFile' }, '**/*.po').forEach (file) ->
-
-      messages = grunt.file.read(localeSrc + '/' + locale + '/' + file, { encoding: null })
+    grunt.file.expand({ cwd: localesDir + '/' + locale, filter: 'isFile' }, '**/*.po').forEach (file) ->
+
+      messages = grunt.file.read(localesDir + '/' + locale + '/' + file, { encoding: null })
       i18n.addTextdomain(locale, messages)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -14,7 +14,7 @@
     zoom: 1
     transform: Matrix()
     scroll: Point(0, 0)
-    m
+    maxApproa

   currentType = "centered"
   currentObject = null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -37,7 +37,8 @@

     if objectCenter.x > centerRect.right

-    else if objectCenter.x
+    else if objectCenter.x < centerRect.left
+

     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)


fix same JSON bug clientside

fix same JSON bug clientside

--- src/client.coffee
+++ src/client.coffee
@@ -25,8 +25,15 @@
     queue = []

     handleMessage = ( e ) ->
-        data = JSON.parse e.data
-        if e.origin == 'https://storage.gw2treasures.de' && data.storage
+        if e.origin isnt 'https://storage.gw2treasures.de'
+            return
+
+        data =
+            try
+                if typeof e.data is 'string' then JSON.parse e.data else e.data
+            catch
+                e.data
+        if data.storage
             if data.storage.loaded
                 _loaded++
                 postMessage x, 'https://storage.gw2treasures.de' for x in queue


Allow configuration of heartbeat endpoint via env var.

--- src/config.coffee
+++ src/config.coffee
@@ -9,8 +9,8 @@
 	localImage: process.env.SUPERVISOR_IMAGE ? 'resin/rpi-supervisor'
 	configMountPoint: process.env.CONFIG_MOUNT_POINT ? '/mnt/mmcblk0p1/config.json'
 	ledFile: process.env.LED_FILE ? '/sys/class/leds/led0/brightness'
+	heartbeatEndpoint: process.env.HEARTBEAT_ENDPOINT ? 'http://image.resin.io/ping'
 	successMessage: 'SUPERVISOR OK'
-	heartbeatEndpoint: 'http://image.resin.io/ping'

 config.remoteImage = config.registryEndpoint + '/' + config.localImage


Refactor remote revision resource read

Refactor remote revision resource read

Since the remote revision hash is always stored and retrieved as
binary, there is no need to check if it's a string.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -181,16 +181,12 @@
 			fs.writeFileSync @revisionPath, @local_hash

 			# If the remote revision file exists, let's get it's content
-			if typeof data is "string"
-				@remote_hash = data
+			data.on "data", (e) =>
+				data.end()
+				@remote_hash = e.toString()
+
+				# Get the diff tree between the local and remote revisions
 				@checkDiff @remote_hash, @local_hash
-			else
-				data.on "data", (e) =>
-					data.end()
-					@remote_hash = e.toString()
-
-					# Get the diff tree between the local and remote revisions
-					@checkDiff @remote_hash, @local_hash

 	# Get the diff tree between the local and remote revisions
 	checkDiff: (old_rev, new_rev) ->


Adding support for device.noConflict() mode. References #30

--- src/device.coffee
+++ src/device.coffee
@@ -3,6 +3,11 @@
 # Device.js is freely distributable under the MIT license.
 # For all details and documentation:
 # http://matthewhudson.me/projects/device.js/
+
+# Save the previous value of the device variable.
+previousDevice = window.device
+
+# Add device as a global object.
 window.device = {}

 # The <html> element.
@@ -11,6 +16,7 @@
 # The client user agent string.
 # Lowercase, so we can use the more efficient indexOf(), instead of Regex
 _user_agent = window.navigator.userAgent.toLowerCase()
+

 # Main functions
 # --------------
@@ -77,6 +83,11 @@
 device.landscape = ->
   Math.abs(window.orientation) is 90

+# Run device.js in noConflict mode, returning the device variable to its previous owner.
+# Returns a reference to the device object.
+device.noConflict = ->
+  window.device = previousDevice
+  @

 # Private Utility
 # ---------------


Make accented character suggestion work for multiple cursors.

--- src/editor.coffee
+++ src/editor.coffee
@@ -713,9 +713,7 @@
       # Work around of the accented character suggestion feature in OS X.
       selectedLength = @hiddenInput[0].selectionEnd - @hiddenInput[0].selectionStart
       if selectedLength is 1 and lastInput is @hiddenInput.val()
-        position = @getCursorScreenPosition()
-        position.column -= 1
-        @selectToScreenPosition(position)
+        @selectLeft()

       lastInput = e.originalEvent.data
       @insertText(lastInput)


Adding diagnostic info.

Adding diagnostic info.

--- src/engine.coffee
+++ src/engine.coffee
@@ -27,6 +27,7 @@

     # Start the engine's time simulation.
     start : () ->
+        @logger.info("Starting engine.")
         @isRunning = true
         @timestamp = new Date()
         @step()
@@ -34,6 +35,7 @@

     # Stop the engine's time simulation.
     stop : () ->
+        @logger.info("Stopping engine.")
         @isRunning = false
         @timestamp = null
         return this


Updated udefine/export

Updated udefine/export

--- src/export.coffee
+++ src/export.coffee
@@ -1,2 +1,2 @@
 udefine = require './udefine'
-module.exports = udefine.export
+module.exports = udefine.exports


Add users command

Add users command

--- src/github.coffee
+++ src/github.coffee
@@ -15,6 +15,7 @@
 #
 # Commands:
 #   hubot github iam <username> - Let hubot know what your github username is
+#   hubot github users - List the github users hubot knows about
 #   hubot github open [for <user>] - Shows a list of open pull requests for the repo of this room [optionally for a specific user]
 #   hubot github notification hh:mm - I'll remind about open pull requests in this room at hh:mm every weekday.
 #   hubot github list notifications - See all pull request notifications for this room.
@@ -180,12 +181,24 @@
      saveGithubUsers users
      msg.reply "Thanks, I'll remember that <@#{msg.message.user.id}> is `#{githubUsername}` on github"

+   robot.respond /(?:github|gh|git) (?:list|show|users)(?:\s+users)?$/, (msg) ->
+     users = getGithubUsers()
+     message = ""
+     for user of users
+       message += "\n<@#{user}> is `#{users[user]}` on github"
+
+     if message.length is 0
+       message = "I don't have any github users yet"
+
+     msg.reply message
+
   robot.respond /(github|gh|git) help/i, (msg) ->
     msg.send """
       I can remind you about open pull requests for the repo that belongs to this channel
       Use me to create a notification, and then I'll post in this room every weekday at the time you specify. Here's how:

       #{robot.name} github iam <username> - Let hubot know what your github username is
+      #{robot.name} github users - List the github users hubot knows about
       #{robot.name} github open [for <user>] - Shows a list of open pull requests for the repo of this room [optionally for a specific user]
       #{robot.name} github notification hh:mm - I'll remind about open pull requests in this room at hh:mm every weekday.
       #{robot.name} github list notifications - See all pull request notifications for this room.


Just playin

Just playin

--- src/gonews.coffee
+++ src/gonews.coffee
@@ -25,7 +25,7 @@
       console.log err if err
       console.log rss

-      res.reply rss
+      res.reply rss[0]

   #robot.respond /hello/, (res) ->
   #  res.reply "hello!"


Calculate aggregate startup/shutdown numbers

--- src/heroku.coffee
+++ src/heroku.coffee
@@ -6,6 +6,7 @@
 Heroku = require 'heroku-client'
 child = require 'child_process'
 async = require 'async'
+statistics = require 'simple-statistics'

 exports.dryrun = false

@@ -103,7 +104,7 @@
       events.push { type: 'process-stopping', time: timestamp, dyno: dyno, msg: info }

     else
-      #console.log info
+      #debug 'unknown-logline', info
   return events

 # Basically a finite state machine, one per dyno
@@ -112,6 +113,7 @@
   state.lasttransition = {} if not state.lasttransition # 'dyno.N' -> lastTransition: Event }
   state.dynostate = {} if not state.dynostate # 'dyno.N' -> DynoState
   state.startups = [] if not state.startups
+  state.shutdowns = [] if not state.shutdowns

   # TODO: reject invalid transitions.\
@@ -121,11 +123,9 @@
     #console.log event.dyno, event.type
     switch event.type
       when 'process-starting'
-        #console.log 'starting', event.dyno
         state.dynostate[event.dyno] = 'starting'
         state.lasttransition[event.dyno] = event
       when 'process-started'
-        #console.log 'started', event.dyno
         if state.lasttransition[event.dyno] and state.dynostate[event.dyno] == 'starting'
           s =
             dyno: event.dyno
@@ -137,12 +137,32 @@
           state.dynostate[event.dyno] = 'started'
           state.lasttransition[event.dyno] = event
         else
-          console.log 'invalid transition', event.type, state.dynostate[event.dyno]
+          debug 'invalid transition', event.type, state.dynostate[event.dyno]
+
       when 'starting->up' then null
-
       when 'up->down' then null
-      when 'process-stopping' then null
-      when 'process-exited' then null
+
+      when 'process-stopping'
+        if state.dynostate[event.dyno] == 'started'
+          state.dynostate[event.dyno] = 'stopping'
+          state.lasttransition[event.dyno] = event
+        else
+          debug 'invalid transition', event.type, state.dynostate[event.dyno]
+
+      when 'process-exited'
+        if state.dynostate[event.dyno] == 'stopping' and state.lasttransition[event.dyno]
+          s =
+            dyno: event.dyno
+            start: state.lasttransition[event.dyno]
+            end: event
+          s.duration = s.end.time.getTime() - s.start.time.getTime()
+          state.shutdowns.push s
+
+          state.dynostate[event.dyno] = 'exited'
+          state.lasttransition[event.dyno] = event
+        else
+          debug 'invalid transition', event.type, state.dynostate[event.dyno]
+
   else
     # FIXME: handle. Maybe outside/before. Particularly scale-to?

@@ -157,7 +177,16 @@
     #results = events.map (e) -> "#{e.dyno or ''} #{e.type}"
     for e in events
       applyEvent state, e
-    results = state.startups.map (s) -> s.duration
+
+    starts = state.startups.map (s) -> s.duration/1000
+    stops = state.shutdowns.map (s) -> s.duration/1000
+    results =
+      startup: statistics.mean starts
+      startup_stddev: statistics.standard_deviation starts
+      startup_length: starts.length
+      shutdown: statistics.mean stops
+      shutdown_stddev: statistics.standard_deviation stops
+      shutdown_length: stops.length
     return callback null, results

 # FIXME: Rename to guv-heroku-workerstats
@@ -165,7 +194,7 @@
 # TODO: allow to separate between (module) loading time, and startup time
 # TODO: callculate whole delay from scaling to up by default
 # TODO: allow to calculate shutdown time
-# TODO: add tool for calculating 'waste' percentage. Ratio of time spent processing versus startup+shutdown
+# TODO: add tool for calculating 'waste' percentage. Ratio of time spent processing versus startup+shutdownl
 exports.startuptime_main = () ->
   program = require 'commander'


Implement retrieving legislation by their code. (e.g. HB 1).

--- routes/api/v1/legislation.coffee
+++ routes/api/v1/legislation.coffee
@@ -81,4 +81,44 @@
   # GET /api/v1/legislation/:type/:number - Retrieve a legilation type
   # by number
   app.get '/api/v1/legislation/:type/:number', (req, res) ->
-    res.send(501)
+    type = req.params.type.toUpperCase()
+
+    unless type == 'HR' || type == 'HB' || type == "SB" || type == "SR"
+      res.json
+        fieldId: "type",
+        error: "invalid",
+        message: "The legislation type must be one of HR, HB, SB, or SR."
+      , 417
+      return
+
+    fullCode = type + " " + req.params.number
+
+    db.collection("sessions").findOne {current: true}, (err, currentSession) ->
+      unless currentSession?
+        err = "Could not find current session."
+        errorId = Math.random().toString(36).substring(7)
+        console.error("Error " + errorId + ": " + err)
+
+        res.json
+          id: errorId,
+          error: err
+        , 500
+
+        return
+
+      selectedSessionIdStr = req.query.sessionId || currentSession._id.toString()
+      selectedSessionId = new ObjectId(selectedSessionIdStr)
+
+      db.collection("legislation").findOne {sessionId: selectedSessionId, code: fullCode}, (err, legislation) ->
+        if err
+          errorId = Math.random().toString(36).substring(7)
+          console.error("Error " + errorId + ": " + err)
+
+          res.json
+            id: errorId,
+            error: err
+          , 500
+
+          return
+
+        res.json(legislation)


fix view el reference

fix view el reference

--- app/assets/javascripts/states/monitoring/views/host_summary.js.coffee
+++ app/assets/javascripts/states/monitoring/views/host_summary.js.coffee
@@ -1,7 +1,7 @@
 namespace "Bixby.view.monitoring", (exports, top) ->

   class exports.HostSummary extends Stark.View
-    el: "div#content"
+    el: "div.monitoring_content"
     template: "monitoring/host_summary"

     links:


Updated lib.coffee -- added historic value,  object_refinement

Historic value is a class for changing/updating/recording a value over
time.

Object refinement is a class for wrapping objects in another class
with new methods.

--- javascripts/lib.coffee
+++ javascripts/lib.coffee
@@ -16,3 +16,59 @@

 ttm.define "lib/class_mixer", ->
   return ttm.ClassMixer
+
+
+ttm.define "lib/historic_value", ->
+  build = ->
+    values = []
+    obj = {}
+    obj.history = ->
+      values
+    obj.update = (val)->
+      values.push val
+    obj.current = ->
+      values[values.length-1]
+
+    # calls fn with current argument,
+    # sets current value to return
+    # value of the fn
+    obj.updatedo = (fn)->
+      values.push fn(obj.current())
+    obj
+  return build: build
+
+
+# see the lib spec for example usage
+ttm.define "lib/object_refinement", ['lib/class_mixer'], (class_mixer)->
+  class Refinement
+    initialize: ->
+      @refinements = []
+
+    forType: (type, methods)->
+      @refinements.push RefinementByType.build(type, methods)
+
+    refine: (component)->
+      for refinement in @refinements
+        if refinement.isApplicable(component)
+          return refinement.apply(component)
+      component
+
+  class_mixer Refinement
+
+  class RefinementDeclaration
+    apply: (subject)->
+      refinement_class = ->
+      refinement_class.prototype = subject
+      ret = new refinement_class
+      _.extend(ret, @methods)
+      ret
+
+  class RefinementByType extends RefinementDeclaration
+    initialize: (@type, @methods)->
+
+    isApplicable: (subject)->
+      subject instanceof @type
+
+  class_mixer RefinementByType
+
+  return Refinement


change home path in unix_user_add

change home path in unix_user_add

--- code/serv.coffee
+++ code/serv.coffee
@@ -295,13 +295,14 @@

 exports.unix_user_add = (user_name, callback) ->
+  homeDir = "#{process.env.CO_STORAGE_DIR}/home"
   cmd = """
         cd /opt/cobalt &&
         . ./code/box_lib.sh &&
         create_user #{user_name} &&
         create_user_directories #{user_name}
-        sh ./code/templates/box.json.template | tee /home/#{user_name}/box.json
-        chown #{user_name}:databox /home/#{user_name}/box.json
+        sh ./code/templates/box.json.template | tee #{homeDir}/#{user_name}/box.json
+        chown #{user_name}:databox #{homeDir}/#{user_name}/box.json
         """
   # insecure - sanitise user_name
   exec cmd, callback


Correct documentation

Correct documentation

--- code/serv.coffee
+++ code/serv.coffee
@@ -35,10 +35,11 @@

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

 # Templating language
@@ -188,9 +189,7 @@
           child_process.exec "chown #{box.name}: #{keys_path}" # insecure
           return res.send {"status": "ok"}

-port = process.env.COBALT_PORT
 app.listen port
-
 if path.existsSync(port) && fs.lstatSync(port).isSocket()
   fs.chmodSync port, 0o600
   child_process.exec "chown www-data #{port}"


Update tests

Update tests

--- test/DevelopmentIntegration.coffee
+++ test/DevelopmentIntegration.coffee
@@ -194,16 +194,15 @@
   test.done()

 exports['css helper function allows user attributes'] = (test) ->
-  cssTag = '<link rel="stylesheet" href="/css/style.css" media="print" data-confirm />'
-  test.equals css('/css/style.css', {media: "print", delete: false, data: { confirm: -> true}}), cssTag
-  test.equals css('style.css', {media: "print", delete: false, data: { confirm: -> true }}), cssTag
-  test.equals css('style', {media: "print", delete: false, data: { confirm: -> true }}), cssTag
-  test.done()
-
-
-exports['Compiled stylesheets work from absolute options.src with user options'] = (test) ->
-  cssTag = '<link rel="stylesheet" href="/css/button.css" data-option />'
-  test.equals css('button', 'data-option': true), cssTag
+  cssTag = '<link rel="stylesheet" href="/css/style.css" media="print" />'
+  test.equals css('/css/style.css', media: "print"), cssTag
+  test.equals css('style.css', media: "print"), cssTag
+  test.equals css('style', media: "print"), cssTag
+  test.done()
+
+exports['css helper function allows user attributes such as functions, objects and booleans'] = (test) ->
+  cssTag = '<link rel="stylesheet" href="/css/style.css" data-confirm="Are you sure to do this?" data-delete />'
+  test.equals css('style.css', { data: { confirm: 'Are you sure to do this?', delete: -> true } }), cssTag
   test.done()

 exports['js helper function provides correct src'] = (test) ->


Tests:Account:Accountpage: Added edit first name test.

--- tests/src/account/accountpage.coffee
+++ tests/src/account/accountpage.coffee
@@ -21,3 +21,14 @@
       .waitForElementVisible   postInPageSelector, 25000
       .assert.containsText     postInPageSelector, post
       .end()
+
+
+  editFirstName: (browser) ->
+
+    helpers.beginTest(browser)
+    paragraph = helpers.getFakeText()
+    inputSelector = '.firstname input.text'
+
+    helpers.changeName(browser, inputSelector, yes)
+    browser.end()
+


a slight change in the formatting

a slight change in the formatting

--- library/membrane/auxes.coffee
+++ library/membrane/auxes.coffee
@@ -63,8 +63,8 @@
     # Please be sure invoke the `next` arg to proceed, if relevant.
     headers: (request, response, resource, domain, next) ->
         assert auxiliaries = @constructor.aux() or {}
+        hosting = try @constructor.identify().underline
         mapper = (closure) -> _.map auxiliaries, closure
-        hosting = try @constructor.identify().underline
         routines = mapper (value, key) -> (callback) ->
             assert _.isObject singleton = value.obtain()
             message = "Cascading headers from %s to %s @ %s"


reduce the logging output

reduce the logging output

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -221,12 +221,10 @@
         return @beacon = timer interval, (parameters) =>
             assert unix = moment().unix().toString()
             pulse = "Kernel beacon pulse at a %s UNIX"
-            finished = "Done firing off beacon at the %s"
             assert services = @router.registry or Array()
             logger.debug pulse.magenta, "#{unix}".bold
             prepared = _.map(services, eua) or Array()
-            async.series prepared, (error, results) ->
-                logger.debug finished, "#{unix}".bold
+            async.series prepared, (err, res) -> null

     # Instantiate a hot swapping watcher for this kernel and setup
     # the watcher per the scoping configuration to watch for certain


Adjustment to addLink function that accommodates events being sent from delegateEvents

--- ui/coffee/edit-views.coffee
+++ ui/coffee/edit-views.coffee
@@ -173,7 +173,7 @@
               root.app.filesView.render()

               # Update the UI, Links
-              root.app.linksView.addLink loc, filename
+              root.app.linksView.addLink null, loc, filename

               $('#new-file-dialog').dialog 'close'
               $('#new-file-dialog').remove()
@@ -324,11 +324,11 @@
   collapse: (evt) ->
     collapseFieldsets evt.target

-  addLink: (url = null, name = null) ->
+  addLink: (evt, url = null, name = null) ->
     linkOpts =
       schemaName: 'link'
     linkOpts.URL = url if url?
-    linkOpts.Name = name if url?
+    linkOpts.Name = name if name?
     link = new root.Link linkOpts
     link.schema = root.app.schemas.link
     root.app.linksView.model.add link


selection highlighting for tutorial list

--- client/app/Applications/Activity.kdapplication/ListItems/SelectableActivityListItemTutorial.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/SelectableActivityListItemTutorial.coffee
@@ -107,15 +107,8 @@

   click:(event)->

-    log @parent, @parent.$("div")
-    @parent.$("div.tutorial.selectable.selected").each (i,e)->
-      log i;e.removeClass "selected"
-    # $(event.target).parent("div.kdlistview").each (i, element)->
-      # element.removeClass "selected"
+    @parent.parent.$("div.tutorial.selected").removeClass "selected"
     @setClass "selected"
-  #   if $(event.target).closest("[data-paths~=title],[data-paths~=body],[data-paths~=preview]")
-  #     if not $(event.target).is("a.action-link, a.count, .like-view")
-  #       appManager.tell "Activity", "createContentDisplay", @getData()

   applyTextExpansions:(str = "")->
     str = @utils.expandUsernames str


Test: FlexSplit: FlexSplit test added for all cases


stray log

stray log

--- client/apps/verticals/routes.coffee
+++ client/apps/verticals/routes.coffee
@@ -19,7 +19,6 @@
 @save = (req, res) ->
   data = _.pick req.body, _.identity
   data.featured_links = _.compact data.featured_links
-  console.log data
   new Vertical(id: req.params.id).save data,
     headers: 'X-Access-Token': req.user?.get('access_token')
     error: res.backboneError


Top team scoreboard progression appears to be working.

--- web/coffee/progression_graphs.coffee
+++ web/coffee/progression_graphs.coffee
@@ -2,6 +2,20 @@

 divFromSelector = (selector) ->
   _.first($(selector))
+
+topTeamsGraphOptions = {
+  title: "Top Team Score Progression",
+  legend: {
+    position: "none"
+  },
+  vAxis: {
+    title: "Score"
+  },
+  hAxis: {
+    ticks: []
+  },
+  pointSize: 3
+}

 teamGraphOptions = {
   title: "Team Score Progression",
@@ -17,9 +31,7 @@
   pointSize: 3
 }

-timestampsToBuckets = (samples, key, seconds) ->
-  min = _.first(samples)[key]
-  max = _.last(samples)[key]
+timestampsToBuckets = (samples, key, min, max, seconds) ->

   bucketNumber = (number) ->
     Math.floor((number - min) / seconds)
@@ -32,7 +44,7 @@

   buckets = _.groupBy samples, (sample) ->
     bucketNumber sample[key]
-
+
   return _.extend continuousBucket, buckets

 maxValuesFromBucketsExtended = (buckets, sampleKey) ->
@@ -53,15 +65,48 @@
   return maxValues

 progressionDataToPoints = (data, bucketWindow, dataPoints) ->
-      buckets = timestampsToBuckets data, "time", bucketWindow
-      steps = maxValuesFromBucketsExtended buckets, "score"

-      if steps.length < dataPoints
-        return steps
+  sortedData = _.sortBy _.flatten(data), (submission) ->
+    return submission.time

-      return _.rest(steps, steps.length - dataPoints)
+  min = _.first(sortedData).time
+  max = _.last(sortedData).time

-@drawTeamProgressionGraph = (selector, points) ->
+  dataSets = []
+
+  _.each data, (teamData) ->
+    buckets = timestampsToBuckets teamData, "time", min, max, bucketWindow
+    steps = maxValuesFromBucketsExtended buckets, "score"
+
+    if steps.length > dataPoints
+      steps = _.rest(steps, steps.length - dataPoints)
+
+    dataSets.push steps
+
+  #Avoid returning a two dimensional array with 1 element
+  return if dataSets.length > 1 then dataSets else _.first(dataSets)
+
+@drawTopTeamsProgressionGraph = (selector) ->
+  div = divFromSelector selector
+  apiCall "GET", "/api/stats/top_teams/score_progression", {}
+  .done (data) ->
+    if data.data.length > 0
+
+      scoreData = (team.score_progression for team in data.data)
+      dataPoints = _.zip.apply _, progressionDataToPoints scoreData, 60, 100
+
+      teamNameData = (team.name for team in data.data)
+
+      graphData = [["Score"].concat(teamNameData)]
+      _.each dataPoints, (dataPoint) ->
+        graphData.push [""].concat(dataPoint)
+
+      packagedData = google.visualization.arrayToDataTable graphData
+
+      chart = new google.visualization.SteppedAreaChart(div)
+      chart.draw(packagedData, topTeamsGraphOptions)
+
+@drawTeamProgressionGraph = (selector) ->
   div = divFromSelector selector
   apiCall "GET", "/api/stats/team/score_progression", {}
   .done (data) ->
@@ -71,11 +116,8 @@
         ["Time", "Score", {role: "tooltip"}]
       ]

-      steps = progressionDataToPoints data.data, 600, 30
-
+      steps = progressionDataToPoints data.data, 60, 100
       (graphData.push(["", score, score]) for score in steps)
-
-      console.log graphData

       packagedData = google.visualization.arrayToDataTable graphData


[helper] Check if account is defined

[helper] Check if account is defined

--- server/lib/server/helpers.coffee
+++ server/lib/server/helpers.coffee
@@ -113,9 +113,10 @@
       return callback null, no, {}  if err or not models?.first
       user = models.last
       user.fetchAccount "koding", (err, account)->
-        if err or account.type is 'unregistered'
-        then callback err, no, account
-        else callback null, yes, account
+        if err or not account or account.type is 'unregistered'
+          return callback err, no, account
+
+        return callback null, yes, account

 saveOauthToSession = (oauthInfo, clientId, provider, callback)->
   {JSession}                       = koding.models


Update f5.coffee

Update f5.coffee

--- data/points/f5.coffee
+++ data/points/f5.coffee
@@ -1,6 +1,6 @@
 id: "f5"
 title: "F5 网页开发调试工具"
 function: [ "debug-tools" ]
-language: [ "html", "javascript" , "CSS"]
+language: [ "html", "javascript" , "css"]
 homepage: "http://www.getf5.com/"
 desc: "[F5] 是支持所有编辑器和浏览器的网页开发免刷工具,让你在保存代码的同时，自动刷新页面"


Disable talk links

Disable talk links

--- app/index.coffee
+++ app/index.coffee
@@ -11,6 +11,10 @@
 Api = require 'zooniverse/lib/api'
 TopBar = require 'zooniverse/lib/controllers/top_bar'
 User = require 'zooniverse/lib/models/user'
+
+# Temporarily disable Talk links
+$(document).on 'click', 'a[href*="talk"]', (e) ->
+  e.preventDefault(); alert 'Talk is currently unavailable. Sorry!'

 app = {}


small refactor

small refactor

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -262,9 +262,7 @@

         # borrow the renderer to do the initial render
         owner = _sceneUsingRenderer
-        wasDynamic = false
-        if owner? and owner.rendererType is 'dynamic'
-            wasDynamic = true
+        wasDynamic = owner? and owner.rendererType is 'dynamic'

         @opts.renderer ?= _defaultRendererType
         @setDynamicRenderer() # used to do the first render


Increase page size for service list

Increase page size for service list

--- src/models.coffee
+++ src/models.coffee
@@ -759,6 +759,7 @@
         initialize: ->
             super
             @chosenService = null
+            @pageSize = 1000
         expand: (id, spinnerOptions = {}) ->
             if not id
                 @chosenService = null


fullpieagent testing

fullpieagent testing


Refactored Router.go() to accept either a route name or path.

--- src/routes.coffee
+++ src/routes.coffee
@@ -55,8 +55,10 @@
       }

     # When switching, remember the last route.
-    Router.go = ->
-      Router.setLastPathAsCurrent()
+    Router.go = (routeNameOrPath) ->
+      current = Router.getCurrentPath()
+      unless routeNameOrPath == current.path || Router.getCurrentName() == routeNameOrPath
+        Router.setLastPathAsCurrent()
       origGoFunc.apply(@, arguments)

     Router.initLastPath = ->


runner: Successful run of a MicroFlo testcase

--- src/runner.coffee
+++ src/runner.coffee
@@ -6,6 +6,7 @@
 fbpClient = require 'fbp-protocol-client'

 debug = console.log
+debug = () ->

 randomString = (n) ->
   text = "";
@@ -77,9 +78,13 @@
 startNetwork = (runtime, graphId, callback) ->
   debug 'startnetwork', graphId

-  runtime.once 'execution', (status) ->
-    return callback new Error 'Network not started after network:start' if not status.started
-    return callback null
+  waitForStarted = (status) ->
+    debug 'runtime status change', status
+    if status.started
+      runtime.removeListener 'execution', waitForStarted
+      return callback null
+
+  runtime.on 'execution', waitForStarted

   runtime.sendNetwork 'start',
     graph: graphId
@@ -147,25 +152,24 @@
     debug 'runtest', "\"#{testcase.name}\""

     received = {}
-    onReceived = (port, data) ->
+    onReceived = (port, data) =>
         received[port] = data
-        nExpected = Object.keys(tcase.expect).length
+        nExpected = Object.keys(testcase.expect).length
         if Object.keys(received).length == nExpected
           @client.removeListener 'runtime', checkPacket
           return callback null, received

-    checkPacket = (msg) ->
+    checkPacket = (msg) =>
       d = msg.payload
-      if msg.command == 'packet' and d.event == 'data' and d.graph == @currentGraphId
+      # FIXME: also check # and d.graph == @currentGraphId
+      if msg.command == 'packet' and d.event == 'data'
         onReceived d.port, d.payload
       else
-        debug 'recv network', msg
-
+        debug 'unknown runtime message', msg
     @client.on 'runtime', checkPacket

     # send input packets
     sendPackets @client, @currentGraphId, testcase.inputs, (err) =>
-      @client.removeListener 'runtime', checkPacket
       return callback err if err

@@ -186,10 +190,17 @@
   runner.setupSuite suite, (err) ->
     return err if err

-    runner.runTest suite.cases[0], (err, received) ->
+    testcase = suite.cases[0]
+    runner.runTest testcase, (err, received) ->
       return err if err

-      chai.expect(received).to.eql suite.cases[0].expect
+      console.log "  #{testcase.name}"
+      err = null
+      try
+        chai.expect(received).to.eql testcase.expect
+      catch e
+        err = e
+      console.log "    #{testcase.assertion}:", if not e then '✓' else "✗\n #{e.message}"

       runner.teardownSuite suite, (err) ->
         return err if err


server favicon.ico

server favicon.ico

--- src/server.coffee
+++ src/server.coffee
@@ -6,6 +6,9 @@
 # Serve the client application
 app.get "/", (req, res) ->
   res.sendfile "index.html"
+
+app.get "/favicon.ico", (req, res) ->
+  res.sendfile "favicon.ico"

 app.use "/scripts", express.static "#{__dirname}/scripts"
 app.use "/styles", express.static "#{__dirname}/styles"


Fix margin in the horizontal mode

Fix margin in the horizontal mode

--- views/layout.horizonal.cjsx
+++ views/layout.horizonal.cjsx
@@ -31,8 +31,8 @@
     document.documentElement.style.overflow = 'hidden';
   """
   $('kan-game webview')?.style?.height = $('kan-game webview /deep/ object[is=browserplugin]')?.style?.height = "#{Math.floor(480 * factor)}px"
-  $('kan-game').style.marginTop = "#{(window.innerHeight - 480 * factor - 25) / 2.0}px"
-  $('poi-app').style.marginTop = "#{(window.innerHeight - 480 * factor - 25) / 2.0}px"
+  $('kan-game').style.marginTop = "#{Math.max(0,(window.innerHeight - 480 * factor - 25)) / 2.0}px"
+  $('poi-app').style.marginTop = "#{Math.max(0,(window.innerHeight - 570)) / 2.0}px"
 # interval = setInterval adjustSize, 500
 if !window._delay
   adjustSize()


update title

update title

--- config/test.cson
+++ config/test.cson
@@ -1,4 +1,4 @@
-# production.cson
+# test.cson
 # App configuration settings for production mode.
 # These settings will be used whenever NODE_ENV is set to 'test'
 # This usually happens when running tests via `grunt test`.


Social: remove oldemail

Social: remove oldemail

--- workers/social/lib/social/models/oldemail.coffee
+++ workers/social/lib/social/models/oldemail.coffee
@@ -1,128 +0,0 @@
-# We're not longer using JMail to send emails. See `email.coffee`,
-# this is here just for legacy reasons.
-{ Model, signature } = require 'bongo'
-
-{ v4: createId } = require 'node-uuid'
-
-getUniqueId = createId
-
-module.exports = class JMail extends Model
-
-  @share()
-
-  @set
-    indexes           :
-      status          : 'sparse'
-    sharedMethods     :
-      static          :
-        unsubscribeWithId:
-          (signature String, String, String, Function)
-    sharedEvents      :
-      instance        : []
-      static          : []
-    schema            :
-      dateIssued      :
-        type          : Date
-        default       : -> new Date
-      dateAttempted   : Date
-      dateDelivered   : Date
-      email           :
-        type          : String
-        email         : yes
-      from            :
-        type          : String
-        email         : yes
-        default       : 'hello@koding.com'
-      replyto         :
-        type          : String
-        email         : yes
-        default       : 'hello@koding.com'
-      status          :
-        type          : String
-        default       : 'queued'
-        enum          : ['Invalid status'
-                        [
-                          'queued'
-                          'sending'
-                          'attempted'
-                          'failed'
-                          'delivered'
-                          'unsubscribed'
-                        ]]
-      smtpId          : String
-      redemptionToken : String
-      force           :
-        type          : Boolean
-        default       : false
-      subject         : String
-      content         : String
-      unsubscribeId   : String
-      bcc             : String
-      properties      : Object
-
-  save:(callback) ->
-    @unsubscribeId = getUniqueId() + ''  unless @_id? or @force
-    super
-
-  isUnsubscribed:(callback) ->
-    JUnsubscribedMail = require './unsubscribedmail'
-    JUnsubscribedMail.isUnsubscribed @email, callback
-
-  @markDelivered = (status, callback = (->)) ->
-    smtpId = do ([_, id] = status['smtp-id'].match /^<(.+)>$/) -> id
-
-    unless smtpId?
-      return process.nextTick -> callback { message: 'Unknown SMTP id' }
-
-    @one { smtpId }, (err, mail) ->
-      return callback err  if err
-      return callback { message: 'Unrecognized SMTP id' }  unless mail?
-
-      operation =
-        $set              :
-          status          : 'delivered'
-          dateDelivered   : status.timestamp * 1000 # milliseconds
-
-      mail.update operation, callback
-
-  @unsubscribeWithId = (unsubscribeId, email, opt, callback) ->
-    JMail.one { email, unsubscribeId }, (err, mail) ->
-      return callback err  if err or not mail
-
-      JUnsubscribedMail = require './unsubscribedmail'
-      JUnsubscribedMail.one { email }, (err, unsubscribed) ->
-        return callback err  if err
-        return callback null, 'Email was already unsubscribed'  if unsubscribed
-
-        unsubscribed = new JUnsubscribedMail { email }
-        unsubscribed.save (err) ->
-          callback err, unless err then 'Successfully unsubscribed' else null
-
-  @fetchWithUnsubscribedInfo = (selector, options, callback) ->
-    JUnsubscribedMail = require './unsubscribedmail'
-
-    JMail.some selector, options, (err, mails) ->
-      return callback err  if err
-      return unless mails?.length > 0
-
-      emailsToCheck = (mail.email  for mail in mails when not mail.force)
-
-      JUnsubscribedMail.some { email: { $in: emailsToCheck } }, {}, (err, uMails) ->
-        return callback err  if err
-
-        unsubscribedEmails = (uMail.email  for uMail in uMails)
-
-        cleanedMails = []
-        for mail in mails
-          unless mail.email in unsubscribedEmails
-            cleanedMails.push mail
-          else
-            mail.update { $set: { status: 'unsubscribed' } }, (err) ->
-              # it's not fatal enough to break the process
-              # also we don't wait for this to proceed with sending the emails
-              # doing a callback(err) here can have unexpected outcomes
-              console.log err  if err
-
-        callback null, cleanedMails
-
-


Refactored view to make state more explicit

--- core/app/backbone/views/evidence/add_evidence_view.coffee
+++ core/app/backbone/views/evidence/add_evidence_view.coffee
@@ -10,50 +10,55 @@
     buttons: '.js-buttons'

   events:
-    'click .js-cancel': 'cancel'
-    'click .js-supporting-button': -> @_showAdd 'supporting'
-    'click .js-weakening-button': -> @_showAdd 'weakening'
+    'click .js-cancel': -> @model.set 'boxType', 'buttons'
+    'click .js-supporting-button': -> @model.set 'boxType', 'supporting'
+    'click .js-weakening-button': -> @model.set 'boxType', 'weakening'

   regions:
     headingRegion: '.js-heading-region'
     contentRegion: '.js-content-region'

   collectionEvents:
-    'saved_added_model': 'cancel'
-    'error_adding_model': 'showBox'
-    'add': 'hideBox'
-    'request sync': '_updateLoading'
+    'saved_added_model': -> @model.set 'boxType', 'buttons'
+    'error_adding_model': -> @model.set 'saving', false
+    'add': -> @model.set 'saving', true
+    'request sync': '_update'

-  onRender: ->
+  initialize: ->
+    @model = new Backbone.Model saving: false, boxType: 'buttons'
+    @listenTo @model, 'change', @_update
+
+  onRender: -> @_update()
+
+  _update: ->
+    @$el.toggle !@collection.loading()
+
+    @ui.buttons.toggle @model.get('boxType') == 'buttons'
+    @ui.box.toggle @model.get('boxType') != 'buttons'
+    @_updateBox()
+    @_updatePopovers()
+
+  _updateBox: ->
+    return if @model.get('boxType') == 'buttons'
+    return if @boxType == @model.get('boxType')
+
+    @boxType = @model.get('boxType')
+
+    @ui.box.removeClass 'evidence-weakening evidence-supporting'
+    @ui.box.addClass 'evidence-' + @model.get('boxType')
+
     @headingRegion.show new EvidenceishHeadingView model: currentUser
-    @cancel()
-    @_updateLoading()
+    @contentRegion.show new AddEvidenceFormView
+      collection: @collection.oneSidedEvidenceCollection @model.get('boxType')
+      fact_id: @options.fact_id
+      type: @model.get('boxType')

-  cancel: ->
-    @hideBox()
-    @ui.buttons.show()
+  _updatePopovers: ->
+    return if @collection.loading()
+    return unless @model.get('boxType') == 'buttons'
+    return if @_popoversRendered

-  showBox: -> @ui.box.show()
-  hideBox: -> @ui.box.hide()
-
-  _showAdd: (type) ->
-    @ui.buttons.hide()
-    @ui.box.show()
-    @ui.box.removeClass 'evidence-weakening evidence-supporting'
-    @ui.box.addClass 'evidence-' + type
-
-    @contentRegion.close()
-    @contentRegion.show new AddEvidenceFormView
-      collection: @collection.oneSidedEvidenceCollection(type)
-      fact_id: @options.fact_id
-      type: type
-
-  _updateLoading: ->
-    @$el.toggle !@collection.loading()
-    @_renderPopovers() unless @collection.loading()
-
-  _renderPopovers: ->
-    @popoverResetAll()
+    @_popoversRendered = true

     @popoverAdd '.js-supporting-button',
       side: 'right'
@@ -68,4 +73,3 @@
       container: @ui.buttons
       contentView: new TextView text: 'Add weakening argument'
       popover_className: 'translucent-popover translucent-grey-popover'
-


fix(search): Command-alt-f to highlight search

Fixes T3011

--- keymaps/base.cson
+++ keymaps/base.cson
@@ -7,6 +7,7 @@
 'body':
   'c'        : 'application:new-message' # Gmail
   '/'        : 'application:focus-search' # Gmail
+  'cmd-alt-f': 'application:focus-search'

   'r'     : 'application:reply' # Gmail
   'R'     : 'application:reply-all' # Nylas Mail


Check if item exists before trying to fetch its first element

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
@@ -101,7 +101,7 @@

     @syntaxMode = new KDCustomHTMLView
       tagName  : "strong"
-      partial  : __aceSettings.syntaxAssociations[syntax][0] or syntax
+      partial  : __aceSettings.syntaxAssociations[syntax]?[0] or syntax

     @saveButton = new KDButtonView
       title     : ""


KodingAppsController: prepareApplication re-written to work with provided machine instance instead vmController and vmName. And now it removes app.skeleton dependency from oskite

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -316,7 +316,7 @@
                   messages        :
                     regExp        : "For Application name only lowercase letters are allowed!"

-  prepareApplication:(name, callback)->
+  prepareApplication:({ machine, name }, callback)->

     unless name
       return new KDNotificationView
@@ -325,49 +325,60 @@
     type         = "blank"
     appname      = KD.utils.slugify name.replace /[^a-zA-Z]/g, ''
     APPNAME      = appname.capitalize()
-    manifestStr  = defaultManifest type, APPNAME
-    changeLogStr = @_createChangeLog APPNAME
+    manifestStr  = AppSkeleton.manifest type, APPNAME
+    changeLogStr = AppSkeleton.changeLog APPNAME
     manifest     = JSON.parse manifestStr
     appPath      = @getAppPath manifest

-    {vmController} = KD.singletons
-
-    vmController.run
-      method    : "app.skeleton"
-      withArgs  :
-        type    : "blank"
-        appPath : appPath
+    machine.getBaseKite()
+
+    .exec
+      command : "mkdir -p #{appPath}/resources"

     .then ->
-      indexFile = FSHelper.createFileInstance path:  "#{appPath}/index.coffee"
-      indexFile.fetchContents().then (content) ->
-        content = content.replace(/\%\%APPNAME\%\%/g, APPNAME)
-                         .replace(/\%\%appname\%\%/g, appname)
-                         .replace(/\%\%AUTHOR\%\%/g , KD.nick())
-        indexFile.save content
+      indexFile = FSHelper.createFileInstance {
+        path: "#{appPath}/index.coffee", machine
+      }
+      content = AppSkeleton.indexCoffee
+                  .replace /\%\%APPNAME\%\%/g, APPNAME
+                  .replace /\%\%appname\%\%/g, appname
+                  .replace /\%\%AUTHOR\%\%/g , KD.nick()
+
+      indexFile.save content

     .then ->
-      styleFile = FSHelper.createFileInstance path:  "#{appPath}/resources/style.css"
-      styleFile.fetchContents().then (content) ->
-        content = content.replace(/\%\%appname\%\%/g, appname)
-        styleFile.save content
+      styleFile = FSHelper.createFileInstance {
+        path: "#{appPath}/resources/style.css", machine
+      }
+      content = AppSkeleton.styleCss
+                  .replace /\%\%appname\%\%/g, appname
+      styleFile.save content

     .then ->
-      readmeFile = FSHelper.createFileInstance path:  "#{appPath}/README.md"
-      readmeFile.fetchContents().then (content) ->
-        author  = Encoder.htmlDecode(KD.utils.getFullnameFromAccount())
-        content = content
-          .replace(/\%\%APPNAME\%\%/g, APPNAME)
-          .replace(/\%\%AUTHOR_FULLNAME\%\%/g , author)
-        readmeFile.save content
+      readmeFile = FSHelper.createFileInstance {
+        path: "#{appPath}/README.md", machine
+      }
+      author  = Encoder.htmlDecode(KD.utils.getFullnameFromAccount())
+      content = AppSkeleton.readmeMd
+        .replace /\%\%APPNAME\%\%/g, APPNAME
+        .replace /\%\%AUTHOR_FULLNAME\%\%/g , author
+      readmeFile.save content

     .then ->
-      FSHelper.createFileInstance path: ("#{appPath}/manifest.json")
-              .save manifestStr
+
+      FSHelper.createFileInstance {
+        path: "#{appPath}/manifest.json", machine
+      }
+
+      .save manifestStr

     .then ->
-      FSHelper.createFileInstance path: ("#{appPath}/ChangeLog")
-              .save changeLogStr
+
+      FSHelper.createFileInstance {
+        path: "#{appPath}/ChangeLog", machine
+      }
+
+      .save manifestStr

     .then ->
       return { appPath }


Shut node-debug logger up.

Shut node-debug logger up.

--- app/config/config.coffee
+++ app/config/config.coffee
@@ -16,7 +16,7 @@
 exports.env[currentEnv] = true

 # log config
-if currentEnv isnt 'production' then process.env['DEBUG'] = '*' # node-debug
+#if currentEnv isnt 'production' then process.env['DEBUG'] = '*' # node-debug
 exports.log4js = require('log4js')
 exports.log4js.configure('./app/config/log4js.json', { reloadSecs: 60, cwd: "./var/log" })
 exports.getLogger = exports.log4js.getLogger # helper


use osu.timeago for account standing

use osu.timeago for account standing

--- resources/assets/coffee/react/profile-page/account-standing.coffee
+++ resources/assets/coffee/react/profile-page/account-standing.coffee
@@ -3,7 +3,7 @@

 import { ExtraHeader } from './extra-header'
 import * as React from 'react'
-import { a, div, span, h3, table, thead, tbody, tr, th, td, time } from 'react-dom-factories'
+import { a, div, span, h3, table, thead, tbody, tr, th, td } from 'react-dom-factories'
 el = React.createElement

 bn = 'profile-extra-recent-infringements'
@@ -61,11 +61,8 @@
         key: i

         td
-          className: "#{bn}__table-cell #{bn}__table-cell--date"
-          time
-            className: "timeago"
-            dateTime: event.timestamp
-            moment(event.timestamp).fromNow()
+          className: "#{bn}__table-cell #{bn}__table-cell--date", dangerouslySetInnerHTML:
+            __html: osu.timeago(event.timestamp)

         td
           className: "#{bn}__table-cell #{bn}__table-cell--action"


fix reset password form has errors on focus

--- _source/packages/newspring:core/client/templates/objects/signup/reset-password.coffee
+++ _source/packages/newspring:core/client/templates/objects/signup/reset-password.coffee
@@ -51,6 +51,9 @@

 Template.resetPassword.events

+  "focus input": (event, template) ->
+    template.hasErrors.set false
+
   "submit #reset-password": (event, template) ->
     event.preventDefault()


(feature) Require esprima via npm.

(feature) Require esprima via npm.

--- src/jailed_sandbox.coffee
+++ src/jailed_sandbox.coffee
@@ -1,6 +1,7 @@
 if !jailed?
   console.error("make sure to load jailed before the advanced markdown javascript sandbox")

+esprima = require 'esprima'
 _ = require 'lodash'
 once = require 'once'


Add fall back for browsers that don't support e.preventDefault

--- src/status.coffee
+++ src/status.coffee
@@ -90,7 +90,7 @@
     @update_updated_time(true, true)

     elements.widget.addEventListener "click", (e) ->
-      e.preventDefault()
+      if e.preventDefault then e.preventDefault() else e.returnValue = false
       e.stopPropagation()
       state = (elements.pane.dataset.open == "false")
       _this.update_updated_time() if state
@@ -130,7 +130,7 @@
       source.addEventListener event, listener, false

   error_listener: (e) ->
-    e.preventDefault()
+    if e.preventDefault then e.preventDefault() else e.returnValue = false
     if e.readyState == window["EventSource"].CLOSED
       console.log "#{_prefix} Connection closed."
     false


Fix until chaining

Fix until chaining

--- src/stdlib.coffee
+++ src/stdlib.coffee
@@ -223,7 +223,7 @@
     callback.apply(newScope)
     # Chain untils() to run sequentially
     {
-      then: (_delay, _callback) => @after(delay, (=> @until(_delay, _callback)))
+      until: (_delay, _callback) => @after(delay, (=> @until(_delay, _callback)))
     }

   #


added oc notification for container

added oc notification for container

--- coffee/lib/services/notification.coffee
+++ coffee/lib/services/notification.coffee
@@ -0,0 +1,18 @@
+###
+# ownCloud
+#
+# @author Bernhard Posselt
+# Copyright (c) 2012 - Bernhard Posselt <nukeawhale@gmail.com>
+#
+# This file is licensed under the Affero General Public License version 3 or
+# later.
+#
+# See the COPYING-README file
+#
+###
+
+###
+# Inject notification into angular to make testing easier
+###
+angular.module('OC').factory 'Notification', ->
+        return OC.Notification


Prettify the JSON response if possible

Prettify the JSON response if possible

--- viewer/app.coffee
+++ viewer/app.coffee
@@ -44,13 +44,18 @@
                     settings    : JSON.parse(data.files['settings.json'].content)
                 htmlResponse(response, content)
             else
+                raw_response = github_response.raw.toString()
+                try
+                    github_response_content = JSON.stringify((JSON.parse(raw_response)), null, 4)
+                catch e
+                    github_response_content = raw_response
                 content = """
                     Valid Proto Gist not found at
                     api.github.com/gists#{ request.url }:
                     #{ github_response.statusCode }

-                    #{ github_response.raw.toString() }
+                    #{ github_response_content }

                 """
                 htmlResponse(response, content, 404)


Better logging for authorization.

Better logging for authorization.

--- src/AccountsUtil.coffee
+++ src/AccountsUtil.coffee
@@ -11,9 +11,9 @@
         return unless userId
         user = Meteor.users.findOne(userId)
         username = user.username
-        Logger.info('User subscribed to collection', userId, username, name)
+        Logger.info("User '#{username}' subscribed to collection '#{name}'")
         @onStop ->
-          Logger.info('User unsubscribed from collection', userId, username, name)
+          Logger.info("User '#{username}' unsubscribed from collection '#{name}'")
         if username == 'admin'
           # Admin can see all docs.
           collection.find()


Removed a log.

Removed a log.

--- src/treema.coffee
+++ src/treema.coffee
@@ -321,7 +321,6 @@
           e.preventDefault()

       keyInput.blur (e) =>
-        console.log('blur and remove?')
         @$el.find('.treema-new-prop-error').remove()
         key = keyInput.val()
         if @schema.properties