Implement schema clean up.

Implement schema clean up.

--- src/postprocessor.coffee
+++ src/postprocessor.coffee
@@ -56,7 +56,24 @@
         @cleanRefs()
         schema = @processRules(data, __path: '#')
         @resolveTypeReferences(schema)
-        #todo Remove paths from schema
+        @cleanUpSchema(schema)
+        schema
+
+    #
+    # Removes technical information from schema
+    #
+    # @param [Object] schema
+    # @return [Object] schema
+    #
+    cleanUpSchema: (schema) ->
+        # Remove context path from schema
+        delete schema.__path
+
+        # Remove context paths from type definitions
+        for key, def of @typeDefs
+            delete def.__path
+
+        # Return schema
         schema

     #


Use theoretically correct touch coordinates

--- coffee/core/events.coffee
+++ coffee/core/events.coffee
@@ -1,7 +1,8 @@
 coordsForTouchEvent = ($el, e) ->
-  t = e.originalEvent.changedTouches[0]
+  tx = e.originalEvent.changedTouches[0].pageX
+  ty = e.originalEvent.changedTouches[0].pageY
   p = $el.offset()
-  return [t.clientX - p.left, t.clientY - p.top]
+  return [tx - p.left, ty - p.top]

 position = (e) ->


Converted fully to Ace

Converted fully to Ace

--- src/js/app/editor.coffee
+++ src/js/app/editor.coffee
@@ -313,9 +313,7 @@

 saveCurrent = ->
   source = sourceArea.getValue()
-  value =
-    source: source
-    mode: currentMode
+  value = serializeSource()

   valueLines = (source.split "\n").length
   exists = false
@@ -323,6 +321,24 @@
   ammendClientTable saveName, "#{saveName},#{valueLines}"
   fileCookie saveName, value
   $.totalStorage LAST_CODE, saveName
+
+serializeSource = ->
+  source: sourceArea.getValue()
+  mode: currentMode
+  selection: sourceArea.getSelectionRange()
+  cursor: sourceArea.getCursorPosition()
+  scroll:
+    top: sourceArea.session.getScrollTop()
+    left: sourceArea.session.getScrollLeft()
+
+deseriazeSource = (serialized, callback) ->
+  {source, mode, selection, cursor, scroll} = serialized
+  sourceArea.setValue source
+  sourceArea.session.selection.setSelectionRange selection
+  sourceArea.moveCursorToPosition cursor
+  sourceArea.session.setScrollTop scroll.top
+  sourceArea.session.setScrollLeft scroll.left
+  setNewMode mode, callback

 saveTimeline = ->
   $.totalStorage TIMELINE_COOKIE, timeline.newest(200)
@@ -358,11 +374,8 @@
   stored = fileCookie(name)
   if stored?
     saveName = name
-    source = stored.source
-    mode = stored.mode
-
-    sourceArea.setValue source
-    setNewMode mode, ->
+
+    deseriazeSource stored, ->
       showFileMessage "" + saveName + " loaded"  if saveName isnt UNNAMED_CODE

   else
@@ -469,29 +482,6 @@
   save Long file name.txt\\
 """

-# sourceArea = CodeMirror.fromTextArea $("#sourceArea")[0],
-#   tabSize: 2
-#   indentUnit: 2
-#   lineNumbers: true
-#   extraKeys:
-#     Tab: "indentMore"
-#     "Shift-Tab": "indentLess"
-
-#   onChange: (inst, e) ->
-#     sourceChange inst, e
-
-#   onCursorActivity: ->
-#     sourceArea.setLineClass hlLine, null, null
-#     hlLine = sourceArea.setLineClass sourceArea.getCursor().line, null, "activeline"
-
-# hlLine = sourceArea.setLineClass 1, null, "activeline"
-# commandArea = CodeMirror.fromTextArea $("#commandArea")[0],
-#   tabSize: 2
-#   indentUnit: 2
-#   keyMap: "commandLine"
-#   onKeyEvent: (inst, e) ->
-#     commandAreaKeyEvent inst, e
-
 sourceArea = ace.edit 'sourceArea'
 sourceArea.setTheme "ace/theme/monokai"
 sourceArea.getSession().setMode "ace/mode/coffee"


disable safe mode by default

disable safe mode by default

--- src/lib/broadcast.coffee
+++ src/lib/broadcast.coffee
@@ -115,7 +115,12 @@
             TSmax = Math.min(timestampMax, TSmin + @options.splitTimespanMS)
             continue if TSmax is TSmin

-            for i in [1..3]
+            if argv.safe
+                REPEAT_TIMES = 3
+            else
+                REPEAT_TIMES = 1
+
+            for i in [1 .. REPEAT_TIMES]
                 preparedTasks.push
                     data:
                         minLatE6:        Math.round(@options.region.SouthWest.Lat * 1e6)


Fix problem with single division map rendering.

--- src/map-base-view.coffee
+++ src/map-base-view.coffee
@@ -175,10 +175,7 @@
         _combineMultiPolygons: (multiPolygons) ->
             multiPolygons.map (mp) => mp.coordinates[0]

-        drawDivisions: (divisions) ->
-            geojson =
-                coordinates: @_combineMultiPolygons divisions.pluck('boundary')
-                type: 'MultiPolygon'
+        drawDivisionGeometry: (geojson) ->
             mp = L.GeoJSON.geometryToLayer geojson,
                 null, null,
                 invert: true
@@ -189,6 +186,17 @@
                 fillOpacity: 0.2
             @map.adapt()
             mp.addTo @divisionLayer
+
+        drawDivisions: (divisions) ->
+            geojson =
+                coordinates: @_combineMultiPolygons divisions.pluck('boundary')
+                type: 'MultiPolygon'
+            @drawDivisionGeometry geojson
+
+        drawDivision: (division) ->
+            unless division?
+                return
+            @drawDivisionGeometry division.get('boundary')

         handlePosition: (positionObject) ->
             accuracy = location.accuracy


fix fill color for rectangular trace

fix fill color for rectangular trace

--- src/standard-tool.coffee
+++ src/standard-tool.coffee
@@ -44,7 +44,10 @@
     if p.height <= 0
       throw new RangeError "#{tool} rect height out of range (#{p.height}<=0)"
     shape = 'rect'
-    unless p.hole? or p.obround then result.trace = { 'stroke-width': 0 }
+    unless p.hole? or p.obround then result.trace = {
+      'stroke-width': 0
+      fill: 'currentColor'
+    }

   else if p.dia? and p.verticies?
     # we've got a polygon tool unless there's confusion


Set .rspec file type to "text.generic-config" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -54,6 +54,7 @@
     "^.inputrc": "text.generic-config"
     "^.profile$": "source.shell"
     "^.psqlrc": "source.sql"
+    "^.rspec$": "text.generic-config"
     "^.shared_env.*": "source.shell"
     "^.spacemacs$": "source.lisp"
     ".*.sshconfig": "source.ssh-config"


Move folder config to the top

Move folder config to the top

--- Learn-server/index.coffee
+++ Learn-server/index.coffee
@@ -1,3 +1,8 @@
+
+###
+# IMAGES FOLDER PATH
+###
+global.image_path = "/Users/balazspete/Documents/"

 ###
 # DECLARATIONS & SETUP
@@ -9,7 +14,6 @@
 global.db = db = mongoose.createConnection 'mongodb://localhost:27017/learn', {
   server: { auto_reconnect: true }
 }
-global.image_path = "/Users/balazspete/Documents/"

 imageHandler = require './handleImage'


Tame attachment toolbar creation

Tame attachment toolbar creation

--- src/trix/controllers/attachment_editor_controller.coffee
+++ src/trix/controllers/attachment_editor_controller.coffee
@@ -38,51 +38,6 @@
       else
         @delegate?.attachmentEditorDidRequestRemovingAttributeForAttachment?("caption", @attachment)

-  createToolbarButtonsElement: ->
-    element = makeElement(tagName: "div", className: "trix-button-row")
-
-    if @attachment.isPreviewable()
-      element.innerHTML += """
-
-          <button type="button" data-trix-cols="3" class="trix-button trix-button--cols trix-button--cols-3" title="#{lang.cols3}">❙❙❙</button>
-          <button type="button" data-trix-cols="2" class="trix-button trix-button--cols trix-button--cols-2" title="#{lang.cols2}">❚❚</button>
-          <button type="button" data-trix-cols="1" class="trix-button trix-button--cols trix-button--cols-1" title="#{lang.cols1}">■</button>
-
-      """
-
-      cols = @attachmentPiece.getCols() || 1
-      button = element.querySelector("[data-trix-cols='#{cols}']")
-      button.classList.add("trix-active")
-
-    element.innerHTML += """
-
-        <button type="button" data-trix-action="remove" class="trix-button trix-button--remove" title="#{lang.remove}">#{lang.remove}</button>
-
-    """
-
-    handleEvent("click", onElement: element, withCallback: @didClickToolbarButton)
-    handleEvent("click", onElement: element, matchingSelector: "[data-trix-cols]", withCallback: @didClickColButton)
-    handleEvent("click", onElement: element, matchingSelector: "[data-trix-action]", withCallback: @didClickActionButton)
-    element
-
-  createMetadataElement: ->
-    element = makeElement(tagName: "span", className: "attachment__metadata")
-    name = @attachment.getFilename()
-    size = @attachment.getFormattedFilesize()
-
-    if name
-      nameElement = makeElement(tagName: "span", className: css.attachmentName, textContent: name, attributes: { title: name })
-      element.appendChild(nameElement)
-
-    if size
-      element.appendChild(document.createTextNode(" ")) if name
-      sizeElement = makeElement(tagName: "span", className: css.attachmentSize, textContent: size)
-      element.appendChild(sizeElement)
-
-    container = makeElement(tagName: "div", className: "attachment__metadata-container")
-    container.appendChild(element)
-    container
-
   # Installing and uninstalling

   makeElementMutable: undoable ->
@@ -90,13 +45,49 @@
     undo: => delete @element.dataset.trixMutable

   addToolbar: undoable ->
-    element = makeElement
-      tagName: "div"
-      className: "attachment__toolbar"
-      data: trixMutable: true
+    element = makeElement(tagName: "div", className: "attachment__toolbar", data: trixMutable: true)
+    element.innerHTML = """
+

+
+          <button type="button" data-trix-cols="1" class="trix-button trix-button--cols trix-button--cols-1" title="#{lang.cols1}">#{lang.cols1}</button>
+          <button type="button" data-trix-cols="2" class="trix-button trix-button--cols trix-button--cols-2" title="#{lang.cols2}">#{lang.cols2}</button>
+          <button type="button" data-trix-cols="3" class="trix-button trix-button--cols trix-button--cols-3" title="#{lang.cols3}">#{lang.cols3}</button>
+

-    element.appendChild(@createToolbarButtonsElement())
-    element.appendChild(@createMetadataElement()) if @attachment.isPreviewable()
+
+          <button type="button" data-trix-action="remove" class="trix-button trix-button--remove" title="#{lang.remove}">#{lang.remove}</button>
+
+

+
+

+
+
+
+
+

+    """
+
+    if @attachment.isPreviewable()
+      cols = @attachmentPiece.getCols() || 1
+      activeButton = element.querySelector("[data-trix-cols='#{cols}']")
+      activeButton.classList.add("trix-active")
+
+    if name = @attachment.getFilename()
+      nameElement = element.querySelector("[data-trix-attachment-name]")
+      nameElement.textContent = name
+      nameElement.title = name
+
+    if size = @attachment.getFormattedFilesize()
+      sizeElement = element.querySelector("[data-trix-attachment-size]")
+      sizeElement.textContent = size
+
+    type = @attachment.getType()
+    for child in element.querySelectorAll("[data-trix-type-only]:not([data-trix-type-only='#{type}'])")
+      child.parentNode.removeChild(child)
+
+    handleEvent("click", onElement: element, withCallback: @didClickToolbar)
+    handleEvent("click", onElement: element, matchingSelector: "[data-trix-cols]", withCallback: @didClickColButton)
+    handleEvent("click", onElement: element, matchingSelector: "[data-trix-action]", withCallback: @didClickActionButton)

     do: => @element.appendChild(element)
     undo: => @element.removeChild(element)
@@ -137,7 +128,7 @@

   # Event handlers

-  didClickToolbarButton: (event) =>
+  didClickToolbar: (event) =>
     event.preventDefault()
     event.stopPropagation()


reverse angle if right or bottom

reverse angle if right or bottom

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -421,8 +421,8 @@
         deg = -angle
       else
         deg = angle
-      if anchor is 'right'
-
+
+      if anchor is 'right' or anchor is 'bottom'
         deg = -deg

       if options.sticky


attach constructor to module.exports

attach constructor to module.exports

--- oridomi.coffee
+++ oridomi.coffee
@@ -1112,8 +1112,10 @@
   @devMode = -> devMode = true

-# Attach `OriDomi` constructor to `window`.
-root.OriDomi = OriDomi
+# Export constructor on `window` and `module.exports` (if applicable).
+root.OriDomi   = OriDomi
+module.exports = OriDomi if module?.exports?
+

 # Plugin Bridge


configurable gap nudge

configurable gap nudge

--- oridomi.coffee
+++ oridomi.coffee
@@ -406,6 +406,9 @@
   # This option allows you to supply the name of a CSS easing method or a
   # cubic bezier formula for customized animation easing.
   easingMethod: ''
+  # Number of pixels to offset each panel to prevent small gaps from appearing
+  # between them. This is configurable if you have a need for precision.
+  gapNudge: 1
   # Allows the user to fold the element via touch or mouse.
   touchEnabled: true
   # Coefficient of touch/drag action's distance delta. Higher numbers cause more movement.
@@ -686,7 +689,7 @@
                               rotateX(#{ x }deg)
                               rotateY(#{ y }deg)
                               rotateZ(#{ z }deg)
-                              translate#{ translate }px)
+                              translate#{ translate }#{ @_config.gapNudge }px)
                               "


reusable panel creation process

reusable panel creation process

--- oridomi.coffee
+++ oridomi.coffee
@@ -336,49 +336,7 @@
     # Add the horizontal mask prototype to the horizontal panel prototype.
     hPanel.appendChild hMask

-    # Loop through just the horizontal anchors.
-    for anchor in ['top', 'bottom']
-      # Loop through the number of horizontal panels.
-      for i in [0...@hPanels]
-        # Clone a copy of the panel prototype for manipulation.
-        panel = hPanel.cloneNode true
-        # Set a reference to its inner content.
-        content = panel.getElementsByClassName('oridomi-content')[0]
-
-        if anchor is 'top'
-          # The `yOffset` shifts the content of the panel down so they appear contiguous.
-          yOffset = -(i * @panelHeight)
-          # This conditional pushes each panel's position down so they stack on top of each other.
-          if i is 0
-            panel.style.top = '0'
-          else
-            panel.style.top = @panelHeight + 'px'
-        else
-          # For bottom panels, make sure the transform origin is `'bottom'`.
-          panel.style[css.origin] = 'bottom'
-          # For the bottom `yOffset` and top position, we need to work backwards.
-          yOffset = -((@hPanels * @panelHeight) - (@panelHeight * (i + 1)))
-
-          if i is 0
-            panel.style.top = @panelHeight * (@vPanels - 1) - bleed + 'px'
-          else
-            panel.style.top = -@panelHeight + 'px'
-
-        content.style.top = yOffset + 'px'
-
-        # Store references to the shader divs in the `shaders` object.
-        if @shading
-          @_shaders[anchor].top[i] = panel.getElementsByClassName('oridomi-shader-top')[0]
-          @_shaders[anchor].bottom[i] = panel.getElementsByClassName('oridomi-shader-bottom')[0]
-
-        # Store a reference to this panel in the `panels` object.
-        @panels[anchor][i] = panel
-
-        # Append each panel to its previous sibling (unless it's the first panel).
-        @panels[anchor][i - 1].appendChild panel unless i is 0
-
-      # Append the first panel (containing all of its siblings) to its respective stage.
-      @stages[anchor].appendChild @panels[anchor][0]
+    @_createPanels 'y', hPanel

     # Now that the horizontal panels are done, we can clone the `hMask` for the vertical mask prototype.
     vMask = hMask.cloneNode true
@@ -402,36 +360,7 @@
     vPanel.appendChild vMask

     # Repeat a similar panel creation process for vertical panels.
-    for anchor in ['left', 'right']
-      for i in [0...@vPanels]
-        panel = vPanel.cloneNode true
-        content = panel.getElementsByClassName('oridomi-content')[0]
-
-        if anchor is 'left'
-          xOffset = -(i * @panelWidth)
-          if i is 0
-            panel.style.left = '0'
-          else
-            panel.style.left = @panelWidth + 'px'
-        else
-          panel.style[css.origin] = 'right'
-          xOffset = -((@vPanels * @panelWidth) - (@panelWidth * (i + 1)))
-          if i is 0
-            panel.style.left = @panelWidth * (@vPanels - 1) - 1 + 'px'
-          else
-            panel.style.left = -@panelWidth + 'px'
-
-        content.style.left = xOffset + 'px'
-
-        if @shading
-          @_shaders[anchor].left[i]  = panel.getElementsByClassName('oridomi-shader-left')[0]
-          @_shaders[anchor].right[i] = panel.getElementsByClassName('oridomi-shader-right')[0]
-
-        @panels[anchor][i] = panel
-        @panels[anchor][i - 1].appendChild panel unless i is 0
-
-      @stages[anchor].appendChild @panels[anchor][0]
-
+    @_createPanels 'x', vPanel

     # Add a special class to the target element.
     @el.classList.add @settings.oriDomiClass
@@ -479,6 +408,49 @@

   # Internal Methods
   # ================
+
+
+  _createPanels: (axis, proto) ->
+    if axis is 'x'
+      anchors = ['left', 'right']
+      count   = @vPanels
+      metric  = 'width'
+    else
+      anchors = ['top', 'bottom']
+      count   = @hPanels
+      metric  = 'height'
+
+    percent = 100 / count
+
+    for anchor, n in anchors
+      for i in [0...count]
+        panel = proto.cloneNode true
+        panel.style[metric] = percent + '%' if i is 0
+        content = panel.getElementsByClassName('oridomi-content')[0]
+        content.style[metric] = count * 100 + '%'
+
+        if n is 0
+          content.style[anchor] = -i * 100 + '%'
+          if i is 0
+            panel.style[anchor] = '0'
+          else
+            panel.style[anchor] = '100%'
+        else
+          content.style[anchors[0]] = -(count - i - 1) * 100 + '%'
+          panel.style[css.origin] = anchor
+          if i is 0
+            panel.style[anchors[0]] = 100 - percent + '%'
+          else
+            panel.style[anchors[0]] = '-100%'
+
+        if @shading
+          for a in anchors
+            @_shaders[anchor][a][i] = panel.getElementsByClassName("oridomi-shader-#{ a }")[0]
+
+        @panels[anchor][i] = panel
+        @panels[anchor][i - 1].appendChild panel unless i is 0
+
+      @stages[anchor].appendChild @panels[anchor][0]

   # This method tests if the called action is identical to the previous one.


some refactoring

some refactoring

--- server/webserver.coffee
+++ server/webserver.coffee
@@ -6,7 +6,7 @@
 express = require("express")
 http = require("http")

-promise = require './promise'
+{Promise} = require './promise'

 publicdir = join __dirname, '../public'

@@ -24,15 +24,13 @@

     @server = http.createServer(app)
     @sio = socket.listen(@server)
-    @sio.set 'log level', 1
-
-    defer = promise.defer()
+    #@sio.set 'log level', 1

     listenObj = @server.listen port, host
-    listenObj.on 'listening', => defer.resolve(new Socket @sio)
-    listenObj.on 'error', -> defer.reject("can't start webserver")

-    defer.promise
+    new Promise (resolve, reject)=>
+      listenObj.on 'listening', => resolve(new Socket @sio)
+      listenObj.on 'error', -> reject("can't start webserver")

 class Socket extends BaseClass


fix(user): reset user model upon avatar upload

The original implementation didn't account for the initial gravatar url. The
fail to refresh is only seen on first avatar set. Reseting the model will ensure
we get the proper URL. It's a bit hacky, but that seemed better than
reimplementing the /api/auth/#{user.id}/avatar logic.

--- frontend/controllers/ProfileController.coffee
+++ frontend/controllers/ProfileController.coffee
@@ -17,8 +17,11 @@
                   file: avatar
               }).success (data, status, headers, config) ->
                 toastr.success "Avatar updated"
-                # hack to force image reloading
-                $scope.user.avatar_url += "?" + Math.random()
+                User.get().then (response) ->
+                  $scope.user = response.data
+
+                  # hack to force image reloading
+                  $scope.user.avatar_url += "?" + Math.random()

     User.get().then (response) ->
       $scope.user = response.data


Wip before switching.

Wip before switching.

--- test/javascripts/integration/super_ad_hoc_test.js.coffee
+++ test/javascripts/integration/super_ad_hoc_test.js.coffee
@@ -130,3 +130,23 @@
   andThen ->
     ok Em.$.trim(find('.inline-edit .item-subject').text()).indexOf('Deep') isnt -1
     ok Em.$.trim(find('.inline-edit .item-text').text()).indexOf('Awesome') isnt -1
+
+    request = _.findWhere(server.requests, {method: "PUT", url: "/tasks/1/send_message"})
+
+test "Adding sending an email from an adhoc task", ->
+  visit "/papers/#{ETahi.Test.currentPaper.id}/manage"
+
+  click '.card-content:contains("Super Ad-Hoc")'
+  click '.adhoc-content-toolbar .glyphicon-plus'
+  click '.adhoc-content-toolbar .adhoc-toolbar-item--email'
+  fillIn '.inline-edit-form input[placeholder="Enter a subject"]', "Deep subject"
+    Em.$('.inline-edit-form div[contenteditable]')
+      .html("Awesome email body!")
+      .trigger('keyup')
+    click '.task-body .inline-edit-body-part .button--green:contains("Save")'
+  andThen ->
+    ok Em.$.trim(find('.inline-edit .item-subject').text()).indexOf('Deep') isnt -1
+    ok Em.$.trim(find('.inline-edit .item-text').text()).indexOf('Awesome') isnt -1
+
+    request = _.findWhere(server.requests, {method: "PUT", url: "/tasks/1/send_message"})
+


Include device ID and channel in faults

Include device ID and channel in faults

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -430,6 +430,8 @@
             fault = faults[channel]
             if not fault?
               fault = {
+                device: currentRequest.sessionData.deviceId
+                channel: channel
                 timestamp: currentRequest.sessionData.timestamp
                 provisions: []
                 fault: flt


WIP: Implement decorator pattern for student dashboard.

--- javascripts/lib.coffee
+++ javascripts/lib.coffee
@@ -1,6 +1,7 @@
 #= require almond

 window.ttm ||= {}
+window.decorators ||= {}

 ttm.ClassMixer = (klass)->
   klass.build = ->


better data detection in box plots

better data detection in box plots

--- src/viz/types/box.coffee
+++ src/viz/types/box.coffee
@@ -32,7 +32,8 @@
       obj[key] = if vals.length is 1 then vals[0] else vals
     obj

-  noData = false
+  noData  = false
+  medians = []

   returnData = []
   d3.nest()
@@ -209,13 +210,14 @@
         d.d3plus.shape = vars.shape.value

       noData = !outliers.length and top - bottom is 0
+      medians.push median

       returnData = returnData.concat(outliers)

       return leaves
     .entries vars.data.viz

-  if noData then [] else returnData
+  if noData and uniques(medians).length is 1 then [] else returnData

 # Visualization Settings and Helper Functions
 box.modes        = ["tukey", "extent", Array, Number]


Fix typo

Fix typo

--- lib/init.coffee
+++ lib/init.coffee
@@ -165,7 +165,7 @@
     rootPath = path.dirname(filePath)
     baseNamePath = path.basename(filePath)

-    # Prepare the command line parameteres.
+    # Prepare the command line parameters.
     params = []

     ## Use the python module mypy


Using atom-linter::exec

Using atom-linter::exec

--- lib/init.coffee
+++ lib/init.coffee
@@ -15,53 +15,38 @@
   deactivate: ->
     @subscriptions.dispose()
   provideLinter: ->
+    helpers = require('atom-linter')
+    os = require 'os'
     provider =
       grammarScopes: ['source.elixir']
       scope: 'file'
       lintOnFly: false
-      name: 'Atom-Elixir'
+      name: 'Elixir'
       lint: (textEditor) =>
-        return new Promise (resolve, reject) =>
-          error_stack = []
-          compile_result = ''
-          helpers = require('atom-linter')
-          os = require 'os'
-          filePath = textEditor.getPath()
-          project_path = atom.project.getPaths()
-          build_env = "dev"
-          elixirc_args = [
-            "--ignore-module-conflict", "--app", "mix", "--app", "ex_unit", "-o", os.tmpDir(),
-            filePath
-          ]
-          parse_row = (row) ->
-            #console.log row
-            return unless row.startsWith('** ')
-            re = /.*\((.*)\) ([^:]+):(\d+): (.*)[\n\r]?/
-            re_result = re.exec(row)
-            ret =
-              type: re_result[1]
-              text: re_result[4]
-              fp: filePath
-              filePath: project_path[0] + '/' + re_result[2]
-              range: helpers.rangeFromLineNumber(textEditor, re_result[3] - 1)
-            #console.log ret
-            error_stack.push ret
-          process = new BufferedProcess
-            command: @executablePath
-            args: elixirc_args
-            options:
-              cwd: project_path[0]
-            stdout: (data) ->
-              compile_result += data
-            stderr: (data) ->
-              compile_result += data
-            exit: (code) ->
-              errors = compile_result.split("\n")
-              parse_row error for error in errors unless !errors?
-              resolve error_stack
-          process.onWillThrowError ({error, handle}) ->
-            atom.notifications.addError "Failed to run #{@executablePath}",
-              detail: "#{error.message}"
-              dismissable: true
-            handle()
-            resolve []
+        filePath = textEditor.getPath()
+        elixirc_args = [
+          "--ignore-module-conflict", "--app", "mix", "--app", "ex_unit", "-o", os.tmpDir(),
+          filePath
+        ]
+        project_path = atom.project.getPaths()
+        opts =
+          cwd: project_path[0]
+        helpers.exec(@executablePath, elixirc_args, opts)
+          .then (compile_result) ->
+            error_stack = []
+            parse_row = (row) ->
+              #console.log row
+              return unless row.startsWith('** ')
+              re = /.*\((.*)\) ([^:]+):(\d+): (.*)[\n\r]?/
+              re_result = re.exec(row)
+              ret =
+                type: re_result[1]
+                text: re_result[4]
+                fp: filePath
+                filePath: project_path[0] + '/' + re_result[2]
+                range: helpers.rangeFromLineNumber(textEditor, re_result[3] - 1)
+              #console.log ret
+              error_stack.push ret
+            errors = compile_result.split("\n")
+            parse_row error for error in errors unless !errors?
+            error_stack


Explain how to use mulitple file blacklist items

--- lib/main.coffee
+++ lib/main.coffee
@@ -36,7 +36,7 @@
       order: 5
     fileBlacklist:
       title: 'File Blacklist'
-      description: 'Suggestions will not be provided for files matching this list, e.g. *.md for Markdown files.'
+      description: 'Suggestions will not be provided for files matching this list, e.g. *.md for Markdown files. To blacklist more than one file extension, use comma as a separator, e.g. *.md, *.txt (both Markdown and text files).'
       type: 'array'
       default: ['.*']
       items:


:bug: Catch require time errors thrown from legacy providers

--- lib/main.coffee
+++ lib/main.coffee
@@ -47,8 +47,11 @@
     for atomPackage in atom.packages.getLoadedPackages()
       if atomPackage.metadata['linter-package'] is true
         implementation = atomPackage.metadata['linter-implementation'] ? atomPackage.name
-        linter = legacy(require("#{atomPackage.path}/lib/#{implementation}"))
-        @consumeLinter(linter)
+        try
+          linter = legacy(require("#{atomPackage.path}/lib/#{implementation}"))
+          @consumeLinter(linter)
+        catch error
+          atom.notifications.addError "Failed to activate the #{atomPackage.metadata['name']} package", {detail: error.message + "\n" + error.stack, dismissable: true}

   serialize: ->
     @instance.serialize()


Move firmware to project directory

Move firmware to project directory

--- lib/main.coffee
+++ lib/main.coffee
@@ -170,7 +170,7 @@
 						@consolePanel.raw fs.readFileSync(path.join(outputDir, 'memory-use.log')).toString()
 						# Rename binary based on platform
 						fs.moveSync path.join(outputDir, 'firmware.bin'),
-							path.join(outputDir, @profileManager.currentTargetPlatformName.toLowerCase() + '_firmware.bin')
+							path.join(projectDir, @profileManager.currentTargetPlatformName.toLowerCase() + '_firmware.bin')
 				, (error) =>
 					compileErrorHandler error


Don't use _units attrs for distances/angles on javascript side

This is problematic without a lot more work, because unlike on the
Python side it isn't kept in sync with the dictionary for the spec
value, leading to weird results such as:

 - if Python side did not send units, setting them on the JS side
   would not send the new units to the Python side
 - if Python side did send units (which I think it always does
   right now), then setting them on the JS side would do
   nothing - this is what always happens right now I believe
 - we don't listen to changes on _units attr, so setting after
   initial creation wouldn't work
 - reading the _units attr would not get the value from the
   spec dict, if the _units attr were unset, so reading _units
   didn't really work

--- bokehjs/src/coffee/common/properties.coffee
+++ bokehjs/src/coffee/common/properties.coffee
@@ -91,7 +91,7 @@
     super()
     obj = @get('obj')
     attr = @get('attr')
-    @units = @spec?.units ? obj.get("#{attr}_units") ? "rad"
+    @units = @spec?.units ? "rad"
     if @units != "deg" and @units != "rad"
       throw new Error("Angle units must be one of 'deg' or 'rad', given invalid value: #{@units}")

@@ -107,7 +107,7 @@
     super()
     obj = @get('obj')
     attr = @get('attr')
-    @units = @spec?.units ? obj.get("#{attr}_units") ? "data"
+    @units = @spec?.units ? "data"
     if @units != "data" and @units != "screen"
       throw new Error("Distance units must be one of 'data' or 'screen', given invalid value: #{@units}")


Add convenience methods to Plots (from python API)

--- bokehjs/src/coffee/models/plots/plot.coffee
+++ bokehjs/src/coffee/models/plots/plot.coffee
@@ -8,6 +8,7 @@
 Component = require "../component"
 GlyphRenderer = require "../renderers/glyph_renderer"
 Renderer = require "../renderers/renderer"
+ColumnDataSource = require "../sources/column_data_source"

 build_views = require "../../common/build_views"
 ToolEvents = require "../../common/tool_events"
@@ -714,11 +715,6 @@
     @_left_panel = do_side(solver, min_border_left, 'left', ['left', 'right'], 'width')
     @_right_panel = do_side(solver, min_border_right, 'right', ['right', 'left'], 'width')

-  add_renderers: (new_renderers) ->
-    renderers = @get('renderers')
-    renderers = renderers.concat(new_renderers)
-    @set('renderers', renderers)
-
   nonserializable_attribute_names: () ->
     super().concat(['canvas', 'tool_manager', 'frame', 'min_size'])

@@ -727,6 +723,34 @@
     if 'renderers' of attrs
       attrs['renderers'] = _.filter(attrs['renderers'], (r) -> r.serializable_in_document())
     attrs
+
+  add_renderers: (new_renderers...) ->
+    renderers = @get('renderers')
+    renderers = renderers.concat(new_renderers)
+    @set('renderers', renderers)
+
+  add_layout: (renderer, place="center") ->
+    if renderer.props.plot?
+      renderer.plot = this
+
+    @add_renderers(renderer)
+
+    if place != 'center'
+      @set(place, @get(place).concat([renderer]))
+
+  add_glyph: (glyph, source) ->
+    if not source?
+      source = new ColumnDataSource.Model()
+
+    renderer = new GlyphRenderer.Model({data_source: source, glyph: glyph})
+    @add_renderers(renderer)
+
+    return renderer
+
+  add_tools: (tools...) ->
+    for tool in tools
+      tool.plot = this
+      @set("tools", @get("tools").concat([tool]))

   @mixins ['line:outline_', 'text:title_', 'fill:background_', 'fill:border_']


#1069 - Add to library button shows when editing library question

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -320,6 +320,9 @@
         if @survey.rows.length is 0
           @null_top_row_view_selector.expand()

+      if !@features.copyToLibrary
+        @$el.find('.js-add-to-question-library').hide()
+
     render: ()->
       @$el.addClass("survey-editor--loading")
       @$el.removeClass("content--centered").removeClass("content")
@@ -328,9 +331,9 @@
         @_render_html()
         @_render_attachEvents()
         @_render_addSubViews()
+        @_reset()
+
         @_render_hideConditionallyDisplayedContent()
-
-        @_reset()

       catch error
         @$el.addClass("survey-editor--error")


refetch, don't just reset on VMListChanged

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -22,9 +22,9 @@
       else
         @emit 'ready'

-    mc.on   'AccountChanged', => @emit 'VMListChanged'
-
-    @on 'VMListChanged', @bound 'resetVMData'
+    mc.on 'AccountChanged', => @emit 'VMListChanged'
+
+    @on 'VMListChanged', @bound 'fetchVMs'

   run: Promise.promisify (options, callback = noop)->
     options ?= {}


Client: No modal on export of empty teams.

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -120,8 +120,6 @@
     @$(".select-team[data-cid=#{team.cid}]").attr('data-id', team.id)

   exportTeam: (e) =>
-    if $('#export-team-modal').length == 0
-      $('body').append(@exportTemplate())
     $team = $(e.currentTarget).closest('.select-team')
     id = $team.data('id')
     if not @getTeam(id).hasNonNullPokemon()
@@ -131,6 +129,8 @@
     teamJSON = @getTeam(id).toNonNullJSON()
     teamString = PokeBattle.exportTeam(teamJSON.pokemon)

+    if $('#export-team-modal').length == 0
+      $('body').append(@exportTemplate())
     $modal = $('#export-team-modal')
     $modal.find('.exported-team').val(teamString)
     $modal.modal('show')


client/test: clear value before setting it

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -171,7 +171,9 @@
       .waitForElementVisible modalSelector, 20000
       .waitForElementVisible emailSelector, 20000
       .waitForElementVisible companyNameSelector, 20000
+      .clearValue            emailSelector
       .setValue              emailSelector, user.email
+      .pause                 2000
       .setValue              companyNameSelector, user.teamSlug
       .click                 signUpButton
       .pause                 2500


added default opts for users compile features

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -17,8 +17,8 @@
 		AuthenticationController.getLoggedInUserId req, (error, user_id) ->
 			return next(error) if error?
 			UserGetter.getUser user_id, {"features.compileGroup":1, "features.compileTimeout":1}, (err, user)->
-				settingsOverride.timeout = user.features.compileTimeout
-				settingsOverride.compiler = user.features.compileGroup
+				settingsOverride.timeout = user.features.compileTimeout || Settings.defaultFeatures.compileTimeout
+				settingsOverride.compiler = user.features.compileGroup || Settings.defaultFeatures.compileGroup
 				CompileManager.compile project_id, user_id, { isAutoCompile, settingsOverride }, (error, status, outputFiles) ->
 					return next(error) if error?
 					res.contentType("application/json")


Pass `req` to function.

Pass `req` to function.

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -89,7 +89,7 @@
 		project_id = req.params.Project_id
 		projectName = req.body.projectName
 		logger.log project_id:project_id, projectName:projectName, "cloning project"
-		if !AuthenticationController.isUserLoggedIn()
+		if !AuthenticationController.isUserLoggedIn(req)
 			return res.send redir:"/register"
 		currentUser = AuthenticationController.getSessionUser(req)
 		projectDuplicator.duplicate currentUser, project_id, projectName, (err, project)->


always remove the spinner, even if it is not spinning

--- services/web/public/coffee/ide/pdfng/directives/pdfSpinner.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfSpinner.coffee
@@ -17,9 +17,9 @@
 				element.append(spinner)

 			stop: (element) ->
-				element.find('.fa-spin').removeClass('fa-spin')
+				element.find('.fa-spinner').removeClass('fa-spin')

 			remove: (element) ->
-				element.find('.fa-spin').remove()
+				element.find('.fa-spinner').remove()

 		]


Fix typo (#77)

Fix typo (#77)

Fix typo 'Homer Simpson'

--- support/greetings.coffee
+++ support/greetings.coffee
@@ -169,7 +169,7 @@
     [ "Wookiee",        "raaaaaahhgh raaaaaahhgh aaaaahnr" ]
     [ "Taelon",         "Sinuai Euhura" ]
     [ "Bart Simpson",   "Eat my shorts" ]
-    [ "Homer Simposon", "D'oh" ]
+    [ "Homer Simpson",  "D'oh" ]
     [ "Fonzie",         "Ayyy" ]
     [ "verax",          "yooooooooooooooooooooooooooooooooooooooooooooooo" ]
     [ "Protoss",        "You must construct additional pylons," ]


Corrected comment. It’s currently not possible to set `$list` before `render`. `render` overwrites it anyway.

--- coffee/chaplin/views/collection_view.coffee
+++ coffee/chaplin/views/collection_view.coffee
@@ -21,15 +21,14 @@
     animationDuration: 500

     # A collection view may have a template and use one of its child elements
-    # as the container of the item views. Specify `listSelector` or set
-    # `$list` before rendering. The item views will be appended to this
-    # element. If empty, $el is used.
+    # as the container of the item views. If you specify `listSelector`, the
+    # item views will be appended to this element. If empty, $el is used.
     listSelector: null

     # The actual element which is fetched using `listSelector`
     $list: null

-    # Selector for a fallback element which is shown when the collection is empty.
+    # Selector for a fallback element which is shown if the collection is empty.
     fallbackSelector: null

     # The actual element which is fetched using `fallbackSelector`


better dimensions handling on canvases

better dimensions handling on canvases

--- coffeescript/love.js/graphics/canvas.coffee
+++ coffeescript/love.js/graphics/canvas.coffee
@@ -1,8 +1,7 @@
 class Canvas
-  constructor: (@width, @height) ->
+  constructor: (width, height) ->
     @element = document.createElement('canvas')
-    @element.setAttribute('width', @width)
-    @element.setAttribute('height', @height)
+    this.setDimensions(@width, @height)
     @context = @element.getContext('2d')

   clear: (self, r, g, b, a) ->
@@ -59,4 +58,8 @@
     @context.textAlign = context.textAlign
     @context.textBaseline = context.textBaseline

+  setDimensions: (@width, @height) ->
+    @element.setAttribute('width', @width)
+    @element.setAttribute('height', @height)
+
 Canvas.transparent = new Color(0, 0, 0, 0)


KDFRAMEWORK: fixed missing =

KDFRAMEWORK: fixed missing =

--- components/list/KDListViewController.coffee
+++ components/list/KDListViewController.coffee
@@ -55,7 +55,7 @@

   addDefaultItem:->
     {itemClass,options,data} = @getOptions().defaultItem
-    @getListView().addSubView @defaultItem new itemClass options, data
+    @getListView().addSubView @defaultItem = new itemClass options, data

   removeDefaultItem:->
     @getListView().removeSubView @defaultItem if @defaultItem


connect directly to storage.googleapis.com

--- source/09__vocab.coffee
+++ source/09__vocab.coffee
@@ -56,7 +56,7 @@

     tagg.addBank(new tagg.FamilyBank({
         protocol: "https",
-        hostname: "tree.tagg.to",
-        path: "/"
+        hostname: "storage.googleapis.com",
+        path: "/tree.tagg.to/"
         port: "443"
     }))


:lipstick: Harmonize lineForRow calls in specs

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -2070,31 +2070,31 @@
             'sort'
           ])

-      describe ".pasteText()", ->
+      fdescribe ".pasteText()", ->
         it "pastes text into the buffer", ->
           atom.clipboard.write('first')
           editor.pasteText()
-          expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
-          expect(buffer.lineForRow(1)).toBe "  var first = function(items) {"
+          expect(editor.lineForBufferRow(0)).toBe "var first = function () {"
+          expect(editor.lineForBufferRow(1)).toBe "  var first = function(items) {"

         describe 'when the clipboard has many selections', ->
           it "pastes each selection separately into the buffer", ->
             atom.clipboard.write('first\nsecond', {selections: ['first', 'second'] })
             editor.pasteText()
-            expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
-            expect(buffer.lineForRow(1)).toBe "  var second = function(items) {"
+            expect(editor.lineForBufferRow(0)).toBe "var first = function () {"
+            expect(editor.lineForBufferRow(1)).toBe "  var second = function(items) {"

           describe 'and the selections count does not match', ->
             it "pastes the whole text into the buffer", ->
               atom.clipboard.write('first\nsecond\nthird', {selections: ['first', 'second', 'third'] })
               editor.pasteText()
-              expect(editor.buffer.lineForRow(0)).toBe "var first"
-              expect(buffer.lineForRow(1)).toBe "second"
-              expect(buffer.lineForRow(2)).toBe "third = function () {"
-
-              expect(editor.buffer.lineForRow(3)).toBe "  var first"
-              expect(buffer.lineForRow(4)).toBe "second"
-              expect(buffer.lineForRow(5)).toBe "third = function(items) {"
+              expect(editor.lineForBufferRow(0)).toBe "var first"
+              expect(editor.lineForBufferRow(1)).toBe "second"
+              expect(editor.lineForBufferRow(2)).toBe "third = function () {"
+
+              expect(editor.lineForBufferRow(3)).toBe "  var first"
+              expect(editor.lineForBufferRow(4)).toBe "second"
+              expect(editor.lineForBufferRow(5)).toBe "third = function(items) {"

     describe ".indentSelectedRows()", ->
       describe "when nothing is selected", ->


Remove unused stylesheet cleanup in spec-helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -154,8 +154,6 @@
   atom.project?.destroy()
   atom.project = null

-  atom.themes.removeStylesheet('global-editor-styles')
-
   delete atom.state.packageStates
   atom.reset()


Exclude deprecated APIs when running specs

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,2 +1,3 @@
 require 'coffee-cache'
 jasmine.getEnv().addEqualityTester(require('underscore-plus').isEqual)
+require('grim').includeDeprecatedAPIs = false


:lipstick:

:lipstick:

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -20,6 +20,13 @@
 keymap.loadBundledKeymaps()
 [bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []

+$(window).on 'core:close', -> window.close()
+$(window).on 'toggle-dev-tools', (e) -> atom.toggleDevTools()
+$('html,body').css('overflow', 'auto')
+
+jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
+jasmine.getEnv().defaultTimeoutInterval = 1000
+
 beforeEach ->
   jQuery.fx.off = true
   window.fixturesProject = new Project(require.resolve('fixtures'))
@@ -68,13 +75,6 @@
   ensureNoPathSubscriptions()
   waits(0) # yield to ui thread to make screen update more frequently

-$(window).on 'core:close', -> window.close()
-$(window).on 'toggle-dev-tools', (e) -> atom.toggleDevTools()
-$('html,body').css('overflow', 'auto')
-
-jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
-jasmine.getEnv().defaultTimeoutInterval = 1000
-
 window.loadPackage = (name, options) ->
   Package = require 'package'
   packagePath = _.find atom.getPackagePaths(), (packagePath) -> fs.base(packagePath) == name


Rewrite content-length header for proxied responses

--- speed-middleware.coffee
+++ speed-middleware.coffee
@@ -10,6 +10,13 @@
   data = ''
   write = res.write
   end = res.end
+  writeHead = res.writeHead
+  proxiedStatusCode = null
+  proxiedHeaders = null
+
+  res.writeHead = (statusCode, headers) ->
+    proxiedStatusCode = statusCode
+    proxiedHeaders = headers

   res.write = (chunk) ->
     data += chunk
@@ -25,6 +32,9 @@
     # Restore res properties
     res.write = write
     res.end = end
+    res.writeHead = writeHead
+    proxiedHeaders['content-length'] = Buffer.byteLength(data)
+    res.writeHead proxiedStatusCode, proxiedHeaders
     res.end data, encoding

   next()


Refactor to use jQuery

Refactor to use jQuery

--- src/renderer/main.coffee
+++ src/renderer/main.coffee
@@ -2,58 +2,54 @@
 Category = require './multiplication-category'
 $ = require './jquery-1.11.2.min.js'

-contentElement = ->
-  content = document.getElementById('content')
-
-addElement = (elementName, elementClass, textContent) ->
-  element = document.createElement elementName
-  element.setAttribute('class', elementClass)
-  element.innerText = textContent if textContent
-  contentElement().appendChild element
-
-addCategoryButton = (categoryName, onclick) ->
-  element = addElement 'a', 'category', categoryName
-  element.addEventListener 'click', onclick
-
-onCategoryButtonClick = (event) ->
-  while contentElement().hasChildNodes()
-    contentElement().removeChild contentElement().lastChild
-
-  addQuestion()
-  addAnswer()
+content = ->
+  $('#content')

 question = ->
   $('.question')

-addQuestion = ->
-  addElement 'div', 'question', 'Press any key to start'
-
 answer = ->
   $('.answer')

+toId = (string) ->
+  string.replace(' ', '-')
+
+addCategoryButton = (categoryName) ->
+  categoryId = toId(categoryName)
+  content().append(
+    $('' + categoryName + ''))
+  $('#' + categoryId).click onCategoryButtonClick
+
+onCategoryButtonClick = (event) ->
+  content().empty()
+  addQuestion()
+  addAnswer()
+
+addQuestion = ->
+  content().append($('
Press any key to start
'))
+
 addAnswer = ->
-  answer = addElement 'input', 'answer'
-  answer.setAttribute 'type', 'text'
-  answer.focus()
+  content().append($('<input type="text" class="answer"/>'))
+  answer().focus()
+  answer().keypress startQuestions

   # Then first question appears
   # Listen to key presses, waiting until correct answer given
   # Then disable all key presses, show success, wait for a second
   # Then move to next question
-  $('.answer').keypress startQuestions

 answerCheckerId = null

 startQuestions = (event) ->
   event.preventDefault()
-  $('.question').text('17 x 87')
-  $('.answer').off('keypress')
-  $('.answer').keypress allowDigitsOnly
+  question().text('17 x 87')
+  answer().off('keypress')
+  answer().keypress allowDigitsOnly
   answerCheckerId = setInterval checkAnswer, 100

 checkAnswer = ->
-  if $('.answer').val() is '1'
-    $('.question').text('Correct!')
+  if answer().val() is '1'
+    question().text('Correct!')
     clearInterval answerCheckerId

 allowDigitsOnly = (event) ->
@@ -69,8 +65,8 @@
   zeroToNine

 multiplications = Multiplication.multiplications(100, 100)
-Category.all().map((c) -> addCategoryButton c.name, onCategoryButtonClick)
-addCategoryButton 'uncategorized', onCategoryButtonClick
+Category.all().map((c) -> addCategoryButton c.name)
+addCategoryButton 'uncategorized'

 # NeDB
 #Datastore = require 'nedb'


`new Directory` path has to be specified on Windows

--- lib/util.coffee
+++ lib/util.coffee
@@ -16,7 +16,7 @@
     if res?.getPath?() is 'atom://config'
       res = null
     unless res?.isDirectory?()
-      res = buffer.file?.getParent?() ? new Directory
+      res = buffer.file?.getParent?() ? new Directory ''
     Util.debug "getRootDir path = #{res.getPath()}",
       "atom.project.getDirectories()[0] = #{atom.project.getDirectories()[0]?.getPath?()}",
       "buffer.file?.getParent?() = #{buffer.file?.getParent?()?.getPath?()}"


more strings translated

more strings translated

--- app/locale/el.coffee
+++ app/locale/el.coffee
@@ -31,7 +31,7 @@
     contact: "Επικοινωνία"
     twitter_follow: "Ακολούθησε"
     teachers: "Εκπαιδευτικοί"
-#    careers: "Careers"
+    careers: "Καριέρες"

   modal:
     close: "Κλείσιμο"
@@ -56,7 +56,7 @@
     items: "Αντικείμενα" # Tooltip on item shop button from /play
     unlock: "Ξεκλειδώστε" # For purchasing items and heroes
     confirm: "Επιβεβαίωση"
-#    owned: "Owned" # For items you own
+    owned: "Σου ανήκει" # For items you own
     locked: "Κλειδωμένο"
     purchasable: "Διαθέσιμο για αγορά" # For a hero you unlocked but haven't purchased
     available: "Διαθέσιμο"
@@ -65,7 +65,7 @@
     achievements: "Επιτεύγματα" # Tooltip on achievement list button from /play
     account: "Λογαριασμός" # Tooltip on account button from /play
     settings: "Ρυθμίσεις" # Tooltip on settings button from /play
-#    poll: "Poll" # Tooltip on poll button from /play
+    poll: "Δημοσκόπηση" # Tooltip on poll button from /play
     next: "Επόμενο" # Go from choose hero to choose inventory before playing a level
     change_hero: "Αλλαγή Ήρωα" # Go back from choose inventory to choose hero
     choose_inventory: "Εξοπλίσου με Αντικείμενα"
@@ -74,19 +74,19 @@
     anonymous: "Ανώνυμοι Παίκτες"
     level_difficulty: "Δυσκολία: "
     campaign_beginner: "Εκστρατεία για Αρχάριους"
-#    awaiting_levels_adventurer_prefix: "We release new levels every week."
-#    awaiting_levels_adventurer: "Sign up as an Adventurer"
-#    awaiting_levels_adventurer_suffix: "to be the first to play new levels."
-#    adjust_volume: "Adjust volume"
+    awaiting_levels_adventurer_prefix: "Έχουμε νέα επίπεδα κάθε εβδομάδα."
+    awaiting_levels_adventurer: "Εγγραφή ως Adventurer"
+    awaiting_levels_adventurer_suffix: "για να παίξεις πρώτος νέα επίπεδα."
+    adjust_volume: "ρύθμιση έντασης"
     campaign_multiplayer: "Αρένες Πολλαπλών Παικτών"
     campaign_multiplayer_description: "... στις οποίες προγραμματίζετε σώμα-με-σώμα εναντίον άλλων παικτών."
 #    campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
 #    campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."

   share_progress_modal:
-#    blurb: "You’re making great progress! Tell your parent how much you've learned with CodeCombat."
-#    email_invalid: "Email address invalid."
-#    form_blurb: "Enter your parent's email below and we’ll show them!"
+    blurb: "Κάνεις σημαντική πρόοδο! Πες τους γονείς σου πόσα έχεις μάθει με το CodeCombat."
+    email_invalid: "Άκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου."
+    form_blurb: "Δώσε τη διεύθυνση ηλεκτρονικού ταχυδρομείου των γονιών σου και θα τους το δείξουμε!"
     form_label: "Διεύθυνση ηλεκτρονικού ταχυδρομίου"
     placeholder: "Διεύθυνση ηλεκτρονικού ταχυδρομίου"
     title: "Εξαιρετική Δουλειά, Μαθητευόμενε"
@@ -97,12 +97,12 @@
     logging_in: "Σύνδεση σε εξέλιξη"
     log_out: "Αποσύνδεση"
     forgot_password: "Ξεχάσατε τον κωδικό σας;"
-#    authenticate_gplus: "Authenticate G+"
-#    load_profile: "Load G+ Profile"
-#    finishing: "Finishing"
+    authenticate_gplus: "Authenticate G+"
+    load_profile: "Φόρτωση προφίλ του G+"
+    finishing: "Τελειώνει"
     sign_in_with_facebook: "Σύνδεση μέσω Facebook"
     sign_in_with_gplus: "Σύνδεση μέσω G+"
-#    signup_switch: "Want to create an account?"
+    signup_switch: "θέλεις να δημιουργήσεις ένα λογαριασμός;"

   signup:
     email_announcements: "Λαμβάνετε ανακοινώσεις μέσω email"
@@ -111,7 +111,7 @@
     log_in: "Σύνδεση με κωδικό"
     social_signup: "Ή, μπορείς να συνδεθείς μέσω Facebook ή G+:"
     required: "Θα πρέπει να συνδεθείτε πριν πάτε προς τα εκεί."
-#    login_switch: "Already have an account?"
+    login_switch: "Έχεις ήδη λογαριασμό;"

   recover:
     recover_account_title: "Ανάκτηση λογαριασμού"
@@ -119,8 +119,8 @@
     recovery_sent: "Email ανάκτησης στάλθηκε."

   items:
-#    primary: "Primary"
-#    secondary: "Secondary"
+    primary: "Κύριο"
+    secondary: "Δευτερεύων"
     armor: "Πανοπλία"
     accessories: "Εξαρτήματα"
     misc: "Διάφορα"
@@ -139,40 +139,40 @@
     create: "Δημιουργία"
 #    fork: "Fork"
     play: "Παίξε" # When used as an action verb, like "Play next level"
-#    retry: "Retry"
-#    actions: "Actions"
-#    info: "Info"
-#    help: "Help"
+    retry: "Ξαναδοκίμασε"
+    actions: "Ενέργειες"
+    info: "Πληροφορίες"
+    help: "Βοήθεια"
     watch: "Παρακολούθηση"
     unwatch: "Παύση παρακολούθηση"
     submit_patch: "Αποστολή Επιδιόρθωσης"
-#    submit_changes: "Submit Changes"
-#    save_changes: "Save Changes"
+    submit_changes: "Υποβολή αλλαγών"
+    save_changes: "Απόθηκευση Αλλαγών"

   general:
     and: "και"
     name: "Όνομα"
     date: "Ημερομηνία"
-#    body: "Body"
+    body: "Σώμα"
     version: "Έκδοση"
     pending: "Εκκρεμεί"
-#    accepted: "Accepted"
-#    rejected: "Rejected"
-#    withdrawn: "Withdrawn"
-#    accept: "Accept"
-#    reject: "Reject"
-#    withdraw: "Withdraw"
+    accepted: "Έγινε δεκτό"
+    rejected: "Απορίφθηκε"
+    withdrawn: "Αποσύρθηκε"
+    accept: "Αποδοχή"
+    reject: "Απόρριψη"
+    withdraw: "Απόσυρση"
 #    submitter: "Submitter"
 #    submitted: "Submitted"
 #    commit_msg: "Commit Message"
 #    version_history: "Version History"
 #    version_history_for: "Version History for: "
-#    select_changes: "Select two changes below to see the difference."
-#    undo_prefix: "Undo"
-#    undo_shortcut: "(Ctrl+Z)"
-#    redo_prefix: "Redo"
-#    redo_shortcut: "(Ctrl+Shift+Z)"
-#    play_preview: "Play preview of current level"
+    select_changes: "επίλέξτε δύο αλλαγές από κάτω για να δείτε τη διαφορά."
+    undo_prefix: "Αναίρεση"
+    undo_shortcut: "(Ctrl+Z)"
+    redo_prefix: "Επανάληψη"
+    redo_shortcut: "(Ctrl+Shift+Z)"
+    play_preview: "Παίξε μια προεπισκόπηση του τρέχοντος επιπέδου"
     result: "Αποτέλεσμα"
     results: "Αποτελέσματα"
     description: "Περιγραφή"
@@ -194,9 +194,9 @@
     medium: "Μέτριο"
     hard: "Δύσκολο"
     player: "Παίκτης"
-#    player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
-#    warrior: "Warrior"
-#    ranger: "Ranger"
+    player_level: "Επίπεδο" # Like player level 5, not like level: Dungeons of Kithgard
+    warrior: "Πολεμιστής"
+    ranger: "Καταδρομέας"
     wizard: "Μάγος"

   units:
@@ -217,8 +217,8 @@

   play_level:
     done: "Έτοιμο"
-#    next_game: "Next game"
-#    show_menu: "Show game menu"
+    next_game: "Επόμενο παιχνίδι"
+    show_menu: "Εμφάνιση μενού παιχνιδιού"
     home: "Αρχική" # Not used any more, will be removed soon.
     level: "Επίπεδο" # Like "Level: Dungeons of Kithgard"
     skip: "Παράλειψη"
@@ -227,99 +227,99 @@
     restart: "Επανεκκίνηση"
     goals: "Στόχοι"
     goal: "Στόχος"
-#    running: "Running..."
+    running: "Εκτελείται..."
     success: "Επιτυχία!"
-#    incomplete: "Incomplete"
-#    timed_out: "Ran out of time"
-#    failing: "Failing"
+    incomplete: "Ατελές"
+    timed_out: "Έμεινες από χρόνο"
+    failing: "Αποτυγχάνει"
     action_timeline: "Χρονοδιάγραμμα δράσης"
     click_to_select: "Κάντε κλικ σε μια μονάδα για να το επιλέξετε."
-#    control_bar_multiplayer: "Multiplayer"
-#    control_bar_join_game: "Join Game"
-#    reload: "Reload"
+    control_bar_multiplayer: "πολλών παικτών"
+    control_bar_join_game: "Παίξε κι εσύ"
+    reload: "Επαναφόρτωση"
     reload_title: "Ανανέωση όλου του κωδικά;"
     reload_really: "Είστε σίγουροι ότι θέλετε να φορτώσετε αυτό το επίπεδο από την αρχή;"
     reload_confirm: "Ανανέωση όλων"
     victory: "Νίκη"
 #    victory_title_prefix: ""
-#    victory_title_suffix: " Complete"
+    victory_title_suffix: " Ολοκληρώθηκε"
     victory_sign_up: "Εγγραφείτε για ενημερώσεις"
     victory_sign_up_poke: "Θέλετε να λαμβάνετε τα τελευταία νέα μέσω e-mail; Δημιουργήστε έναν δωρεάν λογαριασμό και θα σας κρατάμε ενήμερους!"
     victory_rate_the_level: "Βαθμολογήστε το επίπεδο: " # Only in old-style levels.
 #    victory_return_to_ladder: "Return to Ladder"
-#    victory_play_continue: "Continue"
-#    victory_saving_progress: "Saving Progress"
+    victory_play_continue: "Συνέχισε"
+    victory_saving_progress: "Αποθήκευση προόδου"
     victory_go_home: "Πηγαίνετε στην Αρχική"
     victory_review: "Πείτε μας περισσότερα!"
-#    victory_review_placeholder: "How was the level?"
+    victory_review_placeholder: "Πως σου φάνηκε το επίπεδο;"
     victory_hour_of_code_done: "Τελείωσες;"
     victory_hour_of_code_done_yes: "Ναι, έχω τελειώσει με την Hour of Code!"
 #    victory_experience_gained: "XP Gained"
 #    victory_gems_gained: "Gems Gained"
-#    victory_new_item: "New Item"
+    victory_new_item: "Νέο αντικείμενο"
 #    victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
-#    victory_become_a_viking: "Become a Viking"
+    victory_become_a_viking: "Γίνε Βίκιγκ"
 #    victory_bloc: "Great work! Your skills are improving, and someone's taking notice. If you've considered becoming a software developer, this may be your lucky day. Bloc is an online bootcamp that pairs you 1-on-1 with an expert mentor who will help train you into a professional developer! By beating A Mayhem of Munchkins, you're now eligible for a $500 price reduction with the code: CCRULES"
-#    victory_bloc_cta: "Meet your mentor – learn about Bloc"
+    victory_bloc_cta: "Συνάντησε το μέντορά σου – μάθε για το Bloc"
     guide_title: "Οδηγός"
     tome_minion_spells: "Ξόρκια για τα τσιράκια σας" # Only in old-style levels.
     tome_read_only_spells: "Ξορκια μονο για αναγνωση" # Only in old-style levels.
     tome_other_units: "Άλλες μονάδες" # Only in old-style levels.
-#    tome_cast_button_run: "Run"
-#    tome_cast_button_running: "Running"
-#    tome_cast_button_ran: "Ran"
-#    tome_submit_button: "Submit"
-#    tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
-#    tome_select_method: "Select a Method"
-#    tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods).
-#    tome_select_a_thang: "Select Someone for "
-#    tome_available_spells: "Available Spells"
-#    tome_your_skills: "Your Skills"
-#    tome_current_method: "Current Method"
-#    hud_continue_short: "Continue"
-#    code_saved: "Code Saved"
-#    skip_tutorial: "Skip (esc)"
-#    keyboard_shortcuts: "Key Shortcuts"
-#    loading_ready: "Ready!"
-#    loading_start: "Start Level"
-#    problem_alert_title: "Fix Your Code"
-#    time_current: "Now:"
-#    time_total: "Max:"
-#    time_goto: "Go to:"
-#    non_user_code_problem_title: "Unable to Load Level"
-#    infinite_loop_title: "Infinite Loop Detected"
-#    infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
-#    check_dev_console: "You can also open the developer console to see what might be going wrong."
-#    check_dev_console_link: "(instructions)"
-#    infinite_loop_try_again: "Try Again"
-#    infinite_loop_reset_level: "Reset Level"
-#    infinite_loop_comment_out: "Comment Out My Code"
-#    tip_toggle_play: "Toggle play/paused with Ctrl+P."
-#    tip_scrub_shortcut: "Use Ctrl+[ and Ctrl+] to rewind and fast-forward."
-#    tip_guide_exists: "Click the guide, inside game menu (at the top of the page), for useful info."
-#    tip_open_source: "CodeCombat is 100% open source!"
-#    tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!"
-#    tip_beta_launch: "CodeCombat launched its beta in October, 2013."
-#    tip_think_solution: "Think of the solution, not the problem."
-#    tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
-#    tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
-#    tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"
-#    tip_forums: "Head over to the forums and tell us what you think!"
-#    tip_baby_coders: "In the future, even babies will be Archmages."
-#    tip_morale_improves: "Loading will continue until morale improves."
-#    tip_all_species: "We believe in equal opportunities to learn programming for all species."
+    tome_cast_button_run: "Εκτέλση"
+    tome_cast_button_running: "Εκτελείται"
+    tome_cast_button_ran: "Εκτελέστηκε"
+    tome_submit_button: "Υποβολή"
+    tome_reload_method: "Φόρτωσε ξανά τον αρχικό κώδικα για τη μέθοδο αυτή" # Title text for individual method reload button.
+    tome_select_method: "Διάλεξε μια μέθοδο"
+    tome_see_all_methods: "Δες όλες τις μεθόδους που μπορείς να επεξεργαστείς" # Title text for method list selector (shown when there are multiple programmable methods).
+    tome_select_a_thang: "Διάλεξε κάποιον για "
+    tome_available_spells: "Διαθέσιμα ξόρκια"
+    tome_your_skills: "Οι Ικανότητες σου"
+    tome_current_method: "Τρέχουσα μέθοδος"
+    hud_continue_short: "Συνέχισε"
+    code_saved: "Ο κώδικας αποθηκεύτηκε"
+    skip_tutorial: "Παράκαμψη (esc)"
+    keyboard_shortcuts: "Συντομεύσεις"
+    loading_ready: "Έτοιμος!"
+    loading_start: "Ξεκίνα το Επίπεδο"
+    problem_alert_title: "Διόρθωσε τον Κώδικά σου"
+    time_current: "Τώρα:"
+    time_total: "Μεγ:"
+    time_goto: "Πήγαινε:"
+    non_user_code_problem_title: "Δεν μπορώ να φορτώσω το επίπεδο"
+    infinite_loop_title: "Ανιχνεύθυκε ατέρμονας βρόχος"
+    infinite_loop_description: "Ο αρχικός κώδικας δημιουργίας του κόσμου εκτελείται επ' άπειρο. Ή είναι πραγματικά αργός κώδικας ή έχουμε επ' άπειρο βρόχο. Ή πρόκειται για προγραμματιστικό σφάλμα. Μπορείται να δοκιμάσετε να εκτελέσετε τον κώδικα ξανάY ή να επαναφέρετε τον κώδικα στην αρχική του κατάσταση. Αν κι αυτό δε φτιάξει το πρόβλημα, παρακαλούμε επικοινωνείστε μαζί μας."
+    check_dev_console: "Μπορείτε επίσης να ανοίξετε την κονσόλα προγραμματιστή κγια να δείτε τι μπορεί να είναι το λάθος"
+    check_dev_console_link: "(οδηγίες)"
+    infinite_loop_try_again: "Δοκίμασε Ξανά"
+    infinite_loop_reset_level: "Επαναφορά Επιπέδου"
+    infinite_loop_comment_out: "Βάλε το κώδικά μου σε σχόλια"
+    tip_toggle_play: "Εναλλαγή παιχνιδιού/παύσης με Ctrl+P."
+    tip_scrub_shortcut: "Πάτησε Ctrl+[ και Ctrl+] για να πας γρήγορα πίσω και μπροστά."
+    tip_guide_exists: "Για χρήσιμες πληροφορίες, πάτα τον οδηγό, μέσα στο μενού του παιχνιδιού (στην κορυφή της σελίδας)."
+    tip_open_source: "Το CodeCombat είναι 100% ανοικτού κώδικα!"
+    tip_tell_friends: "Σου αρέσει το CodeCombat; Πες στους φίλους σου για αυτό!"
+    tip_beta_launch: "Το CodeCombat ξεκίνησε τη δοκιμαστική του μορφή τον Οκτώβlaunched its beta in October, 2013.3."
+    tip_think_solution: "Σκέψου τη λύση, όχι το πρόβλημα."
+    tip_theory_practice: "Στη θεωρία, δεν υπάρχει καμμιά διαφορά μεταξύ θεωρίας και πράξης. Αλλά στην πράξη, υπάρχει. - Yogi Berra"
+    tip_error_free: "Υπάρχουν δύο τρόποι να γράψεις προγράμματα χωρίς σφάλματα. Μόνο ο τρίτος τρόπος δουλεύει. - Alan Perlis"
+    tip_debugging_program: "Αν αποσφαλμάτωση είνα η διαδικασία του να απομακρύνει σφάλματα τότε ο προγραμματισμός πρέπει να είναι η διαδικασία του να τα βάζεις. - Edsger W. Dijkstra"
+    tip_forums: "Πήγαινε στο φόρουμ και πες μας τι νομίζεις!"
+    tip_baby_coders: "Στο μέλλον, ακόμα και τα μωρά θα είναιArchmages."
+    tip_morale_improves: "Η φόρτωση θα συνεχιστεί μέχρι να βελτιωθεί το ηθικό."
+    tip_all_species: "Πιστεύουμε σε ίσες ευκαιρείες για να μάθουν προγραμματισμό όλα τα είδη."
 #    tip_reticulating: "Reticulating spines."
 #    tip_harry: "Yer a Wizard, "
 #    tip_great_responsibility: "With great coding skill comes great debug responsibility."
 #    tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
-#    tip_binary: "There are only 10 types of people in the world: those who understand binary, and those who don't."
+    tip_binary: "Υπάρχουν 10 μόνο είδη ανθρώπων στον κόσμο: αυτοί που καταλαβαίνουν δυαδικό σύστημα και αυτοί που δεν καταλαβαίνουν"
 #    tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
 #    tip_no_try: "Do. Or do not. There is no try. - Yoda"
 #    tip_patience: "Patience you must have, young Padawan. - Yoda"
-#    tip_documented_bug: "A documented bug is not a bug; it is a feature."
-#    tip_impossible: "It always seems impossible until it's done. - Nelson Mandela"
-#    tip_talk_is_cheap: "Talk is cheap. Show me the code. - Linus Torvalds"
-#    tip_first_language: "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay"
+    tip_documented_bug: "Ένα τεκμηριωμένο σφάλμα δεν είναι σφάλμα. Είναι χαρακτηριστικό."
+    tip_impossible: "Πάντα φαίνεται αδύνατο μέχρι να γίνει. - Nelson Mandela"
+    tip_talk_is_cheap: "Τα λόγια είναι τσάμπα. Δείξε μου των κώδικα. - Linus Torvalds"
+    tip_first_language: "Το πιο καταστροφικό πράγμα που μπορεί να μάθεις ποτέ είναι η πρώτη σου γλώσσα προγραμματισμού.- Alan Kay"
 #    tip_hardware_problem: "Q: How many programmers does it take to change a light bulb?  A: None, it's a hardware problem."
 #    tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
 #    tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
@@ -347,60 +347,60 @@

   game_menu:
 #    inventory_tab: "Inventory"
-#    save_load_tab: "Save/Load"
-#    options_tab: "Options"
-#    guide_tab: "Guide"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
+    save_load_tab: "Αποθήκευση/Φόρτωση"
+    options_tab: "Επιλογές"
+    guide_tab: "Οδηγός"
+    guide_video_tutorial: "Βίντεο Βοήθειας"
+    guide_tips: "Tips"
     multiplayer_tab: "Πολλαπλοί παίχτες"
-#    auth_tab: "Sign Up"
-#    inventory_caption: "Equip your hero"
-#    choose_hero_caption: "Choose hero, language"
-#    save_load_caption: "... and view history"
-#    options_caption: "Configure settings"
-#    guide_caption: "Docs and tips"
-#    multiplayer_caption: "Play with friends!"
-#    auth_caption: "Save your progress."
-
-#  leaderboard:
+    auth_tab: "Εγγραφή"
+    inventory_caption: "Εξόπλισε τον ήρωά σου"
+    choose_hero_caption: "Διάλεξε ήρωα και γλώσσα"
+    save_load_caption: "... και δες την ιστορία"
+    options_caption: "Ρυθμίσεις"
+    guide_caption: "Τεκμηρίωση και βοήθεια"
+    multiplayer_caption: "Παίξε με φίλους!"
+    auth_caption: "Αποθήκευσε την πρόοδό σου."
+
+  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Leaderboards"
-#    scores: "Scores"
-#    top_players: "Top Players by"
-#    day: "Today"
-#    week: "This Week"
-#    all: "All-Time"
-#    time: "Time"
-#    damage_taken: "Damage Taken"
-#    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
-
-#  inventory:
-#    choose_inventory: "Equip Items"
-#    equipped_item: "Equipped"
-#    required_purchase_title: "Required"
-#    available_item: "Available"
-#    restricted_title: "Restricted"
-#    should_equip: "(double-click to equip)"
-#    equipped: "(equipped)"
-#    locked: "(locked)"
-#    restricted: "(restricted in this level)"
-#    equip: "Equip"
-#    unequip: "Unequip"
-
-#  buy_gems:
-#    few_gems: "A few gems"
-#    pile_gems: "Pile of gems"
-#    chest_gems: "Chest of gems"
-#    purchasing: "Purchasing..."
-#    declined: "Your card was declined"
-#    retrying: "Server error, retrying."
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Do you want to get more?"
-#    prompt_button: "Enter Shop"
-#    recovered: "Previous gems purchase recovered. Please refresh the page."
-#    price: "x3500 / mo"
+    scores: "Σκορ"
+    top_players: "Κορυφαίοι Παίκτες Κατά"
+    day: "Σήμερα"
+    week: "Αυτήν την εβδομάδα"
+    all: "Παντοτινά"
+    time: "Χρόνος"
+    damage_taken: "Ζημιά που Δέχθηκες"
+    damage_dealt: "Ζημιά που αντιμετώπησες"
+    difficulty: "δυσκολία"
+    gold_collected: "Χρυσός που μαζεύτηκε"
+
+  inventory:
+    choose_inventory: "Εξοπλισμός"
+    equipped_item: "εξοπλισμένος"
+    required_purchase_title: "Απαιτείται"
+    available_item: "Διαθέσιμα"
+    restricted_title: "Μη διαθέσιμο"
+    should_equip: "(διπλό κλικ για να εξοπλιστείς με αυτό)"
+    equipped: "(εξοπλισμένο με αυτό)"
+    locked: "(κλειδωμένο)"
+    restricted: "(Μη διαθέσιμο σε αυτό το επίπεδο)"
+    equip: "Εξοπλίσου"
+    unequip: "Βάλτο στην άκρη"
+
+  buy_gems:
+    few_gems: "Λίγα πετράδια"
+    pile_gems: "Σωρός από πετράδια"
+    chest_gems: "Σεντούκι με πετράδια"
+    purchasing: "Αγορά..."
+    declined: "Η κάρτυα σας δεν έγινε αποδεκτή"
+    retrying: "Σφάλμα εξυπηρετητή, προσπαθώ ξανά."
+    prompt_title: "Τα πετράδια δεν αρκούν"
+    prompt_body: "Θέλεις να πάρεις περισσότερα;"
+    prompt_button: "Μπες στο κατάστημα"
+    recovered: "Ανακτήθηκε η προηγούμενη αγορά πετραδιών. παρακαλώ ανανεώστε τη σελίδα."
+    price: "x3500 / μήνα"

 #  subscribe:
 #    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
@@ -535,7 +535,7 @@
 #    action_name: "name"
 #    action_cooldown: "Takes"
 #    action_specific_cooldown: "Cooldown"
-#    action_damage: "Damage"
+#  οaction_damage: "Damage"
 #    action_range: "Range"
 #    action_radius: "Radius"
 #    action_duration: "Duration"


Update fr.coffee v1.0 (#5124)

Update fr.coffee v1.0 (#5124)

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -1283,9 +1283,9 @@

   courses:
     create_new_class: "Créer un nouveau cours"
-#    hoc_blurb1: "Try the"
-#    hoc_blurb2: "Code, Play, Share"
-#    hoc_blurb3: "activity! Construct four different minigames to learn the basics of game development, then make your own!"
+    hoc_blurb1: "Essayez le"
+    hoc_blurb2: "Code, Jouer, Partager"
+    hoc_blurb3: "activité ! Construisez quatre mini-jeux différents pour apprendre les bases du développement de jeux, puis créez le vôtre !"
     solutions_require_licenses: "Les solutions des niveaus sont disponibles pour les professeurs qui ont des licences."
     unnamed_class: "Classe sans nom"
     edit_settings1: "Modifier les Réglages de la Classe"
@@ -2248,43 +2248,43 @@
     basic_syntax: "Syntaxe basique"
     binary: "Binaire"
     boolean_and: "Opérateur booléen ET"
-#    boolean_inequality: "Boolean Inequality"
+    boolean_inequality: "Inégalité booléenne"
     boolean_equality: "Opérateur booléen EGAL"
     boolean_greater_less: "Opérateur booléen PLUS GRAND/PLUS PETIT"
-#    boolean_logic_shortcircuit: "Boolean Logic Shortcircuiting"
+    boolean_logic_shortcircuit: "Raccourci logique booléen"
     boolean_not: "Opérateur booléen NON"
-#    boolean_operator_precedence: "Boolean Operator Precedence"
+    boolean_operator_precedence: "Priorité de l'opérateur booléen"
     boolean_or: "Opérateur booléen OU"
-#    boolean_with_xycoordinates: "Coordinate Comparison"
+    boolean_with_xycoordinates: "Comparaison de coordonnées"
     bootstrap: "Bootstrap"
     break_statements: "Déclarations de sortie"
     classes: "Classes"
     continue_statements: "Instructions Continuer"
     dom_events: "Événements du DOM"
-#    dynamic_styling: "Dynamic Styling"
-#    events: "Events"
-#    event_concurrency: "Event Concurrency"
-#    event_data: "Event Data"
-#    event_handlers: "Event Handlers"
+    dynamic_styling: "Dynamic style"
+    events: "Événements"
+    event_concurrency: "Concurrence des événements"
+    event_data: "Données de l'événement"
+    event_handlers: "Gestionnaires d'événements"
 #    event_spawn: "Spawn Event"
     for_loops: "Boucles Pour"
 #    for_loops_nested: "Nested For Loops"
-#    for_loops_range: "For Loops Range"
+    for_loops_range: "Pour la gamme des boucles"
     functions: "Fonctions"
     functions_parameters: "Paramètres"
-#    functions_multiple_parameters: "Multiple Parameters"
+    functions_multiple_parameters: "Paramètres multiples"
     game_ai: "IA du jeu"
     game_goals: "Objectifs du jeu"
     game_spawn: "Point de départ du jeu"
     graphics: "Graphiques"
     graphs: "Graphes"
     heaps: "Tas"
-#    if_condition: "Conditional If Statements"
-#    if_else_if: "If/Else If Statements"
-#    if_else_statements: "If/Else Statements"
+    if_condition: "Déclarations conditionnelles Si"
+    if_else_if: "Si/Sinon Déclarations Si"
+    if_else_statements: "Déclarations SI/Sinon"
     if_statements: "Déclarations conditionnelles Si"
 #    if_statements_nested: "Nested If Statements"
-#    indexing: "Array Indexes"
+    indexing: "Index des tableaux"
 #    input_handling_flags: "Input Handling - Flags"
 #    input_handling_keyboard: "Input Handling - Keyboard"
 #    input_handling_mouse: "Input Handling - Mouse"
@@ -2297,26 +2297,26 @@
     jquery_filtering: "jQuery Filtrage des éléments"
     jquery_selectors: "Sélecteurs jQuery"
 #    length: "Array Length"
-#    math_coordinates: "Coordinate Math"
+    math_coordinates: "Coordonner les mathématiques"
     math_geometry: "Géométrie"
     math_operations: "Opérations mathématiques"
-#    math_proportions: "Proportion Math"
+    math_proportions: "Proportion Mathématiques"
     math_trigonometry: "Trigonométrie"
     object_literals: "Objets littéraux"
     parameters: "Paramètres"
-#    programs: "Programs"
-#    properties: "Properties"
-#    property_access: "Accessing Properties"
-#    property_assignment: "Assigning Properties"
-#    property_coordinate: "Coordinate Property"
-#    queues: "Data Structures - Queues"
+    programs: "Programmes"
+    properties: "Propriétés"
+    property_access: "Accès aux propriétés"
+    property_assignment: "Affectation des propriétés"
+    property_coordinate: "Coordonner la propriété"
+    queues: "Structures de données - Files d'attente"
     reading_docs: "Lire la docs"
     recursion: "Récursivité"
     return_statements: "Relevés de retour"
-#    stacks: "Data Structures - Stacks"
+    stacks: "Structures de données - Piles"
     strings: "Chaînes de caractères"
-#    strings_concatenation: "String Concatenation"
-#    strings_substrings: "Substring"
+    strings_concatenation: "Concaténation de cordes"
+    strings_substrings: "Sous-chaîne"
     trees: "Structures de données - Arbres"
     variables: "Variables"
     vectors: "Vecteurs"


Run copy-i18n-tags (#6217)

Run copy-i18n-tags (#6217)

Co-authored-by: CoCo CI <5df1af3d7600b97ee69552caffeccf9dfd29ab8f@codecombat.com>

--- app/locale/pl.coffee
+++ app/locale/pl.coffee
@@ -11,13 +11,13 @@
 #    built_for_teachers_subtitle1: "Computer Science"
 #    built_for_teachers_subblurb1: "Starting with our free Introduction to Computer Science course, students master core coding concepts such as while/for loops, functions, and algorithms."
     built_for_teachers_subtitle2: "Tworzenie gier"
-#    built_for_teachers_subblurb2: "Studenci tworzą labirynty i używają klawiatury, aby zaprogramować własne gry i dzielić się nimi ze znajomymi i rodziną."
+#    built_for_teachers_subblurb2: "Learners construct mazes and use basic input handling to code their own games that can be shared with friends and family."
     built_for_teachers_subtitle3: "Tworzenie stron internetowych"
     built_for_teachers_subblurb3: "Używając HTML, CSS i jQuery, uczniowie wykazują się kreatywnością abt stworzyć ich własne strony internetowe z ustalonym adresem URL, który mogą udostępnić go swoim kolegom/koleżankom z klasy."
-#    century_skills_title: "Umiejętności 21-ego wieku"
+#    century_skills_title: "21st Century Skills"
 #    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
-#    century_skills_subtitle1: "Myślenie krytyczne"
+#    century_skills_subtitle1: "Critical Thinking"
 #    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
 #    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
 #    century_skills_subtitle2: "Creativity"
@@ -28,7 +28,7 @@
 #    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
 #    century_skills_subtitle4: "Communication"
 #    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
-#    classroom_in_box_title: "Staramy się, aby:"
+#    classroom_in_box_title: "We Strive To:"
 #    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
 #    classroom_in_box_blurb2: "Empower any educator to feel confident when teaching coding."
 #    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
@@ -43,7 +43,7 @@
 #    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
 #    featured_partners_title1: "Featured In"
-#    featured_partners_title2: "Nagrody i Partnerzy"
+#    featured_partners_title2: "Awards & Partners"
 #    featured_partners_blurb1: "Clever Partner"
 #    featured_partners_blurb2: "Best Creativity Tool for Students"
 #    featured_partners_blurb3: "Top Pick for Learning"


delete works better

delete works better

--- lineman/app/js/services/message_channel_service.coffee
+++ lineman/app/js/services/message_channel_service.coffee
@@ -15,7 +15,7 @@
       if data.memo?
         console.log 'new memo!', data.memo
         memo = data.memo
-        if memo.kind == 'CommentDestroyed'
+        if memo.kind == 'comment_destroyed'
           if comment = RecordStoreService.get('comments', memo.comment_id)
             comment.destroy()


Update ru.coffee

Update ru.coffee

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -284,8 +284,8 @@
     write_this_down: "Запишите это:"
     start_playing: "Начать играть!"
     sso_connected: "Успешно подключились с помощью:"
-    select_your_starting_hero: "Выберите своего стартового персонажа"
-    you_can_always_change_your_hero_later: "Вы всегда можете поменять Вашего стартового персонажа"
+    select_your_starting_hero: "Выберите своего стартового Hero:"
+    you_can_always_change_your_hero_later: "Вы всегда можете поменять вашего стартового Hero позже."

   recover:
     recover_account_title: "Восстановить учетную запись"
@@ -445,7 +445,7 @@
     victory_experience_gained: "Опыта получено"
     victory_gems_gained: "Самоцветов получено"
     victory_new_item: "Новый предмет"
-    victory_new_hero: "Новый персонаж"
+    victory_new_hero: "Новый Hero"
     victory_viking_code_school: "Ого, это было тяжелый уровень! Если вы еще не разработчик программ, вам стоит им стать. Вы только что ускорири принятие в Школу Викингов, где вы сможете поднять свои навыки на новый уровень и стать профессиональным веб-разработчиком за 14 недель."
     victory_become_a_viking: "Станьте Викингом"
     victory_no_progress_for_teachers: "Прогресс не сохраняется для учителей. Но, вы можете для себя добавить учетную запись ученика в свою классную комнату."


Fix up-popup main docs

Fix up-popup main docs

--- lib/assets/javascripts/unpoly/popup.js.coffee
+++ lib/assets/javascripts/unpoly/popup.js.coffee
@@ -7,7 +7,7 @@

 To open a popup, add an [`up-popup` attribute](/up-popup) to a link:

-    Show options
+    Show options

 When this link is clicked, Unpoly will request the path `/options` and extract
 an element matching the selector `.menu` from the response. The matching element


i18n Inst Dash pending tasks table

i18n Inst Dash pending tasks table

LMS-2357

--- lms/static/coffee/src/instructor_dashboard/util.coffee
+++ lms/static/coffee/src/instructor_dashboard/util.coffee
@@ -42,47 +42,74 @@
   columns = [
     id: 'task_type'
     field: 'task_type'
-    name: 'Task Type'
+    ###
+    Translators: a "Task" is a background process such as grading students or sending email
+    ###
+    name: gettext('Task Type')
     minWidth: 102
   ,
     id: 'task_input'
     field: 'task_input'
-    name: 'Task inputs'
+    ###
+    Translators: a "Task" is a background process such as grading students or sending email
+    ###
+    name: gettext('Task inputs')
     minWidth: 150
   ,
     id: 'task_id'
     field: 'task_id'
-    name: 'Task ID'
+    ###
+    Translators: a "Task" is a background process such as grading students or sending email
+    ###
+    name: gettext('Task ID')
     minWidth: 150
   ,
     id: 'requester'
     field: 'requester'
-    name: 'Requester'
+    ###
+    Translators: a "Requester" is a username that requested a task such as sending email
+    ###
+    name: gettext('Requester')
     minWidth: 80
   ,
     id: 'created'
     field: 'created'
-    name: 'Submitted'
+    ###
+    Translators: A timestamp of when a task (eg, sending email) was submitted appears after this
+    ###
+    name: gettext('Submitted')
     minWidth: 120
   ,
     id: 'duration_sec'
     field: 'duration_sec'
-    name: 'Duration (sec)'
+    ###
+    Translators: The length of a task (eg, sending email) in seconds appears this
+    ###
+    name: gettext('Duration (sec)')
     minWidth: 80
   ,
     id: 'task_state'
     field: 'task_state'
-    name: 'State'
+    ###
+    Translators: The state (eg, "In progress") of a task (eg, sending email) appears after this.
+    ###
+    name: gettext('State')
     minWidth: 80
   ,
     id: 'status'
     field: 'status'
-    name: 'Task Status'
+    ###
+    Translators: a "Task" is a background process such as grading students or sending email
+    ###
+    name: gettext('Task Status')
     minWidth: 80
   ,
     id: 'task_message'
     field: 'task_message'
-    name: 'Task Progress'
+    ###
+    Translators: a "Task" is a background process such as grading students or sending email
+    ###
+    name: gettext('Task Progress')
     minWidth: 120
   ]


Implement a canDo method for capability checks

--- src/runtimes/base.coffee
+++ src/runtimes/base.coffee
@@ -2,12 +2,16 @@

 class BaseRuntime extends EventEmitter
   constructor: (@definition) ->
+    @definition.capabilities = [] unless definition.capabilities
     @graph = null

   setMain: (@graph) ->

   getType: -> @definition.protocol
   getAddress: -> @definition.address
+
+  canDo: (capability) ->
+    @definition.capabilities.indexOf(capability) isnt -1

   isConnected: -> false

@@ -41,6 +45,9 @@
   getElement: ->

   recvRuntime: (command, payload) ->
+    if command is 'runtime'
+      for key, val of payload
+        @definition[key] = val
     @emit 'runtime',
       command: command
       payload: payload


Mobile input mode is no more

Mobile input mode is no more

--- test/src/test_helpers.coffee
+++ test/src/test_helpers.coffee
@@ -1,6 +1,4 @@
 #= require trix/core/helpers/global
-
-Trix.config.useMobileInputMode = -> false

 keyCodes =
   left: 37


added response to let the user know something happened.

--- src/scripts/email.coffee
+++ src/scripts/email.coffee
@@ -30,3 +30,4 @@

   robot.respond /email (.*) -s (.*) -m (.*)/i, (msg) ->
     sendEmail msg.match[1], msg.match[2], msg.match[3]
+    msg.send "Email Sent"


Tested unit tests

Tested unit tests

--- test/unit/reportsTest.coffee
+++ test/unit/reportsTest.coffee
@@ -205,14 +205,16 @@

   describe "Reports", ->
     it "should return a channel Report", (done) ->
-      reports.fetchChannelReport channel2, testUser1, 'dailyReport', (item) ->
+      from = moment().subtract(1, 'days').startOf('day').toDate()
+      to = moment().subtract(1, 'days').endOf('day').toDate()
+      reports.fetchChannelReport channel2, testUser1, 'dailyReport', from, to, (item) ->
         item.data[0].should.have.property 'load', 1
         item.data[0].should.have.property 'avgResp', 1000
         item.statusData[0].should.have.property 'completed', 1
         done()

     it "should send a  weekly channel report", (done) ->
-      sinon.spy(reports, 'fetchWeeklySubscribers');
+      sinon.spy(reports, 'fetchWeeklySubscribers')
       reports.sendReports {}, 'weeklyReport', () ->
         reports.fetchWeeklySubscribers.should.be.called
         reports.fetchChannelReport.should.be.called
@@ -221,7 +223,7 @@
       done()

     it "should send a  daily channel report", (done) ->
-      sinon.spy(reports, 'fetchDailySubscribers');
+      sinon.spy(reports, 'fetchDailySubscribers')
       reports.sendReports {}, 'weeklyReport', () ->
         reports.fetchDailySubscribers.should.be.called
         reports.fetchChannelReport.should.be.called


Make SetIndex fire array events instead of splats.

--- src/set/set_index.coffee
+++ src/set/set_index.coffee
@@ -13,9 +13,9 @@
       @_setObserver = new Batman.SetObserver(@base)
       @_setObserver.observedItemKeys = [@key]
       @_setObserver.observerForItemAndKey = @observerForItemAndKey.bind(@)
-      @_setObserver.on 'itemsWereAdded', (items...) =>
+      @_setObserver.on 'itemsWereAdded', (items) =>
         @_addItem(item) for item in items
-      @_setObserver.on 'itemsWereRemoved', (items...) =>
+      @_setObserver.on 'itemsWereRemoved', (items) =>
         @_removeItem(item) for item in items
     @base.forEach @_addItem.bind(@)
     @startObserving()


better instantiation for some collections

--- shared/idle/IdlePlayerController.coffee
+++ shared/idle/IdlePlayerController.coffee
@@ -4,8 +4,6 @@
     '$scope', '$stateParams', '$sce', '$collection', '$subscribe', 'IdleCollections', 'PageTitle'
     ($scope, $stateParams, $sce, $collection, $subscribe, IdleCollections, PageTitle) =>

-      $subscribe.subscribe 'playerEvents', $stateParams.playerName
-      $subscribe.subscribe 'singlePlayer', $stateParams.playerName

       window.scrollTo 0,0

@@ -15,13 +13,17 @@

       PageTitle.setTitle "Idle Lands - #{$scope.playerName} (Stats)"

-      $collection IdleCollections.IdlePlayers, name: $stateParams.playerName
-      .bind $scope, 'player'
+      $subscribe.subscribe 'singlePlayer', $stateParams.playerName
+      .then ->
+        $collection IdleCollections.IdlePlayers, name: $stateParams.playerName
+        .bind $scope, 'player'

       $scope.playerEvents = []

-      $collection IdleCollections.IdlePlayerEvents
-      .bind $scope, 'playerEvents'
+      $subscribe.subscribe 'playerEvents', $stateParams.playerName
+      .then ->
+        $collection IdleCollections.IdlePlayerEvents
+        .bind $scope, 'playerEvents'

       $scope.getPlayerTagline = (player) ->
         player.messages?.web


add some logging in

add some logging in

--- metrics.coffee
+++ metrics.coffee
@@ -19,15 +19,19 @@
 	initialize: (_name) ->
 		appname = _name
 		collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey()})
-
+
+		logger.log("ENABLE_TRACE_AGENT set to #{process.env['ENABLE_TRACE_AGENT']}")
 		if process.env['ENABLE_TRACE_AGENT'] == "true"
+			logger.log("starting google trace agent")
 			traceAgent = require('@google-cloud/trace-agent')

 			traceOpts =
 				ignoreUrls: [/^\/status/, /^\/health_check/]
 			traceAgent.start(traceOpts)

+		logger.log("ENABLE_DEBUG_AGENT set to #{process.env['ENABLE_DEBUG_AGENT']}")
 		if process.env['ENABLE_DEBUG_AGENT'] == "true"
+			logger.log("starting google debug agent")
 			debugAgent = require('@google-cloud/debug-agent')
 			debugAgent.start({
 				serviceContext: {


Match http or https.

Match http or https.

--- app/assets/javascripts/active_admin/editor/core.js.coffee
+++ app/assets/javascripts/active_admin/editor/core.js.coffee
@@ -54,7 +54,7 @@
               populateSrc = (el) ->
                 scale = selectedScale()
                 container.find('.asset').removeClass('active')
-                domain = el.src.match(/http:\/\/[^/]*/gi)
+                domain = el.src.match(/(http|https):\/\/[^/]*/gi)
                 if scale == 'full'
                   setDialogInput 'src', domain + $(el).data('image-src').url
                 else


Fixed syntax error that was thrown when choosing a previously uploaded image.

--- app/assets/javascripts/active_admin/editor/core.js.coffee
+++ app/assets/javascripts/active_admin/editor/core.js.coffee
@@ -58,7 +58,7 @@
           image_dialog.find('.asset_scale_selection').show()

           selectedScale = ->
-            image_dialog.find('input[@name="asset_scale"]:checked').data('scale')
+            image_dialog.find('input[name="asset_scale"]:checked').data('scale')

           populateSrc = (el) ->
             scale = selectedScale()


Remove a FIXME that got fixed with data beta3.

--- app/assets/javascripts/all_aboard/routes/slides.js.coffee
+++ app/assets/javascripts/all_aboard/routes/slides.js.coffee
@@ -1,8 +1,5 @@
 AllAboard.SlidesRoute = Em.Route.extend
   model: ->
-    # FIXME: So this is great, except that it doesn't work on a Board that was
-    # created by the app while that same instance is still running.  It just
-    # sits and hangs resolving the model.
     @modelFor("board").get("slides")

   afterModel: (slides) ->


Implementation of inherited properties check

--- src/Overloadable.coffee
+++ src/Overloadable.coffee
@@ -181,14 +181,22 @@
     matcherFactory.registerMatcher "regexp", RegExpMatcher

 class PropertyMatcher extends AbstractMatcher
-    match: (argument, overloadSignatureElement) ->
-        for property of overloadSignatureElement
-            if property not of argument then return false
-
+    @_INHERITED_PROPERTY_PREFIX: "^"
+    match: (argument, overloadSignatureElement) ->
+        for own property of overloadSignatureElement
+            if property.charAt(0) is PropertyMatcher._INHERITED_PROPERTY_PREFIX
+                propertyName = property.slice(1)
+                hasProperty = propertyName of argument
+            else
+                propertyName = property
+                hasProperty = Object.prototype.hasOwnProperty.call(argument, propertyName)
+
+            if hasProperty isnt true then return false
+
             element = overloadSignatureElement[property]
             matcher = AbstractMatcher.getMatcher element
-            unless matcher.match(argument[property], element) then return false
-
+            unless matcher.match(argument[propertyName], element) then return false
+
         true

     matcherFactory.registerMatcher "object", PropertyMatcher


Same actual/expect order in Rework test

Same actual/expect order in Rework test

--- test/autoprefixer.coffee
+++ test/autoprefixer.coffee
@@ -88,11 +88,10 @@
       for type in commons
         instansce = prefixer(type)

-        ideal = cases.read('autoprefixer/' + type + '.out')
-        real  = rework(cases.read('autoprefixer/' + type)).
+        real = rework(cases.read('autoprefixer/' + type)).
           use(instansce.rework).
           toString()
-        compare(ideal, real)
+        compare(real, cases.read('autoprefixer/' + type + '.out'))

   describe 'inspect()', ->


Fix rare crash when sendsetpresence was throttled causing an updated outside action error.

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -69,7 +69,8 @@

 handle 'presence', (ev) ->
     entity.setLastSeen ev[0][0][0][0], Math.floor(ev[0][0][1][9] / 1000000)
-    entity.updatePresence()
+    later ->
+        action 'updatepresence'

 # handle 'self_presence', (ev) ->
 #     console.log 'self_presence', ev
@@ -193,10 +194,15 @@
 handle 'toggleclosetotray', ->
     viewstate.setCloseToTray(not viewstate.closetotray)

+handle 'updatepresence', ->
+    entity.updatePresence()
+
 sendsetpresence = throttle 10000, ->
     ipc.send 'setpresence'
     ipc.send 'setactiveclient', true, 15
-    entity.updatePresence()
+    later ->
+        action 'updatepresence'
+
 resendfocus = throttle 15000, -> ipc.send 'setfocus', viewstate.selectedConv

 # on every keep alive signal from hangouts


Add tests for new arbitrary marking

Add tests for new arbitrary marking

--- test/coffee/tests.coffee
+++ test/coffee/tests.coffee
@@ -742,3 +742,28 @@
     '''

     strictEqual editor.currentlyUsingBlocks, false
+
+  test 'Controller: arbitrary row/column marking', ->
+    document.getElementById('test-main').innerHTML = ''
+    editor = new droplet.Editor document.getElementById('test-main'), {
+      mode: 'coffeescript'
+      palette: []
+    }
+
+    editor.setEditorState true
+
+    editor.setValue '''
+    for [1..10]
+      fd 10 + 10
+      bk 10 - 10
+      fd 10 * 10
+      bk 10 / 10
+    '''
+
+    key = editor.mark 2, 4, {color: '#F00'}
+
+    strictEqual editor.markedBlocks[key].model.stringify(), '10 - 10'
+    strictEqual editor.markedBlocks[key].style.color, '#F00'
+
+    editor.unmark key
+    ok key not of editor.markedBlocks


:lipstick: Spread large regexp string on several lines

--- lib/expressions.coffee
+++ lib/expressions.coffee
@@ -306,7 +306,13 @@
 ##    ##        #######  ##    ##  ######

 # darken(#666666, 20%)
-registry.createExpression 'darken', "darken#{ps}(#{notQuote})#{comma}(#{percent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'darken', strip("
+  darken#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{percent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloat(amount)
@@ -320,7 +326,13 @@
   @alpha = baseColor.alpha

 # lighten(#666666, 20%)
-registry.createExpression 'lighten', "lighten#{ps}(#{notQuote})#{comma}(#{percent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'lighten', strip("
+  lighten#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{percent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloat(amount)
@@ -336,7 +348,13 @@
 # transparentize(#ffffff, 0.5)
 # transparentize(#ffffff, 50%)
 # fadein(#ffffff, 0.5)
-registry.createExpression 'transparentize', "(transparentize|fadein)#{ps}(#{notQuote})#{comma}(#{floatOrPercent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'transparentize', strip("
+  (transparentize|fadein)#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{floatOrPercent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, _, subexpr, amount] = match

   amount = context.readFloatOrPercent(amount)
@@ -350,7 +368,13 @@
 # opacify(0x78ffffff, 0.5)
 # opacify(0x78ffffff, 50%)
 # fadeout(0x78ffffff, 0.5)
-registry.createExpression 'opacify', "(opacify|fadeout)#{ps}(#{notQuote})#{comma}(#{floatOrPercent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'opacify', strip("
+  (opacify|fadeout)#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{floatOrPercent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, _, subexpr, amount] = match

   amount = context.readFloatOrPercent(amount)
@@ -362,7 +386,13 @@
   @alpha = clamp(baseColor.alpha + amount)

 # adjust-hue(#855, 60deg)
-registry.createExpression 'adjust-hue', "adjust-hue#{ps}(#{notQuote})#{comma}(-?#{int}deg|#{variables}|-?#{percent})#{pe}", (match, expression, context) ->
+registry.createExpression 'adjust-hue', strip("
+  adjust-hue#{ps}
+    (#{notQuote})
+    #{comma}
+    (-?#{int}deg|#{variables}|-?#{percent})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloat(amount)
@@ -377,7 +407,21 @@

 # mix(#f00, #00F, 25%)
 # mix(#f00, #00F)
-registry.createExpression 'mix', "mix#{ps}((#{notQuote})#{comma} (#{notQuote})#{comma}(#{floatOrPercent}|#{variables})|(#{notQuote})#{comma}(#{notQuote}))#{pe}", (match, expression, context) ->
+registry.createExpression 'mix', strip("
+  mix#{ps}
+    (
+      (#{notQuote})
+      #{comma}
+      (#{notQuote})
+      #{comma}
+      (#{floatOrPercent}|#{variables})
+    |
+      (#{notQuote})
+      #{comma}
+      (#{notQuote})
+    )
+  #{pe}
+"), (match, expression, context) ->
   [_, _, color1A, color2A, amount, _, _, color1B, color2B] = match

   if color1A?
@@ -398,7 +442,13 @@
   @rgba = mixColors(baseColor1, baseColor2, amount).rgba

 # tint(red, 50%)
-registry.createExpression 'tint', "tint#{ps}(#{notQuote})#{comma}(#{floatOrPercent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'tint', strip("
+  tint#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{floatOrPercent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloatOrPercent(amount)
@@ -411,7 +461,13 @@
   @rgba = mixColors(white, baseColor, amount).rgba

 # shade(red, 50%)
-registry.createExpression 'shade', "shade#{ps}(#{notQuote})#{comma}(#{floatOrPercent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'shade', strip("
+  shade#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{floatOrPercent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloatOrPercent(amount)
@@ -440,7 +496,13 @@

 # saturate(#855, 20%)
 # saturate(#855, 0.2)
-registry.createExpression 'saturate', "saturate#{ps}(#{notQuote})#{comma}(#{floatOrPercent}|#{variables})#{pe}", (match, expression, context) ->
+registry.createExpression 'saturate', strip("
+  saturate#{ps}
+    (#{notQuote})
+    #{comma}
+    (#{floatOrPercent}|#{variables})
+  #{pe}
+"), (match, expression, context) ->
   [_, subexpr, amount] = match

   amount = context.readFloatOrPercent(amount)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -5,6 +5,6 @@
     x: 10
     y: 20
     width: 10
-
+    height:

 module()


fixed issues with non-default layouts

fixed issues with non-default layouts