fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,227 +1,221 @@
 class Dashing.FullpieAgent extends Dashing.Widget
-    @accessor 'data'
-    @width = 750
-
-    onData: (data) ->
-        #$(@node).fadeOut().fadeIn()
-        @update(data.data)
-
-    update: (dataSet) ->
-
-        if !dataSet
-            dataSet = @get("data")
-        if !dataSet
-            return
-
-        # Remove any previous svg
-        $(@node).children("svg").remove();
-
-        width = @width
-        height = 400
-        radius = Math.min(width, height) / 2
-        radiuso = 130
-        radiusi = 65
-        color = d3.scale.category20()
-        pie = d3.layout.pie().value((d) -> d.value)
-        arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
-        svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
-        console.log 'update pie', dataSet
-        #that = this
-        piedata = pie(dataSet)
-        # Remove zero values
-        piedata = piedata.filter (pd) -> pd.value isnt 0
-        console.log 'update pie', piedata
-        #create a marker element if it doesn't already exist
-        defs = svg.select('defs')
-        if defs.empty()
-            defs = svg.append('defs')
-        marker = defs.select('marker#circ')
-        if marker.empty()
-            defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
-        #Create/select <g> elements to hold the different types of graphics
-        #and keep them in the correct drawing order
-        pathGroup = svg.select('g.piePaths')
-        if pathGroup.empty()
-            pathGroup = svg.append('g').attr('class', 'piePaths')
-        pointerGroup = svg.select('g.pointers')
-        if pointerGroup.empty()
-            pointerGroup = svg.append('g').attr('class', 'pointers')
-        labelGroup = svg.select('g.labels')
-        if labelGroup.empty()
-            labelGroup = svg.append('g').attr('class', 'labels')
-        path = pathGroup.selectAll('path.pie').data(piedata)
-        #console.log("path",this.path.empty())
-        path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
-            color i
-        #path.attr('d',arc)
-        #    .exit()
-        path.transition().duration(1500).attrTween 'd', (d,i) ->
+  #@accessor 'value'
+  @accessor 'data'
+  onData: (data) ->
+    #$(@node).fadeOut().fadeIn()
+    @update(data.data)
+  update: (dataSet) ->
+    if !dataSet
+        dataSet = @get("data")
+    if !dataSet
+        return
+    $(@node).children("svg").remove();
+    width = 750
+    height = 400
+    radius = Math.min(width, height) / 2
+    radiuso = 130
+    radiusi = 65
+    color = d3.scale.category20()
+    pie = d3.layout.pie().value((d) -> d.value)
+    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
+    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
+    console.log 'update pie', dataSet
+    #that = this
+    piedata = pie(dataSet)
+    # Remove zero values
+    piedata = piedata.filter (pd) -> pd.value isnt 0
+    console.log 'update pie', piedata
+    #create a marker element if it doesn't already exist
+    defs = svg.select('defs')
+    if defs.empty()
+      defs = svg.append('defs')
+    marker = defs.select('marker#circ')
+    if marker.empty()
+      defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
+    #Create/select <g> elements to hold the different types of graphics
+    #and keep them in the correct drawing order
+    pathGroup = svg.select('g.piePaths')
+    if pathGroup.empty()
+      pathGroup = svg.append('g').attr('class', 'piePaths')
+    pointerGroup = svg.select('g.pointers')
+    if pointerGroup.empty()
+      pointerGroup = svg.append('g').attr('class', 'pointers')
+    labelGroup = svg.select('g.labels')
+    if labelGroup.empty()
+      labelGroup = svg.append('g').attr('class', 'labels')
+    path = pathGroup.selectAll('path.pie').data(piedata)
+    #console.log("path",this.path.empty())
+    path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
+      color i
+    #path.attr('d',arc)
+    #    .exit()
+    path.transition().duration(1500).attrTween 'd', (d,i) ->
+        `var i`
+        theOldDataInPie = oldPieData
+        # Interpolate the arcs in data space
+        s0 = undefined
+        e0 = undefined
+        if theOldDataInPie[i]
+          s0 = theOldDataInPie[i].startAngle
+          e0 = theOldDataInPie[i].endAngle
+        else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
+          s0 = theOldDataInPie[i - 1].endAngle
+          e0 = theOldDataInPie[i - 1].endAngle
+        else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
+          s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+          e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+        else
+          s0 = 0
+          e0 = 0
+        i = d3.interpolate({
+          startAngle: 0
+          endAngle: 0
+        },
+          startAngle: d.startAngle
+          endAngle: d.endAngle)
+        (t) ->
+          b = i(t)
+          return arc b
+    path.exit()
+        .transition()
+        .duration(300)
+        .attrTween("d", (d,i) ->
             `var i`
-            theOldDataInPie = oldPieData
-            # Interpolate the arcs in data space
-            s0 = undefined
-            e0 = undefined
-            if theOldDataInPie[i]
-                s0 = theOldDataInPie[i].startAngle
-                e0 = theOldDataInPie[i].endAngle
-            else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
-                s0 = theOldDataInPie[i - 1].endAngle
-                e0 = theOldDataInPie[i - 1].endAngle
-            else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
-                s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-                e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-            else
-                s0 = 0
-                e0 = 0
+            s0 = 2 * Math.PI
+            e0 = 2 * Math.PI
             i = d3.interpolate({
-                startAngle: 0
-                endAngle: 0
+              startAngle: d.startAngle
+              endAngle: d.endAngle
             },
-                startAngle: d.startAngle
-                endAngle: d.endAngle)
+              startAngle: s0
+              endAngle: e0)
             (t) ->
-                b = i(t)
-                return arc b
-        path.exit()
-            .transition()
-            .duration(300)
-            .attrTween("d", (d,i) ->
-                `var i`
-                s0 = 2 * Math.PI
-                e0 = 2 * Math.PI
-                i = d3.interpolate({
-                    startAngle: d.startAngle
-                    endAngle: d.endAngle
-                },
-                    startAngle: s0
-                    endAngle: e0)
-                (t) ->
-                    b = i(t)
-                    return arc b
-            )
-            .remove()
-        labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
-            p1.startAngle - p2.startAngle
-        ))
-        labels.enter().append('text').attr 'text-anchor', 'middle'
-        labels.exit().remove()
-        labelLayout = d3.geom.quadtree().extent([
-            [
-            -width
-            -height
-            ]
-            [
-            width
-            height
-            ]
-        ]).x((d) ->
-            d.x
-        ).y((d) ->
-            d.y
-        )([])
-        #create an empty quadtree to hold label positions
-        maxLabelWidth = 0
-        maxLabelHeight = 0
-        labels.text((d) ->
-            # Set the text *first*, so we can query the size
-            # of the label with .getBBox()
-            #d.value
-            console.log 'ts: d', d
-            d.data.label
-        ).each((d, i) ->
-            # Move all calculations into the each function.
-            # Position values are stored in the data object
-            # so can be accessed later when drawing the line
-            ### calculate the position of the center marker ###
-            a = (d.startAngle + d.endAngle) / 2
-            #trig functions adjusted to use the angle relative
-            #to the "12 o'clock" vector:
-            #console.log 'ts: a', a
-            #d.cx = Math.sin(a) * (radius - 75)
-            #d.cy = -Math.cos(a) * (radius - 75)
-            d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
-            d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
-            ### calculate the default position for the label,
-                so that the middle of the label is centered in the arc
-            ###
-            bbox = @getBBox()
-            #bbox.width and bbox.height will
-            #describe the size of the label text
-            labelRadius = radius - 20
-            #labelRadius = radiuso * 1.3
-            d.x = Math.sin(a) * labelRadius
-            d.l = d.x - bbox.width / 2 - 2
-            d.r = d.x + bbox.width / 2 + 2
-            #d.y = -Math.cos(a) * (radius - 20)
-            d.y = -Math.cos(a) * (labelRadius)
-            d.b = d.oy = d.y + 5
-            d.t = d.y - bbox.height - 5
-            #console.log 'ts: d', d
-            ### check whether the default position
-                overlaps any other labels
-            ###
-            conflicts = []
-            labelLayout.visit (node, x1, y1, x2, y2) ->
-            #recurse down the tree, adding any overlapping
-            #node is the node in the quadtree,
-            #node.point is the value that we added to the tree
-            #x1,y1,x2,y2 are the bounds of the rectangle that
-            #this node covers
-            if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
-                return true #don't bother visiting children or checking this node
-            p = node.point
-            v = false
-            h = false
-            if p
-                #p is defined, i.e., there is a value stored in this node
-                h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
-                #horizontal conflict
-                v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
-                #vertical conflict
-                if h and v
-                conflicts.push p
-                #add to conflict list
-            return
-            if conflicts.length
-            console.log d, ' conflicts with ', conflicts
-            rightEdge = d3.max(conflicts, (d2) ->
-                `var maxLabelHeight`
-                `var maxLabelWidth`
-                d2.r
-            )
-            d.l = rightEdge
-            d.x = d.l + bbox.width / 2 + 5
-            d.r = d.l + bbox.width + 10
-            else
-            console.log 'no conflicts for ', d
-            ### add this label to the quadtree, so it will show up as a conflict
-                for future labels.
-            ###
-            labelLayout.add d
-            maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
-            maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
-            return
+              b = i(t)
+              return arc b
         )
-        .transition()
-        .attr('x', (d) ->
-            d.x
-        ).attr 'y', (d) ->
-            d.y
-        pointers = pointerGroup.selectAll('path.pointer').data(piedata)
-        pointers.enter()
-            .append('path')
-            .attr('class', 'pointer')
-            .style('fill', 'none')
-            .style('stroke', 'black')
-            .attr('marker-end', 'url(#circ)')
-        pointers.exit().remove()
-        pointers.transition().attr 'd', (d) ->
-            console.log 'ts: d', d
-            if d.cx > d.l
-            console.log 'if d.cx > d.l','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            else
-            console.log 'else', 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-        oldPieData = piedata
+        .remove()
+    labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
+      p1.startAngle - p2.startAngle
+    ))
+    labels.enter().append('text').attr 'text-anchor', 'middle'
+    labels.exit().remove()
+    labelLayout = d3.geom.quadtree().extent([
+      [
+        -width
+        -height
+      ]
+      [
+        width
+        height
+      ]
+    ]).x((d) ->
+      d.x
+    ).y((d) ->
+      d.y
+    )([])
+    #create an empty quadtree to hold label positions
+    maxLabelWidth = 0
+    maxLabelHeight = 0
+    labels.text((d) ->
+      # Set the text *first*, so we can query the size
+      # of the label with .getBBox()
+      #d.value
+      console.log 'ts: d', d
+      d.data.label
+    ).each((d, i) ->
+      # Move all calculations into the each function.
+      # Position values are stored in the data object
+      # so can be accessed later when drawing the line
+      ### calculate the position of the center marker ###
+      a = (d.startAngle + d.endAngle) / 2
+      #trig functions adjusted to use the angle relative
+      #to the "12 o'clock" vector:
+      #console.log 'ts: a', a
+      #d.cx = Math.sin(a) * (radius - 75)
+      #d.cy = -Math.cos(a) * (radius - 75)
+      d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
+      d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
+      ### calculate the default position for the label,
+         so that the middle of the label is centered in the arc
+      ###
+      bbox = @getBBox()
+      #bbox.width and bbox.height will
+      #describe the size of the label text
+      labelRadius = radius - 20
+      #labelRadius = radiuso * 1.3
+      d.x = Math.sin(a) * labelRadius
+      d.l = d.x - bbox.width / 2 - 2
+      d.r = d.x + bbox.width / 2 + 2
+      #d.y = -Math.cos(a) * (radius - 20)
+      d.y = -Math.cos(a) * (labelRadius)
+      d.b = d.oy = d.y + 5
+      d.t = d.y - bbox.height - 5
+      #console.log 'ts: d', d
+      ### check whether the default position
+         overlaps any other labels
+      ###
+      conflicts = []
+      labelLayout.visit (node, x1, y1, x2, y2) ->
+        #recurse down the tree, adding any overlapping
+        #node is the node in the quadtree,
+        #node.point is the value that we added to the tree
+        #x1,y1,x2,y2 are the bounds of the rectangle that
+        #this node covers
+        if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
+          return true #don't bother visiting children or checking this node
+        p = node.point
+        v = false
+        h = false
+        if p
+          #p is defined, i.e., there is a value stored in this node
+          h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
+          #horizontal conflict
+          v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
+          #vertical conflict
+          if h and v
+            conflicts.push p
+          #add to conflict list
         return
+      if conflicts.length
+        console.log d, ' conflicts with ', conflicts
+        rightEdge = d3.max(conflicts, (d2) ->
+          `var maxLabelHeight`
+          `var maxLabelWidth`
+          d2.r
+        )
+        d.l = rightEdge
+        d.x = d.l + bbox.width / 2 + 5
+        d.r = d.l + bbox.width + 10
+      else
+        console.log 'no conflicts for ', d
+      ### add this label to the quadtree, so it will show up as a conflict
+         for future labels.
+      ###
+      labelLayout.add d
+      maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
+      maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
+      return
+    )
+    .transition()
+    .attr('x', (d) ->
+      d.x
+    ).attr 'y', (d) ->
+      d.y
+    pointers = pointerGroup.selectAll('path.pointer').data(piedata)
+    pointers.enter()
+        .append('path')
+        .attr('class', 'pointer')
+        .style('fill', 'none')
+        .style('stroke', 'black')
+        .attr('marker-end', 'url(#circ)')
+    pointers.exit().remove()
+    pointers.transition().attr 'd', (d) ->
+      console.log 'ts: d', d
+      if d.cx > d.l
+        console.log 'if d.cx > d.l','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+      else
+        console.log 'else', 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+    oldPieData = piedata
+    return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -106,7 +106,7 @@
             r = -r
         #return "translate(" + (x/h * labelRadius) + "," + (y/h * labelRadius) + ")"
         #return "translate(" + r + "," + y + ")"#(y/h * labelRadius) + ")"
-        return "translate(" + ((x/h * labelRadius)+r) + "," + y*my + ")"#(y/h * labelRadius) + ")"
+        return "translate(" + ((x/h * labelRadius)*mx) + "," + y*my + ")"#(y/h * labelRadius) + ")"
         )

         .attr('fill', "#fff")


VM: Fetch vm meta too in fieldsToFetch method.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -563,15 +563,15 @@

       selector.users = $elemMatch: id: user.getId()

-      fieldsToFetch = { hostnameAlias: 1, region: 1, hostKite: 1, stack: 1 }
+      fieldsToFetch = { hostnameAlias: 1, region: 1, hostKite: 1, stack: 1, meta: 1 }
       JVM.someData selector, fieldsToFetch, options, (err, cursor)->
         return callback err  if err

         cursor.toArray (err, arr) ->
           return callback err  if err
-          callback null, arr.map ({ hostnameAlias, region, hostKite, stack }) ->
+          callback null, arr.map ({ hostnameAlias, region, hostKite, stack, meta }) ->
             hostKite = null  if hostKite in ['(banned)', '(maintenance)']
-            { hostnameAlias, region, hostKite, stack }
+            { hostnameAlias, region, hostKite, stack, meta }

   @fetchVmsByContext = secure (client, options, callback) ->
     {connection:{delegate}, context:{group}} = client


only include dropzone on web client for now.

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -33,7 +33,9 @@
 require('react-native-listener')
 require('react-bootstrap')
 require('react-timeago')
-require('react-dropzone-component')
+
+if window?
+    require('react-dropzone-component')

 # end "don't delete"


Defend against undefined invites and members

--- services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
+++ services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
@@ -43,10 +43,10 @@
 							contact.display = contact.name

 		getCurrentMemberEmails = () ->
-			$scope.project.members.map (u) -> u.email
+			($scope.project.members || []).map (u) -> u.email

 		getCurrentInviteEmails = () ->
-			$scope.project.invites.map (u) -> u.email
+			($scope.project.invites || []).map (u) -> u.email

 		$scope.filterAutocompleteUsers = ($query) ->
 			currentMemberEmails = getCurrentMemberEmails()


Use init() instead of constructor()

Use init() instead of constructor()

--- src/main.coffee
+++ src/main.coffee
@@ -2,7 +2,7 @@
   a: instance
   conf: instance.conf.plugins["neo4jBackend"]
   graphJSON:  {}
-  constructor: ->
+  init: ->
     conf = @conf

     # Fill in blank settings with the defaults


more cleanups

more cleanups

--- src/main.coffee
+++ src/main.coffee
@@ -43,17 +43,15 @@

 seqreq = require './seqreq'

-saveConfig = ->
-    nconf.save (err) ->
-        console.log 'error while writing config.json', err if err
-
-# Load configuration
+# serverside configuration management
 nconf = require 'nconf'
 nconf.file { file: paths.configpath }
 nconf.defaults {
     'startminimized': false
-    'minimizetotray': false
 }
+saveConfig = ->
+    nconf.save (err) ->
+        console.log 'error while writing config.json', err if err

 mainWindow = null

@@ -81,21 +79,10 @@
 toggleWindowVisible = ->
     if mainWindow.isVisible() then mainWindow.hide() else mainWindow.show()

-toggleDockIcon = ->
-    if nconf.get 'minimizetotray'
-      app.dock.show()
-      nconf.set 'minimizetotray', false
-    else
-      app.dock.hide()
-      nconf.set 'minimizetotray', true
-    saveConfig()
-
 # helper wait promise
 wait = (t) -> Q.Promise (rs) -> setTimeout rs, t

 app.on 'ready', ->
-    if nconf.get 'minimizetotray'
-      app.dock.hide()

     proxycheck = ->
         todo = [
@@ -354,11 +341,9 @@

     # Emitted when the window is about to close.
     # For OSX only hides the window if we're not force closing.
-    # Prevent close, if minimizetotray is enabled.
     mainWindow.on 'close', (ev) ->
-        hideToTray = !readyToClose and nconf.get 'minimizetotray'
         darwinHideOnly = process.platform == 'darwin' and not mainWindow?.forceClose

-        if hideToTray or darwinHideOnly
+        if darwinHideOnly
             ev.preventDefault()
             mainWindow.hide()


output file

output file

--- docscript.coffee
+++ docscript.coffee
@@ -11,16 +11,17 @@
 Mod.set 'Weya', Weya
 Mod.set 'Weya.Base', Weya.Base

-#TODO
 require './coffee/parser'
 require './coffee/nodes'
 require './coffee/reader'

 argv = require 'optimist'
  .usage 'DocScript parser.\n Usage: $0'
- .demand ['i']
+ .demand ['i', 'o']
  .alias 'i', 'input'
  .describe 'i', 'Input file'
+ .alias 'o', 'output'
+ .describe 'o', 'Output file'
  .argv

 input = "#{fs.readFileSync argv.input}"
@@ -39,7 +40,7 @@
     sidebar: sidebar.innerHTML
     code: input

-   fs.writeFileSync './build/static.html', output
+   fs.writeFileSync "./build/#{argv.output}.html", output

 Mod.initialize()


Generated link fixes

Generated link fixes

--- packages/shrub-grunt/dox/dynamic.coffee
+++ packages/shrub-grunt/dox/dynamic.coffee
@@ -371,7 +371,7 @@
         fileStats.file
       }:#{
         todo.index
-      }](source/#{
+      }](../source/#{
         filename
       }##{
         id
@@ -474,9 +474,9 @@
       parts = fileStats.pkg.split '/'
       parentPkg = parts.shift()
       subPkg = parts.join '/'
-      render += "## [#{parentPkg}/#{subPkg}](source/#{sourcePath})"
+      render += "## [#{parentPkg}/#{subPkg}](../source/#{sourcePath})"
     else
-      render += "## [#{fileStats.pkg}](source/#{sourcePath})"
+      render += "## [#{fileStats.pkg}](../source/#{sourcePath})"

     # Naively parse out the file description. It must be wrapped in asterisks,
     # i.e. italicized in markdown.


mqtt: Debug logging for connected/disconnected

--- src/mqtt.coffee
+++ src/mqtt.coffee
@@ -13,6 +13,7 @@
   connect: (callback) ->
     @client = mqtt.connect @address
     onConnected = (err) =>
+      debug 'connected'
       @client.on 'message', (topic, message) =>
         @_onMessage topic, message
       return callback err
@@ -23,6 +24,7 @@
     @client.removeAllListeners 'connect'
     @subscribers = {}
     @client.end (err) =>
+      debug 'disconnected'
       @client = null
       return callback err


fancybox: don't show images as binary. Closes #843

Requires db update. From now on fancybox links to raw images must have
fancybox_image as class

--- app/assets/javascripts/fancybox.coffee
+++ app/assets/javascripts/fancybox.coffee
@@ -5,6 +5,9 @@
     type: 'ajax'
     href: $(this).attr('href')
     autoSize: true
+
+  $("a.fancybox_image").fancybox
+    type: 'image'

   $("a.fancybox_narrow").fancybox
     type: 'ajax'


Remove old commented out client code

Remove old commented out client code

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -11,98 +11,3 @@
   initLanguageSelectors()
   initDom('body')

-
-###
-openNewOrganDialog = ->
-  $("#modal-wrap").show()
-  $("#create-new-organ").fadeIn('fast')
-
-closeNewOrganDialog = ->
-  $("#create-new-organ").fadeOut('fast')
-  $("#modal-wrap").hide()
-
-index = ->
-
-newOrgan = ->
-  openNewOrganDialog()
-
-$(document).ready ->
-  $("select, input:checkbox, input:radio, input:file, input:text, textarea, submit").uniform()
-
-  $("#sidebar a.create-new-organ").click () ->
-    router.setRoute '/organ/new'
-    return false
-
-  $("#create-new-organ a.close-modal").click () ->
-    router.setRoute '/'
-    return false
-
-  $("#create-new-organ select").change () ->
-    organization_id = $(this).val()
-    $.get "organizations.json", (organizations) ->
-      children = _(organizations).filter((org) -> org.parent_id == organization_id)
-      if children.length > 0
-
-    return false
-
-  $("#single-organ a.application-call").click () ->
-    $("#modal-wrap").show()
-    $("#call-for-application").fadeIn('fast')
-    return false
-
-  $("#call-for-application a.close-modal").click () ->
-    $("#call-for-application").fadeOut('fast')
-    $("#modal-wrap").hide()
-    return false
-
-  $("#single-organ .member-card").click () ->
-    $("#modal-wrap").show()
-    $("#received-application").fadeIn('fast')
-    return false
-
-  $("#received-application a.close-modal").click () ->
-    $("#received-application").fadeOut('fast')
-    $("#modal-wrap").hide()
-    return false
-
-  $("#single-organ a.send-message").click () ->
-    $("#modal-wrap").show()
-    $("#send-group-message").fadeIn('fast')
-    return false
-
-  $("#send-group-message a.close-modal").click () ->
-    $("#send-group-message").fadeOut('fast')
-    $("#modal-wrap").hide()
-    return false
-
-  $("#single-call a.send-application").click () ->
-    $("#modal-wrap").show()
-    $("#send-application").fadeIn('fast')
-    return false
-
-  $("#send-application a.close-modal").click () ->
-    $("#send-application").fadeOut('fast')
-    $("#modal-wrap").hide()
-    return false
-
-  $('.tab-content').each ->
-    $(this).children(':gt(0)').hide()
-
-  $('.tabs-list li a').click ->
-    tab = $(this).parents('.tabs').siblings('.tab-content').children('.tab').eq($(this).parent().index())
-    if tab.length > 0
-      tab.show().siblings().hide()
-      $(this).parent().addClass('active').siblings().removeClass 'active'
-      tab.trigger('tabActivated')
-    false
-
-
-  router = Router(
-    "" : index
-    "/organ/new" :
-      on:    newOrgan
-      after: closeNewOrganDialog
-  )
-
-  router.init()
-###


Fixed token message

Fixed token message

--- app/assets/javascripts/polls.js.coffee
+++ app/assets/javascripts/polls.js.coffee
@@ -17,6 +17,7 @@
     $(".js-question-answer").on
       click: =>
         token_message = $(".js-token-message")
-        token_message.html(token_message.html() + "
" + @token + "");
-        token_message.show()
+        if !token_message.is(':visible')
+          token_message.html(token_message.html() + "
" + @token + "");
+          token_message.show()
     false


Make the spoiler tags stop doing weird stuff with turbolinks

--- app/assets/javascripts/posts.js.coffee
+++ app/assets/javascripts/posts.js.coffee
@@ -10,6 +10,7 @@
         success: (html) ->
             $('#preview').html(html)
             $('#preview-accordion').collapse('hide').collapse('show')
+            instrument_spoiler_tags()
             document.location.hash = "#preview-accordion"
         error: (object, thing, text) ->
             $('#preview').html("
 Preview failed.
")
@@ -35,4 +36,7 @@
     if location.hash
       location.hash = location.hash

-$(document).on("turbolinks:load", () -> add_show_more_links())
+window.instrument_spoiler_tags = () ->
+    $('a[href="#s"]').attr("data-turbolinks", "false")
+
+$(document).on("turbolinks:load", () -> add_show_more_links(); instrument_spoiler_tags())


Saves name of last used printer in local storage, fixes #236

--- app/assets/javascripts/print.js.coffee
+++ app/assets/javascripts/print.js.coffee
@@ -1,8 +1,13 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
+printer_local_storage_key = "last-printer-used"

 $ ->
+  printer_name = localStorage.getItem(printer_local_storage_key)
+  if (printer_name)
+    $('#print_printer').val printer_name
+
   if $('#print_printer').length
     $('#print_printer').chosen
       no_results_text: 'No matches'
@@ -33,7 +38,6 @@

     $('.printer-list').on 'click', '.set-printer', ->
-      console.log(this.textContent)
       $('#print_printer').val this.textContent
       $('#print_printer').trigger 'chosen:updated'
       $('#print_printer').trigger 'change'
@@ -41,6 +45,8 @@
     $('#print_printer').on 'change', ->
       selected = $ 'option:selected', this
       setMedia selected.data('media').split(' ')
+      printer_name = selected["0"].textContent
+      localStorage.setItem(printer_local_storage_key, printer_name)

     setMedia $('#print_printer option:selected').data('media').split(' ')


get rid of print statement

get rid of print statement

--- lib/debugger/stepper-view.coffee
+++ lib/debugger/stepper-view.coffee
@@ -70,9 +70,6 @@
       w = rect.width + rect.left - 40 - parseInt(@view.parentElement.style.left)
       if w < 100 then w = 100
       @view.style.maxWidth = w + 'px'
-
-      console.log w
-
       setTimeout((() => process.nextTick(() => window.requestAnimationFrame(@widthListener))), 15*1000/60)

     window.requestAnimationFrame(@widthListener)


Clean modal

Clean modal

--- admin/static/coffee/ui_components/modals.coffee
+++ admin/static/coffee/ui_components/modals.coffee
@@ -46,8 +46,8 @@
                 @$modal.remove()

             # Define @el to be the modal (the root of the view), make sure events perform on it
-            @.setElement @$modal
-            @.delegateEvents()
+            @setElement @$modal
+            @delegateEvents()

             for btn in @custom_buttons
                 @$('.custom_btn_placeholder').append("<button class='btn #{ btn.class_str }' data-loading-text='#{ btn.data_loading_text }'>#{ btn.main_text }</button>")
@@ -62,7 +62,7 @@
             @remove()

         cancel_modal: (e) ->
-            @.hide_modal()
+            @hide_modal()
             e.preventDefault()

         check_keypress_is_enter: (event) =>
@@ -107,7 +107,7 @@
                 on_click: on_click

         find_custom_button: (class_str) =>
-            return @$('.custom_btn_placeholder > .' + class_str)
+            @$('.custom_btn_placeholder > .' + class_str)

     # This is for doing user confirmation easily
     class @ConfirmationDialogModal extends @AbstractModal


Bugfix, closes #254

Bugfix, closes #254

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -120,7 +120,7 @@
       yes              : 'Yes'
       no               : 'No'
       never            : 'Never'
-      doNotShow        : 'Do not show mini-course in the future.'
+    doNotShow          : 'Do not show mini-course in the future.'
     closeButton        : 'Close'


Update fa.coffee (#4866)

Update fa.coffee (#4866)

--- app/locale/fa.coffee
+++ app/locale/fa.coffee
@@ -70,37 +70,37 @@
 #    courses: "Courses"
     blog: "بلاگ"
     forum: "انجمن"
-#    account: "Account"
-#    my_account: "My Account"
-#    profile: "Profile"
+#    account: "اکانت"
+#    my_account: "اکانت من"
+#    profile: "پروفایل"
     home: "خانه"
     contribute: "همکاری"
     legal: "موارد قانونی"
-#    privacy: "Privacy Notice"
+#    privacy: "درباره ی حریم خصوصی"
     about: "درباره"
     contact: "تماس "
     twitter_follow: "دنبال کردن"
-#    my_classrooms: "My Classes"
-#    my_courses: "My Courses"
-#    careers: "Careers"
-#    facebook: "Facebook"
-#    twitter: "Twitter"
-#    create_a_class: "Create a Class"
-#    other: "Other"
-#    learn_to_code: "Learn to Code!"
-#    toggle_nav: "Toggle navigation"
-#    schools: "Schools"
-#    get_involved: "Get Involved"
-#    open_source: "Open source (GitHub)"
-#    support: "Support"
+#    my_classrooms: "کلاس های من"
+#    my_courses: "دوره های من"
+#    careers: "شغل"
+#    facebook: "فیسبوک"
+#    twitter: "توییتر"
+#    create_a_class: "ساختن کلاس"
+#    other: "بقیه"
+#    learn_to_code: "یاد بگیرید به کد!"
+#    toggle_nav: "تغییر مکان"
+#    schools: "مدرسه ها"
+#    get_involved: "گرفتار شدن"
+#    open_source: "باز کردن منبع (GitHub)"
+#    support: "پشتیبانی"
 #    faqs: "FAQs"
-#    copyright_prefix: "Copyright"
-#    copyright_suffix: "All Rights Reserved."
-#    help_pref: "Need help? Email"
-#    help_suff: "and we'll get in touch!"
-#    resource_hub: "Resource Hub"
-#    apcsp: "AP CS Principles"
-#    parent: "Parents"
+#    copyright_prefix: "کپی رایت"
+#    copyright_suffix: "همه حقوق محفوظ است."
+#    help_pref: "به کمک نیاز دارید؟ ایمیل بزنید"
+#    help_suff: "و ما در تماس هستسم"
+#    resource_hub: "منبع های ما"
+#    apcsp: "اصول HubAP CS"
+#    parent: "خانواده"

   modal:
     close: "بستن"
@@ -118,9 +118,9 @@
     subscribe_as_diplomat: "به عنوان یک دیپلمات تعقیبمان کنید"

   play:
-#    play_as: "Play As" # Ladder page
-#    get_course_for_class: "Assign Game Development and more to your classes!"
-#    request_licenses: "Contact our school specialists for details."
+#    play_as: "بازی کردن به عنوان" # Ladder page
+#    get_course_for_class: "به توسعه ی بازی ها و کلاس های خود بیشتر اختصاص دهید!"
+#    request_licenses: "برای جزئیات بیشتر با متخصصین مدرسه تماس بگیرید."
 #    compete: "Compete!" # Course details page
 #    spectate: "Spectate" # Ladder page
 #    players: "players" # Hover over a level on /play


translated "new_home" section to french. (#3728)

translated "new_home" section to french.

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -14,85 +14,84 @@
     for_developers: "Pour développeurs" # Not currently shown on home page.
     or_ipad: "Ou télécharger pour iPad"

-#  new_home:
-#    slogan: "The most engaging game for learning programming."
-#    classroom_edition: "Classroom Edition:"
-#    learn_to_code: "Learn to code:"
-#    teacher: "Teacher"
-#    student: "Student"
-#    play_now: "Play Now"
-#    im_a_teacher: "I'm a Teacher"
-#    im_a_student: "I'm a Student"
-#    learn_more: "Learn more"
-#    classroom_in_a_box: "A classroom in-a-box for teaching computer science."
-#    codecombat_is: "CodeCombat is a platform for students to learn computer science while playing through a real game."
-#    our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
-#    top_screenshots_hint: "Students write code and see their changes update in real-time"
-#    designed_with: "Designed with teachers in mind"
-#    real_code: "Real, typed code"
-#    from_the_first_level: "from the first level"
-#    getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure."
-#    educator_resources: "Educator resources"
-#    course_guides: "and course guides"
-#    teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds."
-#    accessible_to: "Accessible to"
-#    everyone: "everyone"
-#    democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code."
-#    forgot_learning: "I think they actually forgot that they were actually learning something."
-#    wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
-#    why_games: "Why is learning through games important?"
-#    games_reward: "Games reward the productive struggle."
-#    encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
-#    excel: "Games excel at rewarding"
-#    struggle: "productive struggle"
-#    kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
-#    motivating: "motivating"
-#    not_tedious: "not tedious."
-#    gaming_is_good: "Studies suggest gaming is good for children’s brains. (it’s true!)"
-#    game_based: "When game-based learning systems are"
-#    compared: "compared"
-#    conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and"
-#    perform_at_higher_level: "perform at a higher level of achievement"
-#    feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."
-#    real_game: "A real game, played with real coding."
-#    great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
-#    agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
-#    request_demo_title: "Get your students started today!"
-#    request_demo_subtitle: "Request a demo and get your students started in less than an hour."
-#    get_started_title: "Set up your class today"
-#    get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
-#    request_demo: "Request a Demo"
-#    setup_a_class: "Set Up a Class"
-#    have_an_account: "Have an account?"
-#    logged_in_as: "You are currently logged in as"
-#    view_my_classes: "View my classes"
-#    computer_science: "Computer science courses for all ages"
-#    show_me_lesson_time: "Show me lesson time estimates for:"
-#    curriculum: "Total curriculum hours:"
-#    ffa: "Free for all students"
-#    lesson_time: "Lesson time:"
-#    coming_soon: "Coming soon!"
-#    courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)"
-#    boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
-#    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
-#    run_class: "Everything you need to run a computer science class in your school today, no CS background required."
-#    teachers: "Teachers!"
-#    teachers_and_educators: "Teachers & Educators"
-#    class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum."
-#    get_started: "Get Started"
-#    students: "Students:"
-#    join_class: "Join Class"
-#    role: "Your role:"
-#    student_count: "Number of students:"
-#    start_playing_for_free: "Start Playing for Free!"
-#    students_and_players: "Students & Players"
-#    goto_classes: "Go to My Classes"
-#    view_profile: "View My Profile"
-#    view_progress: "View Progress"
-#    check_out_wiki: "Check out our new Educator Wiki"
-#    want_coco: "Want CodeCombat at your school?"
-#    form_select_role: "Select primary role"
-#    form_select_range: "Select class size"
+ new_home:
+    slogan: "Le jeu le plus engageant pour apprendre la programmation."
+    classroom_edition: "Édition Classe:"
+    learn_to_code: "Apprend à programmer:"
+    teacher: "Enseignant"
+    student: "Étudiant"
+    play_now: "Jouez Maintenant"
+    im_a_teacher: "Je suis un enseignant"
+    im_a_student: "Je suis un étudiant"
+    learn_more: "Pour en savoir plus"
+    classroom_in_a_box: "Une classe tout-inclus pour enseigner l'informatique."
+    codecombat_is: "CodeCombat est une plateforme pour les étudiants afin d'apprendre l'informatique tout en jouant."
+    our_courses: "Nos cours ont été testés spécifiquement pour excellé en classe, même si les enseignants ont peu ou pas d'expérience en programmation."
+    top_screenshots_hint: "Les étudiants écrivent du code et voient leurs changements s'appliqués en temps-réel"
+    designed_with: "Concu en pensant aux enseignants"
+    real_code: "Vrai code entré au clavier"
+    from_the_first_level: "à partir du premier niveau"
+    getting_students: "Encourager les étudiants à écrire du code aussi rapidement que possible est primordial afin qu'ils apprennent comment bien structurer et écrire un programme."
+    educator_resources: "Resources pour les enseignants"
+    course_guides: "et les guides de cours"
+    teaching_computer_science: "Enseigner l'informatique ne nécessite pas un diplôme dispendieux, parce que nous fournissons des outils afin d'aider les enseignants, peu importe leurs curriculums."
+    accessible_to: "Accessible à"
+    everyone: "tout le monde"
+    democratizing: "Démocratiser le processus d'apprendre à programmer est au coeur de notre philosophie. Tout le monde devrait être capable d'apprendre la programmation."
+    forgot_learning: "Je crois qu'ils ont oublié qu'ils étaient en train d'apprendre quelquechose."
+    wanted_to_do: " Programmer est quelquechose que j'ai toujours voulu faire, et je n'ai jamais pensé que je pourrais l'apprendre à l'école."
+    why_games: "Pourquoi est-ce qu'apprendre en jouant est important?"
+    games_reward: "Les jeux récompensent l'effort productif."
+    encourage: "Le jeu est une plateforme qui encourage l'interaction, la découverte et l'essaie et erreur. Un bon jeu défit le joeur d'apprendre à maîtriser de nouvelles habiletés au fil du temps, ce qui correspond au même processus critique que l'on retrouve au niveau de l'éducation.
+    excel: "Les jeux sont excellents afin de récompenser"
+    struggle: "l'effort productif"
+    kind_of_struggle: "le type d'effort qui permet d'apprendre, qui nous engage et"
+    motivating: "qui nous motive"
+    not_tedious: "non pas fastidieux."
+    gaming_is_good: "Des études suggèrent que jouer est bon pour le cerveau des enfants. (C'est vrai!)"
+    game_based: "Quand les systèmes d'apprentissage basés sur le jeu sont"
+    compared: "comparés"
+    conventional: "aux méthodes d'évaluation conventionnelles, la différence est claire: les jeux sont meilleurs afin d'aider les enfants à retenir ce qu'ils apprennent, à se concentrer et"
+    perform_at_higher_level: "à performer à un niveau supérieur d'accomplissement"
+    feedback: "Les jeux procurent aussi des résultats en temps réel qui permettent aux étudiants d'ajuster leur solution et de comprendre des concepts de façon plus holistique, au lieu d'être limité aux simples réponses “correct” ou “incorrect”."
+    real_game: "Un vrai jeu, joué en programmant pour de vrai."
+    great_game: "Un bon jeu c'est plus qu'une série d'insignes et d'accomplissements - c'est à propos de l'aventure d'un joueur, des énigmes bien conçues et la capacité d'entreprendre des défis avec confiance et détermination."
+    agency: "CodeCombat est un jeu qui donne aux joueurs cette confiance et détermination à l'aide de notre interpretteur de lignes de code, qui aide les étudiants débutants ainsi qu'avancés à concevoir des programmes bien écrits et valides."
+    request_demo_title: "Débutez avec vos étudiants aujourd'hui!"
+    request_demo_subtitle: "Demandez une démonstration et permettez à vos étudiants de démarrer en moins d'une heure."
+    get_started_title: "Mettez en place votre classe aujourd'hui"
+    get_started_subtitle: "Mettez en place une classe, ajoutez vos étudiants et surveillez leurs progrès au fur et à mesure qu'ils apprennent l'informatique."
+    request_demo: "Demandez une Démonstration"
+    setup_a_class: "Mettre en place une classe"
+    have_an_account: "Avez-vous un compte?"
+    logged_in_as: "Vous êtes présentement enregistré en tant que"
+    view_my_classes: "Voir Mes Classes"
+    computer_science: "Des cours d'informatique pour tous âges"
+    show_me_lesson_time: "Montrez-moi des estimés de temps de cours pour:"
+    curriculum: "Total des heures du curriculum:"
+    ffa: "Gratuis pour tous les étudiants"
+    lesson_time: "Temps de cours:"
+    coming_soon: "Bientôt disponible!"
+    courses_available_in: "Les cours sont disponibles en JavaScript, Python et Java (bientôt disponible!)"
+    boast: "Comprend des énigmes assez complexes pour fasciner autant les joueurs que les programmeurs."
+    winning: "Une combinaison gagnante d'élément de jeux de rôles et programmation qui permet aux enfants d'apprendre de façon légitimement plaisante."
+    run_class: "Tous ce dont vous avez besoin pour enseigner l'informaique dans votre classe aujourd'hui, sans expérience préalable en informatique."
+    teachers: "Enseignants!"
+    teachers_and_educators: "Enseignants et éducateurs"
+    class_in_box: "Apprenez comment notre plateforme classe-tout-inclus s'adapte à votre curriculum."
+    students: "Étudiants:"
+    join_class: "Joindre une classe"
+    role: "Votre rôle:"
+    student_count: "Nombre d'étudiants:"
+    start_playing_for_free: "Commencez à jouer gratuitement!"
+    students_and_players: "Étudiants et joueurs"
+    goto_classes: "Aller à Mes Classes"
+    view_profile: "Voir Mon Profil"
+    view_progress: "Voir Progrès"
+    check_out_wiki: "Visionner notre nouveau Wiki pour Éducateurs"
+    want_coco: "Voulez-vous CodeCombat à votre école?"
+    form_select_role: "Sélectionnez votre rôle principale"
+    form_select_range: "Sélectionnez la taille de votre classe"

   nav:
     play: "Jouer" # The top nav bar entry where players choose which levels to play


Fix clean button

Fix clean button

--- app/controllers/animal_selector.coffee
+++ app/controllers/animal_selector.coffee
@@ -13,6 +13,7 @@
     'keydown input[name="search"]': 'onSearchKeyDown'
     'click [data-animal]': 'onAnimalItemClick'
     'keydown [data-animal]': 'onAnimalItemKeyDown'
+    'click button[name="clear-filters"]': 'onClickClearFilters'

   elements:
     'input[name="search"]': 'searchInput'
@@ -87,6 +88,9 @@
     @itemsContainer.append details.el
     setTimeout details.show, 125

+  onClickClearFilters: ->
+    @clearFilters()
+
   clearFilters: =>
     @set.filter {}, true
     @searchInput.val ''


Fix some grammar, and translate easier part first

--- app/locale/id.coffee
+++ app/locale/id.coffee
@@ -36,7 +36,7 @@
     game_based: "Ketika sistem pembelajaran berbasis permainan"
     compared: "dibandingkan"
     conventional: "terhadap metode penilaian konvensional, perbedaannya jelas: permainan merupakan pilihan yang lebih baik untuk membantu siswa mempertahankan pengetahuan, berkonsentrasi dan"
-    perform_at_higher_level: "tampil di tingkat yang lebih tinggi dari pencapaian"
+    perform_at_higher_level: "tampil di tingkat yang lebih tinggi dari prestasi"
     feedback: "Permainan juga memberikan umpan balik langsung yang memungkinkan siswa untuk menyesuaikan jalan solusi mereka dan memahami konsep-konsep yang lebih holistik, daripada hanya terbatas dengan jawaban “benar” atau “salah” saja."
     real_game: "Sebuah permainan yang sebenarnya, dimainkan dengan pengkodean yang nyata."
     great_game: "Sebuah permainan yang hebat merupakan lebih dari sekedar lencana dan penghargaan - melainkan tentang perjalanan pemain, teka-teki yang dirancang dengan baik, dan kemampuan untuk mengatasi tantangan dengan kepercayaan diri."
@@ -1232,7 +1232,7 @@
     private: "(privat)"
     summary: "Rangkuman"
     average_level: "Level Rata-rata"
-    average_achievements: "Pencapaian Rata-rata"
+    average_achievements: "Prestasi Rata-rata"
     delete_clan: "Hapus Klan"
     leave_clan: "Tinggalkan Klan"
     join_clan: "Gabung Klan"
@@ -1251,7 +1251,7 @@
     not_started_2: "Belum Dimulai"
     view_solution: "Klik untuk melihat solusi."
     view_attempt: "Klik untuk melihat percobaan."
-    latest_achievement: "Pencapaian Terakhir"
+    latest_achievement: "Prestasi Terakhir"
     playtime: "Waktu bermain"
     last_played: "Yang terakhir dimainkan"
     leagues_explanation: "Bermain dalam liga melawan anggota klan lain di instansi arena multipemain."
@@ -1523,9 +1523,9 @@
     project_or_arena: "Proyek atau Arena"
     students_not_assigned: "Siswa yang belum ditetapkan {{courseName}}"
     course_overview: "Ikhtisar Kursus"
-    copy_class_code: "Kopi Kode Kelas"
+    copy_class_code: "Salin Kode Kelas"
     class_code_blurb: "Para Siswa dapat bergabung di kelas kamu menggunakan Kode Kelas. Tidak membutuhkan alamat email ketika membuat akun siswa dengan Kode Kelas ini."
-    copy_class_url: "Kopi URL Kelas"
+    copy_class_url: "Salin URL Kelas"
     class_join_url_blurb: "Kamu juga dapat mengirim URL Kelas unik ini ke halaman web bersama."
     add_students_manually: "Undang Siswa dengan Email"
     bulk_assign: "Pilih kursus"
@@ -1740,7 +1740,7 @@
     teacher_quest_keep_going: "Teruskan! Inilah yang dapat anda lakukan berikutnya:"
     teacher_quest_more: "Lihat semua pencarian"
     teacher_quest_less: "Lihat lebih sedikit pencarian"
-    refresh_to_update: "(refresh halaman untuk melihat pembaharuan)"
+    refresh_to_update: "(muat ulang halaman untuk melihat pembaharuan)"
     view_project_gallery: "Lihat Galeri Proyek"
     office_hours: "Webinars Guru"
     office_hours_detail: "Belajar bagaimana cara mengikuti perkembangan siswa anda selagi mereka membuat permainan dan memulai perjalanan koding mereka! Datang dan hadiri"
@@ -1761,58 +1761,58 @@
     combo_explanation: "Siswa melewati tantangan level Combo dengan menggunakan salah satu konsep yang terdaftar. Tinjau kode siswa dengan mengklik titik perkembangan."
     concept: "Konsep"

-#  share_licenses:
-#    share_licenses: "Share Licenses"
-#    shared_by: "Shared By:"
-#    add_teacher_label: "Enter exact teacher email:"
-#    add_teacher_button: "Add Teacher"
-#    subheader: "You can make your licenses available to other teachers in your organization. Each license can only be used for one student at a time."
-#    teacher_not_found: "Teacher not found. Please make sure this teacher has already created a Teacher Account."
-#    teacher_not_valid: "This is not a valid Teacher Account. Only teacher accounts can share licenses."
-#    already_shared: "You've already shared these licenses with that teacher."
-#    teachers_using_these: "Teachers who can access these licenses:"
-#    footer: "When teachers revoke licenses from students, the licenses will be returned to the shared pool for other teachers in this group to use."
-#    you: "(you)"
-#    one_license_used: "(1 license used)"
-#    licenses_used: "(__licensesUsed__ licenses used)"
-#    more_info: "More info"
-
-#  sharing:
-#    game: "Game"
-#    webpage: "Webpage"
-#    your_students_preview: "Your students will click here to see their finished projects! Unavailable in teacher preview."
-#    unavailable: "Link sharing not available in teacher preview."
-#    share_game: "Share This Game"
-#    share_web: "Share This Webpage"
-#    victory_share_prefix: "Share this link to invite your friends & family to"
-#    victory_share_prefix_short: "Invite people to"
-#    victory_share_game: "play your game level"
-#    victory_share_web: "view your webpage"
-#    victory_share_suffix: "."
-#    victory_course_share_prefix: "This link will let your friends & family"
-#    victory_course_share_game: "play the game"
-#    victory_course_share_web: "view the webpage"
-#    victory_course_share_suffix: "you just created."
-#    copy_url: "Copy URL"
-#    share_with_teacher_email: "Send to your teacher"
+  share_licenses:
+    share_licenses: "Bagikan Lisensi"
+    shared_by: "Dibagikan Oleh:"
+    add_teacher_label: "Masukan email guru yang tepat:"
+    add_teacher_button: "Tambahkan Guru"
+    subheader: "Anda dapat membuat lisensi anda tersedia bagi guru lain di organisasi anda. Setiap lisensi akan hanya digunakan oleh satu siswa dalam satu waktu."
+    teacher_not_found: "Guru tidak ditemukan. Harap pastikan guru ini telah membuat Akun Guru."
+    teacher_not_valid: "Ini bukan Akun Guru yang valid. Hanya akun guru yang bisa membagikan lisensi."
+    already_shared: "Anda telah membagikan lisensi dengan guru tersebut."
+    teachers_using_these: "Guru yang dapat mengakses lisensi ini:"
+    footer: "Ketika guru mencabut lisensi dari siswa, lisensi akan dikembalikan ke kumpulan lisensi untuk digunakan oleh guru lainnya di dalam grup."
+    you: "(Kamu)"
+    one_license_used: "(1 lisensi digunakan)"
+    licenses_used: "(__licensesUsed__ lisensi digunakan)"
+    more_info: "Info lanjut"
+
+  sharing:
+    game: "Permainan"
+    webpage: "Halaman Web"
+    your_students_preview: "Siswa anda akan mengeklik di sini untuk melihat proyek yang mereka selesaikan. Tidak tersedia dalam pratinjau guru."
+    unavailable: "Berbagi tautan tidak tersedia dalam pratinjau guru."
+    share_game: "Bagikan Permainan Ini"
+    share_web: "Bagikan Halaman Web Ini"
+    victory_share_prefix: "Bagikan tautan ini untuk mengundang teman dan keluarga kamu untuk"
+    victory_share_prefix_short: "Undang orang untuk"
+    victory_share_game: "bermain level permainanmu"
+    victory_share_web: "melihat halaman web kamu"
+    victory_share_suffix: "."
+    victory_course_share_prefix: "Tautan ini memungkinkan teman & keluarga kamu"
+    victory_course_share_game: "bermain permainan"
+    victory_course_share_web: "melihat halaman web"
+    victory_course_share_suffix: "yang kamu buat."
+    copy_url: "Salin URL"
+    share_with_teacher_email: "Kirimkan ke gurumu"

   game_dev:
     creator: "Pembuat"

-#  web_dev:
-#    image_gallery_title: "Image Gallery"
-#    select_an_image: "Select an image you want to use"
-#    scroll_down_for_more_images: "(Scroll down for more images)"
-#    copy_the_url: "Copy the URL below"
-#    copy_the_url_description: "Useful if you want to replace an existing image."
-#    copy_the_img_tag: "Copy the  tag"
-#    copy_the_img_tag_description: "Useful if you want to insert a new image."
-#    copy_url: "Copy URL"
-#    copy_img: "Copy "
-#    how_to_copy_paste: "How to Copy/Paste"
-#    copy: "Copy"
-#    paste: "Paste"
-#    back_to_editing: "Back to Editing"
+  web_dev:
+    image_gallery_title: "Galeri Gambar"
+    select_an_image: "Pilih gambar yang mau kamu gunakan"
+    scroll_down_for_more_images: "(Gulirkan kebawah untuk melihat image lebih banyak)"
+    copy_the_url: "Salin URL di bawah"
+    copy_the_url_description: "Berguna jika kamu ingin mengganti gambar saat ini."
+    copy_the_img_tag: "Salin tag "
+    copy_the_img_tag_description: "Berguna jika anda ingin memasukkan gambar yang baru."
+    copy_url: "Salin URL"
+    copy_img: "Salin "
+    how_to_copy_paste: "Bagaimana cara Salin/Tempel"
+    copy: "Salin"
+    paste: "Tempel"
+    back_to_editing: "Kembali ke Sunting"

 #  classes:
 #    archmage_title: "Archmage"
@@ -2061,33 +2061,33 @@
 #    humans: "Red" # Ladder page display team name
 #    ogres: "Blue"

-#  user:
-#    stats: "Stats"
-#    singleplayer_title: "Singleplayer Levels"
-#    multiplayer_title: "Multiplayer Levels"
-#    achievements_title: "Achievements"
-#    last_played: "Last Played"
-#    status: "Status"
-#    status_completed: "Completed"
-#    status_unfinished: "Unfinished"
-#    no_singleplayer: "No Singleplayer games played yet."
-#    no_multiplayer: "No Multiplayer games played yet."
-#    no_achievements: "No Achievements earned yet."
-#    favorite_prefix: "Favorite language is "
-#    favorite_postfix: "."
-#    not_member_of_clans: "Not a member of any clans yet."
-
-#  achievements:
-#    last_earned: "Last Earned"
-#    amount_achieved: "Amount"
-#    achievement: "Achievement"
-#    current_xp_prefix: ""
-#    current_xp_postfix: " in total"
-#    new_xp_prefix: ""
-#    new_xp_postfix: " earned"
-#    left_xp_prefix: ""
-#    left_xp_infix: " until level "
-#    left_xp_postfix: ""
+  user:
+    stats: "Statistik"
+    singleplayer_title: "Level Pemain Tunggal"
+    multiplayer_title: "Level Multi Pemain"
+    achievements_title: "Prestasi"
+    last_played: "Terakhir Dimainkan"
+    status: "Status"
+    status_completed: "Selesai"
+    status_unfinished: "Belum Selesai"
+    no_singleplayer: "Belum ada permainan Pemain Tunggal yang dimainkan."
+    no_multiplayer: "Belum ada permainan Multi Pemain yang dimainkan"
+    no_achievements: "Belum ada Prestasi yang dicapai."
+    favorite_prefix: "Bahasa favorit adalah "
+    favorite_postfix: "."
+    not_member_of_clans: "Belum menjadi anggota klan manapun."
+
+  achievements:
+    last_earned: "Terakhir diperoleh"
+    amount_achieved: "Jumlah"
+    achievement: "Prestasi"
+    current_xp_prefix: ""
+    current_xp_postfix: " secara keseluruhan"
+    new_xp_prefix: ""
+    new_xp_postfix: " diperoleh"
+    left_xp_prefix: ""
+    left_xp_infix: " sampai tingkat "
+    left_xp_postfix: ""

 #  account:
 #    payments: "Payments"
@@ -2152,36 +2152,36 @@
 #    you_can2: "purchase a prepaid code"
 #    you_can3: "that can be applied to your own account or given to others."

-#  loading_error:
-#    could_not_load: "Error loading from server"
-#    connection_failure: "Connection Failed"
-#    connection_failure_desc: "It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page."
-#    login_required: "Login Required"
-#    login_required_desc: "You need to be logged in to access this page."
-#    unauthorized: "You need to be signed in. Do you have cookies disabled?"
-#    forbidden: "Forbidden"
-#    forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!"
-#    not_found: "Not Found"
-#    not_found_desc: "Hm, there’s nothing here. Visit one of the following links to get back to programming!"
-#    not_allowed: "Method not allowed."
-#    timeout: "Server Timeout"
-#    conflict: "Resource conflict."
-#    bad_input: "Bad input."
-#    server_error: "Server error."
-#    unknown: "Unknown Error"
-#    error: "ERROR"
-#    general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!"
-
-#  resources:
-#    level: "Level"
-#    patch: "Patch"
-#    patches: "Patches"
-#    system: "System"
-#    systems: "Systems"
-#    component: "Component"
-#    components: "Components"
-#    hero: "Hero"
-#    campaigns: "Campaigns"
+  loading_error:
+    could_not_load: "Kesalahan memuat dari server"
+    connection_failure: "Koneksi Gagal"
+    connection_failure_desc: "Sepertinya kamu tidak terhubung dengan internet! Periksa jaringan kamu dan muat ulang halaman ini."
+    login_required: "Wajib Masuk"
+    login_required_desc: "Anda harus masuk untuk mengakses halaman ini."
+    unauthorized: "Anda harus masuk. Apakah anda menonaktifkan cookies?"
+    forbidden: "Terlarang"
+    forbidden_desc: "Oh tidak, tidak ada yang kami bisa tunjukkan kepadamu di sini! Pastikan kamu masuk menggunakan akun yang benar, atau kunjungi salah satu tautan dibawah untuk kembali ke pemrograman!"
+    not_found: "Tidak Ditemukan"
+    not_found_desc: "Hm, tidak ada apa-apa di sini. Kunjungi salah satu tautan berikut untuk kembali ke pemrograman!"
+    not_allowed: "Metode tidak diijinkan."
+    timeout: "Waktu Server Habis"
+    conflict: "Konflik sumber daya."
+    bad_input: "Masukan salah."
+    server_error: "Kesalahan server."
+    unknown: "Kesalahan tidak diketahui"
+    error: "KESALAHAN"
+    general_desc: "Ada yang salah, dan mungkin itu salah kami. Coba tunggu sebentar lalu muat ulang halaman, atau kunjungi salah satu tautan dibawah untuk kembali ke pemrograman!"
+
+  resources:
+    level: "Level"
+    patch: "Perbaikan"
+    patches: "Perbaikan"
+    system: "Sistem"
+    systems: "Sistem"
+    component: "Komponen"
+    components: "Komponen"
+    hero: "Jagoan"
+    campaigns: "Kampanye"

 #  concepts:
 #    advanced_css_rules: "Advanced CSS Rules"
@@ -2405,22 +2405,22 @@
 #    license: "license"
 #    oreilly: "ebook of your choice"

-#  calendar:
-#    year: "Year"
-#    day: "Day"
-#    month: "Month"
-#    january: "January"
-#    february: "February"
-#    march: "March"
-#    april: "April"
-#    may: "May"
-#    june: "June"
-#    july: "July"
-#    august: "August"
-#    september: "September"
-#    october: "October"
-#    november: "November"
-#    december: "December"
+  calendar:
+    year: "Tahun"
+    day: "Hari"
+    month: "Bulan"
+    january: "Januari"
+    february: "Februari"
+    march: "Maret"
+    april: "April"
+    may: "Mei"
+    june: "Juni"
+    july: "Juli"
+    august: "Agustus"
+    september: "September"
+    october: "Oktober"
+    november: "November"
+    december: "Desember"

 #  code_play_create_account_modal:
 #    title: "You did it!" # This section is only needed in US, UK, Mexico, India, and Germany
@@ -2429,9 +2429,9 @@
 #    victory_sign_up_poke: "Create a free account to save your code & be entered for a chance to win prizes!"
 #    victory_sign_up: "Sign up & be entered to win $2,500"

-#  server_error:
-#    email_taken: "Email already taken"
-#    username_taken: "Username already taken"
+  server_error:
+    email_taken: "Email telah diambil"
+    username_taken: "Username telah diambil"

 #  esper:
 #    line_no: "Line $1: "


Actually commit my ru.coffee merge

Actually commit my ru.coffee merge

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -631,13 +631,8 @@
     who_for_1: "Мы рекомендуем CodeCombat для учеников старше 9 лет. Какой-либо опыт программирования не требуется."
     who_for_2: "Мы разработали CodeCombat так, чтобы он подходил и мальчикам и девочкам."
     material_title: "Как много здесь материала?"
-<<<<<<< HEAD
     material_premium_server: "Около 30 часов игрового процесса, распределенного более чем на 140 уровней для подписчиков с добавлением новых уровней каждую неделю." # {change}
     material_1: "Около 10 часов бесплатного контента и 20 часов дополнительного контента для подписчиков с добавлением новых уровней каждую неделю." # {change}
-=======
-    material_china: "Около 30 часов игрового процесса, распределенного более чем на 140 уровней для подписчиков с добавлением новых уровней каждую неделю." # {change}
-    material_1: "Приблизно 25 часов бесплатного контента и дополнительные 15 часов контента для подписчиков." # {change}
->>>>>>> Fokusnica-master
     concepts_title: "О каких концептах мы рассказываем?"
     how_much_title: "Сколько стоит месячная подписка?"
     how_much_1: "Цена"


add ru translation

add ru translation

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -340,7 +340,7 @@
     tip_recurse: "Итерация свойственна человеку, рекурсия божественна. - L. Peter Deutsch"
     tip_free_your_mind: "Отвлекись от всего, Нео. Страх, неверие, сомнения отбрось — очисти свой разум. - Morpheus"
     tip_strong_opponents: "Даже сильнейший противник имеет слабость. - Itachi Uchiha"
-#    tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen."
+    tip_paper_and_pen: "Прежде чем приступить к решению, попробуй решить задачу с помощью бумаги и ручки."

   game_menu:
     inventory_tab: "Инвентарь"
@@ -437,24 +437,24 @@
     payment_methods_title: "Принимаемые методы оплаты"
     payment_methods_blurb1: "На данный момент мы принимаем кредитные карты и Alipay."
     payment_methods_blurb2: "Если вам необходим альтернативный способ оплаты, пожалуйста, свяжитесь"
-#    sale_already_subscribed: "You're already subscribed!"
-#    sale_blurb1: "Save 35%"
-#    sale_blurb2: "off regular subscription price of $120 for a whole year!" # {changed}
-#    sale_button: "Sale!"
-#    sale_button_title: "Save 35% when you purchase a 1 year subscription"
-#    sale_click_here: "Click Here"
-#    sale_ends: "Ends"
-#    sale_extended: "*Existing subscriptions will be extended by 1 year."
-#    sale_feature_here: "Here's what you'll get:"
-#    sale_feature2: "Access to 9 powerful new heroes with unique skills!"
-#    sale_feature4: "42,000 bonus gems awarded immediately!"
-#    sale_continue: "Ready to continue adventuring?"
-#    sale_limited_time: "Limited time offer!"
-#    sale_new_heroes: "New heroes!"
-#    sale_title: "Back to School Sale"
-#    sale_view_button: "Buy 1 year subscription for"
+    sale_already_subscribed: "У вас уже есть подписка!"
+    sale_blurb1: "Скидка 35%"
+    sale_blurb2: "от годовой подписки за $120 на весь год!" # {changed}
+    sale_button: "Распродажа!"
+    sale_button_title: "Сэкономьте 35% при приобретении годовой подписки"
+    sale_click_here: "Нажмите сюда"
+    sale_ends: "Заканчивается"
+    sale_extended: "*Текущие подписки будут продлены на год."
+    sale_feature_here: "Вот что вы получите:"
+    sale_feature2: "Доступ к 9 сильнымновым героямс уникальными умениями!"
+    sale_feature4: "42,000 бонусных самоцветов в подарок!"
+    sale_continue: "Готовы продолжить путешествие?"
+    sale_limited_time: "Ограниченное предложение!"
+    sale_new_heroes: "Новые герои!"
+    sale_title: "Назад на школьную распродажу"
+    sale_view_button: "Купи годовую подписку за"
     stripe_description: "Месячная подписка"
-#    stripe_description_year_sale: "1 Year Subscription (35% discount)"
+    stripe_description_year_sale: "Годовая подписка (35% скидка)"
     subscription_required_to_play: "Чтобы сыграть этот уровень нужна подписка."
     unlock_help_videos: "Подпишитесь, чтобы разблокировать все обучающие видео."
     personal_sub: "Личная подписка" # Accounts Subscription View below
@@ -1026,7 +1026,7 @@
     my_matches: "Мои матчи"
     simulate: "Симулирование"
     simulation_explanation: "Симулированием игр вы сможете быстрее получить оценку игры!"
-#    simulation_explanation_leagues: "You will mainly help simulate games for allied players in your clans and courses."
+    simulation_explanation_leagues: "Вы поможете симулировать игры для союзных игроков в вашем клане или курсе."
     simulate_games: "Симулировать игры!"
     simulate_all: "СБРОСИТЬ И СИМУЛИРОВАТЬ ИГРЫ"
     games_simulated_by: "Игры, симулированные вами:"
@@ -1118,7 +1118,7 @@
     no_recent_games: "Нет сыгранных игр за последние две недели."
     payments: "Платежи"
     purchased: "Куплено"
-#    sale: "Sale"
+    sale: "Распродажа"
     subscription: "Подписка"
     invoices: "Счета"
     service_apple: "Apple"
@@ -1219,17 +1219,17 @@
     arrays: "Массивы"
     basic_syntax: "Базовый синтаксис"
     boolean_logic: "Булевая логика"
-    break_statements: "Циклы"
+    break_statements: "Оператор \"break\""
     classes: "Классы"
-#    continue_statements: "Continue Statements"
+    continue_statements: "Оператор \"continue\""
     for_loops: "Циклы \"for\""
     functions: "Функции"
-#    graphics: "Graphics"
+    graphics: "Графика"
     if_statements: "Условные операторы"
     input_handling: "Обработка ввода"
     math_operations: "Математические операции"
     object_literals: "Литералы объектов"
-#    parameters: "Parameters"
+    parameters: "Параметры"
     strings: "Строки"
     variables: "Переменные"
     vectors: "Векторы"


Improve locale sk for landing page

Improve locale sk for landing page

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -10,7 +10,7 @@
     classroom_in_a_box: "Virtuálna trieda pre výuku programovania."
     codecombat_is: "CodeCombat je platforma  pre študentov, kde sa naučia programovať hrou. "
     our_courses: "Naše kurzy boli špeciálne testované v reálnych triedach a to dokonca aj učiteľmi, ktorí nemali predchádzajúce skúsenosti v programovaní."
-#    watch_how: "Watch how CodeCombat is transforming the way people learn computer science."
+    watch_how: "Pozri si ako CodeCombat mení spôsob učenia informatiky."
     top_screenshots_hint: "Zmeny v správaní programu vidia študenti v reálnom čase."
     designed_with: "Navrhnuté s ohľadom na potreby učiteľov"
     real_code: "Skutočný kód"
@@ -23,12 +23,12 @@
     everyone: "pre každého"
     democratizing: "Demokratizácia procesu výuky programovania je jadrom našej filozofie. Každému by malo byť umožnené naučiť sa programovať."
     forgot_learning: "Moji študenti zabudli, že sa učia programovať. Brali to ako hru."
-    wanted_to_do: "Vždy som sa chcel naučiť programovať a nikdy som nemyslel, že tu bude v škole."
+    wanted_to_do: "Vždy som sa chcel naučiť programovať a nikdy som nemyslel, že to bude v škole."
     why_games: "Prečo je učenie sa hrou také dôležité?"
     games_reward: "Hry odmeňujú tvorivé úsilie."
     encourage: "Hra podporuje interaktivitu, objavovanie a metódu pokusov a omylov. Dobrá hra núti hráča, aby sa časom jeho schopnosti zdokonaľovali, čo je ktitické aj v procese učenia."
     excel: "Hry vynikajú"
-    struggle: "v oceňovaní tvorivého úsilia,"
+    struggle: "v oceňovaní tvorivého úsilia"
     kind_of_struggle: "ktoré sa často spája s činnosťou,"
     motivating: "ktorá nie je"
     not_tedious: "nudná."
@@ -50,7 +50,7 @@
     have_an_account: "Máš účet?"
     logged_in_as: "Si prihlásený ako"
     computer_science: "Kurz programovania pre každý vek" # {change}
-    ffa: "Zdarma pre všetkých študentov"
+    ffa: "Zdarma pre študentov"
     coming_soon: "Čoskoro!"
     courses_available_in: "Kurzy sú dostupné v JavaScripte, Pythone a čoskoro aj v Jave." # {change}
     boast: "Komplexita úloh je dostatočná na fascináciu hráčov aj programátorov."
@@ -1322,7 +1322,7 @@
 #    view_project: "View Project"
 #    start: "Start"
 #    last_level: "Last level played"
-#    not_you: "Not you?"
+    not_you: "Nie si to ty?"
 #    continue_playing: "Continue Playing"
 #    option1_header: "Invite Students by Email"
 #    remove_student1: "Remove Student"


Fixed last merge

Fixed last merge

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -677,8 +677,8 @@
     other: "Інше:"
     amount_students: "Як багато студентів ви навчаєте?"
     hear_about: "Як ви дізналися про CodeCombat?"
-    fill_fields: "Будь ласка, заповніть всі поля.
-    thanks: "Дякую! Скоро ми надішлемо вам інструкцію по встановленню. "
+    fill_fields: "Будь ласка, заповніть всі поля."
+    thanks: "Дякую! Скоро ми надішлемо вам інструкцію по встановленню."

   versions:
     save_version_title: "Зберегти нову версію"
@@ -1073,7 +1073,7 @@
     tournament_rules: "Правила турніру"
     tournament_blurb: "Пиши код, збирай золото, будуй армії, розбивай ворогів, вигравай призи і покращуй свою кар'єру у нашому Greed Турнірі на 40 000 $! Дізнайся більше "
     tournament_blurb_criss_cross: "Вигравай ставки, створюй шляхи, перехитри опонентів, збирай самоцвіти і покращуй свою кар'єру у нашому Criss-Cross Турнірі! Дізнайся більше "
-    tournament_blurb_zero_sum: Надайте волю своєї програмістської фантазії у збиранні золота і бойової тактиці в цьому високогірному дзеркальному матчі між червоним чарівником і синім чарівником. Турнір розпочався в п'ятницю, 27 березня, і продовжиться до 17.00 PDT понеділка, 6 квітня. Беріть участь для веселощів і слави! Подивіться деталі"
+    tournament_blurb_zero_sum: "Надайте волю своєї програмістської фантазії у збиранні золота і бойової тактиці в цьому високогірному дзеркальному матчі між червоним чарівником і синім чарівником. Турнір розпочався в п'ятницю, 27 березня, і продовжиться до 17.00 PDT понеділка, 6 квітня. Беріть участь для веселощів і слави! Подивіться деталі"
     tournament_blurb_blog: "у нашому блозі"
     rules: "Правила"
     winners: "Переможці"
@@ -1422,7 +1422,7 @@
     education_duration: "Дати"
     education_duration_help: "Коли?"
     education_description: "Опис"
-    education_description_help: "Виділіть що-небудь з цього навчального досвіду. (140 символів; опціонально) "
+    education_description_help: "Виділіть що-небудь з цього навчального досвіду. (140 символів; опціонально)"
     our_notes: "Наші примітки"
     remarks: "Примітки"
     projects: "Роботи"


Refactor enterprises controller to reduce code duplication

--- app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee
+++ app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee
@@ -69,34 +69,22 @@
       $scope.newUser = $scope.invite_errors = $scope.invite_success = null

     $scope.removeLogo = ->
-      return unless confirm($scope.translation("immediate_logo_removal_warning"))
-
-      Enterprises.removeLogo($scope.Enterprise)
-        .then $scope.removeImageSuccessCallback("removed_logo_successfully"),
-              $scope.removeImageSuccessCallback()
+      $scope.performEnterpriseAction("removeLogo", "immediate_logo_removal_warning", "removed_logo_successfully")

     $scope.removePromoImage = ->
-      return unless confirm($scope.translation("immediate_promo_image_removal_warning"))
-
-      Enterprises.removePromoImage($scope.Enterprise)
-        .then $scope.removeImageSuccessCallback("removed_promo_image_successfully"),
-              $scope.removeImageSuccessCallback()
+      $scope.performEnterpriseAction("removePromoImage", "immediate_promo_image_removal_warning", "removed_promo_image_successfully")

     $scope.removeTermsAndConditions = ->
-      return unless confirm($scope.translation("immediate_terms_and_conditions_removal_warning"))
+      $scope.performEnterpriseAction("removeTermsAndConditions", "immediate_terms_and_conditions_removal_warning", "removed_terms_and_conditions_successfully")

-      Enterprises.removeTermsAndConditions($scope.Enterprise)
-        .then $scope.removeImageSuccessCallback("removed_terms_and_conditions_successfully"),
-              $scope.removeImageSuccessCallback()
+    $scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
+      return unless confirm($scope.translation(warning_message_key))

-    $scope.removeImageSuccessCallback = (success_message_key) ->
-      (data) ->
+      Enterprises[enterpriseActionName]($scope.Enterprise).then (data) ->
         $scope.Enterprise = angular.copy(data)
         $scope.$emit("enterprise:updated", $scope.Enterprise)
         StatusMessage.display("success", $scope.translation(success_message_key))
-
-    $scope.removeImageErrorCallback = ->
-      (response) ->
+      , (response) ->
         if response.data.error?
           StatusMessage.display("failure", response.data.error)


poprawka

poprawka

--- def/pl/settings.cson
+++ def/pl/settings.cson
@@ -237,13 +237,13 @@
       }
       {
         _id: 'editor.fontFamily'
-        title: "Font Family"
-        desc: "The name of the font family used for editor text."
+        title: "Czcionka"
+        desc: "Nazwa czcionki używana w edytorze tekstowym."
       }
       {
         _id: 'editor.fontSize'
-        title: "Font Size"
-        desc: "Height in pixels of editor text."
+        title: "Rozmiar czcionki"
+        desc: "Wysokość tekstu w edytorze wyrażona pikselach."
       }
       {
         _id: 'editor.invisibles.cr'


only fetch members of public groups initially and then groups you are member of (that info comes later), still these groups could have set list members permission off, so do not print Access denied errors. it's way cheaper then doing that check.

--- client/landing/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
@@ -62,20 +62,13 @@
       title   : 'Open group'
       click   : @bound 'privateGroupOpenHandler'

-    membersController = new KDListViewController
+    @membersController = new KDListViewController
       view         : @members = new KDListView
         wrapper    : no
         scrollView : no
         type       : "members"
         itemClass  : GroupItemMemberView
-
-    # FIXME: SY
-    # instantiateListItems doesnt fire by default
-    group.fetchMembers (err, members)=>
-      if err then warn err
-      else if members
-        @$('.members-list-wrapper').removeClass "hidden"
-        membersController.instantiateListItems members
+    @fetchMembers() if group.privacy is 'public'

     @memberBadge = new KDCustomHTMLView
       tagName   : "div"
@@ -118,7 +111,10 @@

   setFollowerCount:(count)-> @$('.followers a').html count

-  markMemberGroup:-> @setClass "member-group"
+  markMemberGroup:->
+    @setClass "member-group"
+    @fetchMembers() if @getData().privacy isnt 'public'
+
   markOwnGroup:-> @setClass "own-group"

   pistachio:->
@@ -202,6 +198,15 @@
         duration : 2000
       callback()

+  fetchMembers:->
+    @getData().fetchMembers (err, members)=>
+      if err
+        # HK: better we have error codes for such things
+        warn err unless err.name is 'KodingError' and err.message is 'Access denied'
+      else if members
+        @$('.members-list-wrapper').removeClass "hidden"
+        @membersController.instantiateListItems members
+
 class GroupItemMemberView extends KDListItemView

   constructor:(options = {}, data)->


disable loading indicator when opened modal

--- app/js/jail_iframe/classes/fact.coffee
+++ app/js/jail_iframe/classes/fact.coffee
@@ -28,6 +28,7 @@
     FactlinkJailRoot.openFactlinkModal @id

   _onModalOpened: ->
+    @show_button.stopLoading()
     FactlinkJailRoot.off 'modalOpened', @_onModalOpened, @

   destroy: ->


Changes to fix issues pointed by @bhuga.

Now you can press % inside (abc) and it will do the same as in vim.

--- lib/motions/search-motion.coffee
+++ lib/motions/search-motion.coffee
@@ -143,6 +143,9 @@
     super(@editorView, @vimState)
     Search.currentSearch = @
     @reverse = @initiallyReversed = false
+    @characters         = [')','(','}','{',']','[']
+    @charactersMatching = ['(',')','{','}','[',']']
+    @reverseSearch      = [true,false,true,false,true,false]

     # FIXME: This must depend on the current language
     @input = new Input(@getCurrentWordMatch())
@@ -151,26 +154,13 @@
     cursor = @editor.getCursor()
     tempPoint = cursor.getBufferPosition().toArray()
     @character = @editor.getTextInBufferRange([cursor.getBufferPosition(),new Point(tempPoint[0],tempPoint[1] + 1)])
-    if @character == ']'
-      @matching = '['
-      @reverse = true
-    else if @character == '['
-      @matching = ']'
-      @reverse = false
-    else if @character == ')'
-      @matching = '('
-      @reverse = true
-    else if @character == '('
-      @matching = ')'
-      @reverse = false
-    else if @character == '}'
-      @matching = '{'
-      @reverse = true
-    else if @character == '{'
-      @matching = '}'
-      @reverse = false
-    else
-      @character = ''
+    @startUp = false;
+    index = @characters.indexOf(@character)
+    if index >= 0
+      @matching = @charactersMatching[index]
+      @reverse = @reverseSearch[index]
+    else
+      @startUp = true

     @character

@@ -212,7 +202,11 @@

   select: (count=1) ->
     @scan()
-    cur = @editor.getCursorBufferPosition()
+    if @startUp
+      cur = @startUpPos
+    else
+      cur = @editor.getCursorBufferPosition()
+
     @match count, (pos) =>
       if @reverse
         tempPoint = cur.toArray()
@@ -223,6 +217,25 @@
     [true]

   scan: ->
+    if @startUp
+      @startUpPos = @editor.getCursorBufferPosition()
+      min = -1
+      iwin = -1
+      for i in [0..@characters.length - 1]
+        matchesCharacter = @searchFor(@characters[i])
+        if matchesCharacter.length > 0
+          dst = matchesCharacter[0].range.start.toArray()
+          if @startUpPos.toArray()[0] == dst[0] and @startUpPos.toArray()[1] < dst[1]
+            if dst[1] < min or min == -1
+              line = dst[0]
+              min = dst[1]
+              iwin = i
+      if iwin != -1
+        @editor.setCursorBufferPosition(new Point(line,min))
+        @character = @characters[iwin]
+        @matching = @charactersMatching[iwin]
+        @reverse = @reverseSearch[iwin]
+
     matchesCharacter = @searchFor(@character)
     matchesMatching = @searchFor(@matching)
     if matchesMatching.length == 0
@@ -257,6 +270,10 @@
         retVal.push(matchesMatching[winner])
       @matches = retVal

+    if @matches.length == 0 and @startUp
+      @editor.setCursorBufferPosition(@startUpPos)
+
+

   execute: (count=1) ->
     super(count) if @input.characters.length > 0


Show random ad

Show random ad

--- app/assets/javascripts/ads/jquery.ads.js.coffee
+++ app/assets/javascripts/ads/jquery.ads.js.coffee
@@ -29,8 +29,9 @@
           banner_div = $(".#{type}")
           width  = banner_div.data 'width'
           height = banner_div.data 'height'
-          banner_div.html ""
-          log "Type: #{type} info: #{info[0].link}"
+          random_ad= info[Math.floor(Math.random()*info.length)]
+          banner_div.html ""
+          log "Type: #{type} info: #{random_ad.link}"

       #   get ads limit by number of banners and respect the banner name.
       #   add image into the banner.


A handy page that allows a quick peek at metadata.

--- test/fixtures/foreground/dumpMeta.coffee
+++ test/fixtures/foreground/dumpMeta.coffee
@@ -0,0 +1,3 @@
+module.exports= ()->
+  @res.setHeader 'Content-Type','text/plain'
+  @res.end(JSON.stringify(@meta))


paneItem -> item for consistency

paneItem -> item for consistency

--- src/pane.coffee
+++ src/pane.coffee
@@ -676,8 +676,8 @@
       return false unless @promptToSaveItem(item)
     true

-  handleSaveError: (error, paneItem) ->
-    itemPath = error.path ? paneItem.getPath?()
+  handleSaveError: (error, item) ->
+    itemPath = error.path ? item.getPath?()
     addWarningWithPath = (message, options) ->
       message = "#{message} '#{itemPath}'" if itemPath
       atom.notifications.addWarning(message, options)


Add custom notification for ENOSPC save errors

Closes #5825

--- src/pane.coffee
+++ src/pane.coffee
@@ -683,6 +683,8 @@
       atom.notifications.addWarning("Unable to save file '#{error.path}'", detail: error.message)
     else if error.code is 'EROFS' and error.path?
       atom.notifications.addWarning("Unable to save file: Read-only file system '#{error.path}'")
+    else if error.code is 'ENOSPC' and error.path?
+      atom.notifications.addWarning("Unable to save file: No space left on device '#{error.path}'")
     else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
       fileName = errorMatch[1]
       atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")


begin to deal with PUT

begin to deal with PUT

--- node.js/server.coffee
+++ node.js/server.coffee
@@ -26,9 +26,13 @@
 }

 process.serve_url = (req, res) ->
-    console.log req.method, req.url
-
+#work out what resource the request is about
     file = req.url[1..]
+
+    if req.method == 'PUT'
+       #nasty hack, as we're only putting pages atm, and the URI's are :(
+       file = file.replace(/^page\//, '')    #TODO: mmm, substring anyone?
+       file = file.replace(/\/action$/, '')

     if file == ''
       console.log 'getting / == welcome-visitors.html topic'
@@ -39,15 +43,15 @@
     file = file.replace(/\?.*$/i, '')     #remove ?random= - or other urlparams

     file_extension = file.match(/\.([^\.]*)$/i)
-    if !file_extension[0]
+    if file_extension && file_extension[0]
+      file_extension = file_extension[1].toLowerCase()
+    else
       file_extension = 'json'
-    else
-      file_extension = file_extension[1].toLowerCase()

     if ! filetype[file_extension]
       file_extension = 'index'
       file = 'index.html'
-      #TODO: do something to load the right page (and here we start with html templating
+      #TODO: modify the index.html page div id

     encoding = filetype[file_extension]['encoding']
     contentType =  filetype[file_extension]['content_type']
@@ -56,8 +60,10 @@
     #and here's a pointless exception to the filename.extension :(
     filePath = filePath.replace(/\.json/, '') if file_extension == 'json'

-    console.log req.method+filePath
+    console.log req.method+' '+req.url+': '+filePath

+#fulfil the request
+    #read
     fs.readFile filePath, encoding, (err, data) ->
       status = 200
       if err


Add start method

Add start method

--- source/assets/javascripts/jquery-law.js.coffee
+++ source/assets/javascripts/jquery-law.js.coffee
@@ -1,14 +1,17 @@
 class AlertLaw
+
+  @start: (options={}) ->
+    alert = new AlertLaw(options)
+    alert.alertOrNot()
+
   constructor: (options={}) ->
-    @cookieUrl      = options.cookieUrl ? '/cookies'
-    @messageTitle   = options.messageTitle ? 'Cookies'
+    @cookieUrl      = options.cookieUrl      ? '/cookies'
+    @messageTitle   = options.messageTitle   ? 'Cookies'
     @messageContent = options.messageContent ? 'En poursuivant votre navigation sur notre site, vous en acceptez l‘utilisation pour vous proposer un service personnalisé, des publicités ciblées adaptées à vos centres d’intérêts et réaliser des statistiques de visites.'
-
-  cookieName: ->
-    'cookies_law'
+    @cookieName     = options.cookieName     ? 'cookies_law'

   getCookie: ->
-    search      = @cookieName() + "="
+    search      = @cookieName + "="
     returnvalue = ""

     if document.cookie.length
@@ -27,12 +30,11 @@
   expirationDate: ->
     month_number    = 13
     current_date    = new Date()
-    current_date.setMonth(current_date.getMonth() + month_number)
-    expirationDate = current_date.toUTCString()
+    expiration_date = current_date.setMonth(current_date.getMonth() + month_number)
+    expiration_date.toUTCString()

   alertOrNot: ->
-    if @getCookie() == ''
-      @loadAlert()
+    @loadAlert() if @getCookie() == ''

   buildAlert: ->
     "<div id='js-law--alert' style='display: none;'>
@@ -47,13 +49,14 @@
     $('body').prepend(message)
     $('#js-law--alert').slideDown(300)

-    $('#js-law--close').on 'click', (event) ->
+    $('#js-law--close').on 'click', (event) =>
       event.preventDefault()
       $("#js-law--alert").slideUp(300)

-    $('a').on 'click', (event) ->
-      document.cookie = "#{cookieName()}=yes;expires='#{expirationDate()}';path=/"
+    $('a').on 'click', (event) =>
+      document.cookie = "#{@cookieName}=yes;expires='#{@expirationDate()}';path=/"

 $ ->
-  alert = new AlertLaw { cookieUrl: 'foo' }
-  alert.alertOrNot()
+  AlertLaw.start()
+  # alert = new AlertLaw { cookieUrl: 'foo' }
+  # alert.alertOrNot()


Add "Why doesn't this use official Atom Shell" comment

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -165,6 +165,8 @@
         'static/**/*.less'
       ]

+    # NB: This can be switched to official Atom Shell once atom/atom-shell#898
+    # gets merged
     'build-atom-shell':
       tag: "dont-hardcode-atom-helper"
       remoteUrl: "https://github.com/paulcbetts/atom-shell"


change view on every input-action on source_address

--- app/assets/javascripts/repository/edit_remote.js.coffee
+++ app/assets/javascripts/repository/edit_remote.js.coffee
@@ -51,6 +51,6 @@
     change_options()
     change_type_display()

-    source_address_el.on('change', change_options)
-    source_address_el.on('change', change_type_display)
+    source_address_el.on('input', change_options)
+    source_address_el.on('input', change_type_display)
   return


Update Address.shortAddress() to use name, street only

--- app/assets/javascripts/sprangular/models/address.coffee
+++ app/assets/javascripts/sprangular/models/address.coffee
@@ -19,7 +19,7 @@
     "#{@firstname} #{@lastname}"

   shortAddress: ->
-    "#{@fullName()}, #{@addressLine()}, #{@city} #{@actualStateName()}, #{@zipcode}"
+    "#{@fullName()}, #{@addressLine()}"

   actualStateName: ->
     @state?.abbr || @state_name


Change query_last_part for safety reason

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -409,6 +409,7 @@
             query = query_before_cursor.slice slice_index

             @query_first_part = query_before_cursor.slice 0, slice_index
+            ###
             #TODO handle string
             next_dot_position = query_after_cursor.indexOf('.')
             next_parenthesis_position = query_after_cursor.indexOf(')')
@@ -427,7 +428,14 @@
                 while query_after_cursor[slice_position]? and /\s/.test(query_after_cursor[slice_position])
                     @query_last_part = query_after_cursor[slice_position] + @query_last_part
                     slice_position--
-
+            ###
+            ###
+            slice_position = 0
+            while query_after_cursor[slice_position]? and /\s/.test(query_after_cursor[slice_position])
+                slice_position++
+            ###
+            @query_last_part = query_after_cursor #.slice slice_position
+
             last_function = @extract_last_function(query)
             if @map_state[last_function]? and @suggestions[@map_state[last_function]]?
                 suggestions = []
@@ -450,7 +458,7 @@
                     @append_suggestion(query, suggestions)

             return false
-
+
         extract_current_function: (query) =>
             #TODO Handle string
             start = query.lastIndexOf '.'


Consider the number of datum instead of size of the JSON representation

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3163,12 +3163,40 @@
                             key: key

+        # Return whether there are too many datums
+        # If there are too many, we will disable syntax highlighting to avoid freezing the page
+        has_too_many_datums: (result) ->
+            if @has_too_many_datums_helper(result) > @max_datum_threshold
+                return true
+            return false
+
+
+        # Return the number of datums if there are less than @max_datum_threshold
+        # Or return a number greater than @max_datum_threshold
+        has_too_many_datums_helper: (result) ->
+            if Object::toString.call(result) is '[object Object]'
+                count = 0
+                for key of result
+                    count += @has_too_many_datums_helper result[key]
+                    if count > @max_datum_threshold
+                        return count
+                return count
+            else if Array.isArray(result)
+                count = 0
+                for element in result
+                    count += @has_too_many_datums_helper element
+                    if count > @max_datum_threshold
+                        return count
+                return count
+
+            return 1
+
+
         json_to_tree: (result) =>
-            result_json = JSON.stringify(result, null, 4)
             # If the results are too large, we just display the raw indented JSON to avoid freezing the interface
-            if result_json.length > @large_response_threshold
+            if @has_too_many_datums(result)
                 return @template_json_tree.large_container
-                    json_data: result_json
+                    json_data: JSON.stringify(result, null, 4)
             else
                 return @template_json_tree.container
                     tree: @json_to_node(result)
@@ -3405,7 +3433,7 @@
                 'click .activate_profiler': 'activate_profiler'

         current_result: []
-        large_response_threshold: 100000 # ~ 2000 uuids
+        max_datum_threshold: 1000

         initialize: (args) =>
             @container = args.container


"Do not list expiring plunks"

"Do not list expiring plunks"

--- lib/plunker/stores/memory.coffee
+++ lib/plunker/stores/memory.coffee
@@ -94,7 +94,7 @@
     clearTimeout(@timeouts[model.id]) if model.id and model.get("expires")
     @

-  list: (start, end, cb) -> cb null, @plunks.toJSON().slice(start, end)
+  list: (start, end, cb) -> cb null, _.reject(@plunks.toJSON(), (plunk) -> !!plunk.expires).slice(start, end)
   reserveId: (cb) -> cb null, uid(6) # OH GOD THE CHILDREN
   create: (json, cb) -> cb null, @plunks.add(json).get(json.id).toJSON()
   fetch: (id, cb) ->


Configure Jade

Configure Jade

--- app._coffee
+++ app._coffee
@@ -2,6 +2,7 @@
 deepzoom = require 'deepzoomtools'
 express = require 'express'
 fs = require 'fs'
+jade = require 'jade'
 path = require 'path'
 request = require 'request'

@@ -15,6 +16,9 @@
 express.static.mime.types.dzi = 'application/xml'
 app = require('streamline-express') express()

+# Template engine
+app.engine 'jade', jade.__express
+app.set 'view engine', 'jade'

 ## MIDDLEWARE:
 # (see http://www.senchalabs.org/connect/ for reference)


fix helper

fix helper

--- app/assets/javascripts/select2_helper.js.coffee
+++ app/assets/javascripts/select2_helper.js.coffee
@@ -67,6 +67,7 @@
     multiple: true
     width: '100%'
     initSelection: (element, callback) ->
+      $(element).val('')
       callback $.map(element.val().split(','), (value) -> id: value, text: value)
     createSearchChoice: (term) ->
       id: term, text: term


Add api to require root for use in test fixtures

--- test/config/karma.common.coffee
+++ test/config/karma.common.coffee
@@ -19,7 +19,7 @@
   webpack:
     devtool: 'eval-source-map'
     resolve:
-      root: [ path.resolve(__dirname, '../../src') ]
+      root: [ path.resolve(__dirname, '../../src'), path.resolve(__dirname, '../../api') ]
       extensions: ['', '.js', '.json', '.coffee', '.cjsx']
     module:
       noParse: [