Add TODO comments, and alter function ordering

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -54,7 +54,19 @@

     return true

+  sync: ->
+    {filePath, lineNumber} = @getEditorDetails()
+
+    unless outputFilePath = @resolveOutputFilePath(filePath)
+      unless atom.inSpecMode()
+        console.info 'Could not resolve path to output file associated with the current file.'
+      return
+
+    if opener = @getOpener()
+      opener.open(outputFilePath, filePath, lineNumber)
+
   # TODO: Improve overall code quality within this function.
+  # NOTE: Does not support `latex.outputDirectory` setting!
   clean: ->
     editor = atom.workspace.getActivePaneItem()
     filePath = editor?.getPath()
@@ -70,7 +82,9 @@
     rootFile.pop()
     rootFile = rootFile.join('.')

+    # TODO: This needs to be added to the config schema.
     cleanExtensions = atom.config.get('latex.cleanExtensions')
+    # NOTE: Use _.extend(..) or something similar here.
     unless cleanExtensions
       cleanExtensions = [
         '.aux'
@@ -84,6 +98,7 @@
         '.out'
       ]

+    # NOTE: This needs to be done async, and there's no point in being this noisy.
     for extension in cleanExtensions
       fileToRemove = path.join(rootFilePath, rootFile + extension)
       if fs.existsSync(fileToRemove)
@@ -91,29 +106,6 @@
         console.info 'LaTeX clean removed: ' + fileToRemove
       else
         console.info 'LaTeX clean did not find: ' + fileToRemove
-
-  sync: ->
-    {filePath, lineNumber} = @getEditorDetails()
-
-    unless outputFilePath = @resolveOutputFilePath(filePath)
-      unless atom.inSpecMode()
-        console.info 'Could not resolve path to output file associated with the current file.'
-      return
-
-    if opener = @getOpener()
-      opener.open(outputFilePath, filePath, lineNumber)
-
-  isTexFile: (filePath) ->
-    # TODO: Improve; will suffice for the time being.
-    return filePath?.search(/\.(tex|lhs)$/) > 0
-
-  getEditorDetails: ->
-    editor = atom.workspace.getActiveTextEditor()
-    return unless editor?
-
-    editorDetails =
-      filePath: editor.getPath()
-      lineNumber: editor.getCursorScreenPosition().row + 1

   getBuilder: ->
     new LatexmkBuilder()
@@ -166,10 +158,6 @@
     outputFilePath = @alterParentPath(rootFilePath, outputFilePath) if @shouldMoveResult()
     outputFilePath

-  alterParentPath: (targetPath, originalPath) ->
-    targetDir = path.dirname(targetPath)
-    path.join(targetDir, path.basename(originalPath))
-
   shouldMoveResult: ->
     atom.config.get('latex.moveResultToSourceDirectory')

@@ -227,3 +215,19 @@
   destroyErrorIndicator: ->
     @errorIndicator?.destroy()
     @errorIndicator = null
+
+  isTexFile: (filePath) ->
+    # TODO: Improve; will suffice for the time being.
+    return filePath?.search(/\.(tex|lhs)$/) > 0
+
+  getEditorDetails: ->
+    editor = atom.workspace.getActiveTextEditor()
+    return unless editor?
+
+    editorDetails =
+      filePath: editor.getPath()
+      lineNumber: editor.getCursorScreenPosition().row + 1
+
+  alterParentPath: (targetPath, originalPath) ->
+    targetDir = path.dirname(targetPath)
+    path.join(targetDir, path.basename(originalPath))


Improve error and warning logging somewhat

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -23,14 +23,12 @@
     editor = atom.workspace.getActivePaneItem()
     filePath = editor?.getPath()
     unless filePath?
-      unless atom.inSpecMode()
-        console.info 'File needs to be saved to disk before it can be TeXified.'
+      @logWarning('File needs to be saved to disk before it can be TeXified.')
       return false

     unless @isTexFile(filePath)
-      unless atom.inSpecMode()
-        extension = path.extname(filePath)
-        console.info "File does not seem to be a TeX file; unsupported extension '#{extension}'."
+      @logWarning("File does not seem to be a TeX file;
+        unsupported extension '#{path.extname(filePath)}'.")
       return false

     editor.save() if editor.isModified() # TODO: Make this configurable?
@@ -58,8 +56,7 @@
     {filePath, lineNumber} = @getEditorDetails()

     unless outputFilePath = @resolveOutputFilePath(filePath)
-      unless atom.inSpecMode()
-        console.info 'Could not resolve path to output file associated with the current file.'
+      @logWarning('Could not resolve path to output file associated with the current file.')
       return

     if opener = @getOpener()
@@ -69,11 +66,10 @@
   # NOTE: Does not support `latex.outputDirectory` setting!
   clean: ->
     editor = atom.workspace.getActivePaneItem()
-    filePath = editor?.getPath()
-    unless filePath?
-      unless atom.inSpecMode()
-        console.info 'File needs to be saved to disk before clean can find the project files.'
+    unless filePath = editor?.getPath()
+      @logWarning('File needs to be saved to disk before clean can find the project files.')
       return
+
     rootFilePath = @resolveRootFilePath(filePath)
     rootFile = path.basename(rootFilePath)
     rootFilePath = path.dirname(rootFilePath)
@@ -125,8 +121,8 @@
       if atom.packages.resolvePackagePath('pdf-view')?
         OpenerImpl = require './openers/atompdf-opener'
       else
-        console.info 'No PDF opener found. For cross-platform viewing,
-          install the pdf-view package.' unless atom.inSpecMode()
+        @logWarning('No PDF opener found. For cross-platform viewing,
+          install the pdf-view package.')
         return
     return new OpenerImpl()

@@ -150,7 +146,7 @@
       builder = @getBuilder()
       result = builder.parseLogFile(rootFilePath)
       unless outputFilePath = result?.outputFilePath
-        console.info 'Log file parsing failed!' unless atom.inSpecMode()
+        @logWarning('Log file parsing failed!')
         return
       @outputLookup ?= {}
       @outputLookup[filePath] = outputFilePath
@@ -165,9 +161,34 @@

   showError: (statusCode, result, builder) ->
     @showErrorIndicator()
+    @logError(statusCode, result, builder)
+
+  showProgressIndicator: ->
+    return @indicator if @indicator?
+
+    @indicator = new ProgressIndicatorView()
+    @statusBar?.addRightTile({item: @indicator, priority: 9001})
+    @indicator
+
+  showErrorIndicator: ->
+    return @errorIndicator if @errorIndicator?
+
+    @errorIndicator = new ErrorIndicatorView()
+    @statusBar?.addRightTile({item: @errorIndicator, priority: 9001})
+    @errorIndicator
+
+  destroyProgressIndicator: ->
+    @indicator?.destroy()
+    @indicator = null
+
+  destroyErrorIndicator: ->
+    @errorIndicator?.destroy()
+    @errorIndicator = null
+
+  logError: (statusCode, result, builder) ->
     return if atom.inSpecMode()

-    console.group('LaTeX')
+    console.group('LaTeX errors')
     switch statusCode
       when 127
         console.log(
@@ -188,27 +209,12 @@
         console.groupEnd()
     console.groupEnd()

-  showProgressIndicator: ->
-    return @indicator if @indicator?
-
-    @indicator = new ProgressIndicatorView()
-    @statusBar?.addRightTile({item: @indicator, priority: 9001})
-    @indicator
-
-  showErrorIndicator: ->
-    return @errorIndicator if @errorIndicator?
-
-    @errorIndicator = new ErrorIndicatorView()
-    @statusBar?.addRightTile({item: @errorIndicator, priority: 9001})
-    @errorIndicator
-
-  destroyProgressIndicator: ->
-    @indicator?.destroy()
-    @indicator = null
-
-  destroyErrorIndicator: ->
-    @errorIndicator?.destroy()
-    @errorIndicator = null
+  logWarning: (message) ->
+    return if atom.inSpecMode()
+
+    console.group('LaTeX warnings')
+    console.log(message)
+    console.groupEnd()

   isTexFile: (filePath) ->
     # TODO: Improve; will suffice for the time being.


Use less expensive endpoints for getting repos with last builds

We display a few recently built repos on landing page. Till now it was
using quite expensive endpoint "/repos". This commit changes it to get
last builds from builds endpoint and then fetch repositories by ids.

--- app/routes/home.coffee
+++ app/routes/home.coffee
@@ -28,8 +28,10 @@

     setTimeout =>
       unless repos.get('length')
-        @store.find('repo').then (reposFromRequest) ->
-          repos.get('external').pushObjects reposFromRequest.toArray().slice(0, 3)
+        @store.find('build').then (builds) =>
+          repoIds = builds.mapBy('data.repo').uniq().slice(0, 3)
+          @store.find('repo', ids: repoIds).then (reposFromRequest) ->
+            repos.get('external').pushObjects reposFromRequest.toArray()
     , 10000

     @_super.apply this, arguments


Fixed typos in browser validation.

Fixed typos in browser validation.

--- app/assets/javascripts/app/lib/app_post/browser.coffee
+++ app/assets/javascripts/app/lib/app_post/browser.coffee
@@ -24,12 +24,12 @@
       return false

     # disable Safari 3 and older
-    else if data.browser == 'Firefox' && data.version <= 3
+    else if data.browser == 'Safari' && data.version <= 3
       @message(data)
       return false

     # disable Opera 10 and older
-    else if data.browser == 'Firefox' && data.version <= 10
+    else if data.browser == 'Opera' && data.version <= 10
       @message(data)
       return false


Set node caption to first attribute if no style.

If no grass style that match a node with
attributes specifies which caption to use,
the first one from the attribute list will be
chosen. If no attributes, db id it is.

--- community/browser/app/scripts/services/GraphStyle.coffee
+++ community/browser/app/scripts/services/GraphStyle.coffee
@@ -32,7 +32,6 @@
         'border-color': '#9AA1AC'
         'border-width': '2px'
         'text-color-internal': '#FFFFFF'
-        'caption': '{id}'
         'font-size': '10px'
       'relationship':
         'color': '#A5ABB6'
@@ -143,8 +142,8 @@
       forNode: (node = {}, idx = 0) ->
         selector = @nodeSelector(node, idx)
         if node.labels?.length > 0
-          @setDefaultStyling(selector)
-        @calculateStyle(selector, node)
+          @setDefaultStyling(selector, node)
+        @calculateStyle(selector)

       forRelationship: (rel) ->
         @calculateStyle(@relationshipSelector(rel))
@@ -161,13 +160,24 @@

         return provider.defaultColors[0]

-      setDefaultStyling: (selector) ->
+      setDefaultStyling: (selector, item) ->
         rule = @findRule(selector)

         if not rule?
-          rule = new StyleRule(selector, @findAvailableDefaultColor())
+          rule = new StyleRule(selector, angular.extend(@findAvailableDefaultColor(), @getDefaultCaption(item)))
           @rules.push(rule)
           @persist()
+        if not rule.props.caption?
+          default_caption = @getDefaultCaption(item)
+          return unless default_caption.caption?
+          angular.extend(rule.props, default_caption)
+          @rules.push(rule)
+          @persist()
+
+      getDefaultCaption: (item) ->
+        return {caption: '{id}'} if not item or not item.propertyList?.length > 0
+        default_caption = {caption: "{#{item.propertyList?[0].key}}"}
+        default_caption

       #
       # Methods for getting and modifying rules


Preload more content.

Preload more content.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -1268,12 +1268,12 @@

     preloadAssets: ->
       $(document).on 'click', '[data-behavior~=show_entry_content]', ->
-        selected = $(@).parents('li')
-        next = selected.next('li')
-        if next.length > 0
-          id = next.data('entry-id')
+        selected = $(@).closest('li')
+        siblings = selected.nextAll().slice(0,3).add(selected.prevAll().slice(0,3))
+        siblings.each ->
+          id = $(@).data('entry-id')
+          console.log id
           feedbin.preloadAssets(id)
-        return

     entriesLoading: ->
       $(document).on 'click', '[data-behavior~=feed_link]', (event) ->


Updated the api

Updated the api

--- dev/api/controllers/api.coffee
+++ dev/api/controllers/api.coffee
@@ -1,8 +1,9 @@
 'use strict'

 module.exports = (app) ->
-
     app.get '/api/', (req, res, next) ->
         res.status(200).send {
             code: 'SUCCESS'
         }
+        return
+    return


update workflow-methods-mixin.cjsx to add initials to comment for consistency

--- app/assets/javascripts/lib/workflow-methods-mixin.cjsx
+++ app/assets/javascripts/lib/workflow-methods-mixin.cjsx
@@ -355,7 +355,7 @@

     # If we've exhausted pages in this subject set, move to next one:
     # NB: By commenting out the following line we can ensure user is always sent to the
-    # next subject set after clicking the big green Done button in the Mark stage
+    # next subject set after clicking the big green Done button in the Mark stage -DD
     #if new_subject_index >= @getCurrentSubjectSet().subjects.length
     new_subject_set_index += 1
     new_subject_index = 0


dataSelectionChanged: Pass in seriesColors, so we know which color the series have.

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -224,7 +224,7 @@
     @element.addClass "#{@options.className}_loading-details"
     @rangeStart = start
     @rangeEnd = end
-    @options.dataSelectionChanged(start, end, @getActiveSeriesNames())
+    @options.dataSelectionChanged(start, end, @getActiveSeriesNames(), @seriesColors)
     $.ajax
       dataType: "jsonp"
       url: "#{@url}"
@@ -246,7 +246,7 @@
     @legend = new PG.Legend @legendContainer, @graphs, {
       onToggle: (seriesStates) =>
         @seriesStates = seriesStates
-        @options.dataSelectionChanged(@rangeStart, @rangeEnd, @getActiveSeriesNames())
+        @options.dataSelectionChanged(@rangeStart, @rangeEnd, @getActiveSeriesNames(), @seriesColors)
     }

   updateSeries: =>
@@ -293,4 +293,4 @@
         @renderOverviewGraph(series)
         @updateSeries()
         @updateLegend() if @options.legend
-        @options.dataSelectionChanged @rangeStart, @rangeEnd, @getActiveSeriesNames()
+        @options.dataSelectionChanged @rangeStart, @rangeEnd, @getActiveSeriesNames(), @seriesColors


fix 'postData not defined'

fix 'postData not defined'

--- app/assets/javascripts/qiniu_direct_uploader.js.coffee
+++ app/assets/javascripts/qiniu_direct_uploader.js.coffee
@@ -100,7 +100,7 @@
         content.errorThrown = data.errorThrown

         data.context.remove() if data.context && settings.removeProgressBarWhenFailed # remove progress bar
-        $uploadForm.trigger("qiniu_upload_failed", [postData])
+        $uploadForm.trigger("qiniu_upload_failed", [content])

       formData: (form) ->
         data = form.serializeArray()


changed bikebook url to Heroku, to take advantage of free SSL

--- app/assets/javascripts/views/bikes/bikes_new.js.coffee
+++ app/assets/javascripts/views/bikes/bikes_new.js.coffee
@@ -57,7 +57,8 @@
     mnfg = $('#bike_manufacturer_id option:selected').text()
     unless mnfg == "Choose manufacturer"
       year = parseInt($('#bike_year').val(),10)
-      url = "http://bikebook.io/model_list/?manufacturer=#{mnfg}"
+      # could be bikebook.io - but then we'd have to pay for SSL...
+      url = "https://bikebook.herokuapp.com//model_list/?manufacturer=#{mnfg}"
       url += "&year=#{year}" if year > 1
       that = @
       $.ajax
@@ -67,7 +68,6 @@
           that.setModelTypeahead(data)
         error: ->
           that.setModelTypeahead()
-

   updateManufacturer: ->


Made you able to provide callbacks to the sessions dialog.

--- app/assets/javascripts/views/sessions/dialog.js.coffee
+++ app/assets/javascripts/views/sessions/dialog.js.coffee
@@ -15,10 +15,14 @@
     args = {} unless args
     @state = args.state || "login"

+    @callback = args.callback if args.callback
+    @callbackArgs = args.callbackArgs if args.callbackArgs
+
     @session = window.session
     @user = @session.get('user')

     @logoutOnHide = false
+    @callbackOnHide = false

     @render()
     @toggleStateFromAction(@state)
@@ -33,6 +37,7 @@
       @.$('.input-group').tooltip('hide')
       window.setTimeout =>
         @logoutUser() if @logoutOnHide
+        @callback(@callbackArgs) if @callback && @callbackOnHide
         $(@el).remove()
       , 500

@@ -110,8 +115,10 @@

   hide: (args) =>
     args = {} unless args
+
     @logoutOnHide = args.logout if args.logout != undefined
-
+    @callbackOnHide = args.callback if args.callback != undefined
+
     @.$('.modal').modal('hide')
     false

@@ -146,7 +153,7 @@
       data: submitData
       dataType: "json"
       success: (response) =>
-        @hide(logout: false)
+        @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
         @.$('.input-group').tooltip(
@@ -176,7 +183,7 @@
         userData = response.result
         session.get('user').set(userData)
         session.reInitializeClouds()
-        @hide(logout: false)
+        @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)

@@ -212,7 +219,7 @@
         userData = response.result.user
         session.get('user').set(userData)
         session.reInitializeClouds()
-        @hide(logout: false)
+        @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)

@@ -243,7 +250,7 @@
       success: (response) =>
         userData = response.user
         session.get('user').set(userData)
-        @hide(logout: false)
+        @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)


redirect to scores page after running an update

Hours of sleep: 25
Cups of coffee: 0
Cans of soda: 0
Cans of redbull: 0

--- controllers/index.coffee
+++ controllers/index.coffee
@@ -49,9 +49,10 @@
     return next error if error
     res.render2 'index/scores', teams: teams

-app.get '/scores/update', (req, res) ->
+app.get '/scores/update', (req, res, next) ->
   Team.updateAllSavedScores (err) ->
-    res.end err or 'ok'
+    next err if err
+    res.redirect '/scores'

 app.get '/services', [m.ensureAuth], (req, res, next) ->
   return next 401 unless req.user.contestant or req.user.judge or req.user.admin


Poll dat API

Poll dat API

--- app/assets/javascripts/ideas.js.coffee
+++ app/assets/javascripts/ideas.js.coffee
@@ -43,13 +43,16 @@
       complete : (xhr, status) ->

   do poll = ->
-    $.ajax '/ideas/similar',
-      data :
-        idea : 'hello'
-      success  : (res, status, xhr) ->
+    setTimeout poll, 10000

-      error    : (xhr, status, err) ->
-        alert "There was a problem with the idea preview"
-      complete : (xhr, status) ->
+    md_title = $('#idea_title').val()
+    md_body = $('#idea-body').val()

-    setTimeout poll, 5000
+    if (md_title or md_body)
+      $.ajax '/ideas/similar',
+        data :
+          idea : md_title + " " + md_body
+        success  : (res, status, xhr) ->
+          $('#similar-results').html(res)
+        error    : (xhr, status, err) ->
+        complete : (xhr, status) ->


No longer need this check here.

No longer need this check here.

--- js/flight.coffee
+++ js/flight.coffee
@@ -102,9 +102,6 @@

   # performs slide animation transition
   slideTransition: (speed) ->
-    unless @targetPanel
-      throw new Error "Need to set current div and target div in Slide in flight.slideTranstion"
-      return

     if speed
       @speed = speed


OX-180 deleted all task options from datGUI

--- app/assets/javascripts/view/gui.coffee
+++ app/assets/javascripts/view/gui.coffee
@@ -2,7 +2,6 @@
 libs/datgui/dat.gui : DatGui
 libs/request : Request
 libs/event_mixin : EventMixin
-routes : routes
 view/toast : Toast
 ###

@@ -28,9 +27,6 @@
     # create GUI
     modelRadius = @model.route.getActiveNodeRadius()
     @settings =
-      upload : @uploadNML
-      download : => window.open(routes.controllers.admin.NMLIO.downloadList().url,
-                                "_blank", "width=700,height=400,location=no,menubar=no")

       position : "#{initPos[0]}, #{initPos[1]}, #{initPos[2]}"
       lockZoom: data.lockZoom
@@ -61,12 +57,6 @@
     @gui = new dat.GUI(autoPlace: false, width : 280, hideable : false, closed : true)

     container.append @gui.domElement
-
-    fTask = @gui.addFolder("Task")
-    (fTask.add @settings, "upload")
-                          .name("Upload NML")
-    (fTask.add @settings, "download")
-                          .name("Download NML")

     fPosition = @gui.addFolder("Position")
     (fPosition.add @settings, "position")
@@ -146,7 +136,6 @@
     (fNodes.add @settings, "deleteActiveNode")
                           .name("Delete Active Node")

-    fTask.open()
     fPosition.open()
     #fControls.open()
     #fView.open()
@@ -167,20 +156,6 @@
     pos = [parseInt(stringArray[0]), parseInt(stringArray[1]), parseInt(stringArray[2])]
     @flycam.setGlobalPos(pos)

-  uploadNML : =>
-    # Create dummy input field
-    input = $("<input>", type : "file")
-    input.trigger("click")
-    input.on("change", (evt) ->
-      file = evt.target.files[0]
-      Request.send(
-        _.extend(routes.controllers.admin.NMLIO.upload(),
-          formData :
-            nmlFile : file
-        )
-      ).done -> window.location.reload()
-    )
-
   updateGlobalPosition : =>
     pos = @flycam.getGlobalPos()
     @settings.position = Math.round(pos[0]) + ", " + Math.round(pos[1]) + ", " + Math.round(pos[2])


fix #3714 - backlog when the user open & close filters

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -235,8 +235,8 @@
         @scope.oldSelectedStatuses = selectedStatuses

         @scope.filtersQOld = @scope.filtersQ
-        @scope.filtersQ = ""
-        @.replaceFilter("q", null)
+        @scope.filtersQ = undefined
+        @.replaceFilter("q", @scope.filtersQ)

         _.each [selectedTags, selectedStatuses], (filterGrp) =>
             _.each filterGrp, (item) =>


Throw errors for unsupported things.

Throw errors for unsupported things.

--- js2coffee.coffee
+++ js2coffee.coffee
@@ -31,12 +31,14 @@
 ###

 js2coffee.parse = (source, options = {}) ->
+  options.filename ?= 'input.js'
+
   try
     ast = Esprima.parse(source, loc: true, range: true, comment: true)
   catch err
-    throw buildError(err, source, options.filename or 'input.js')
-
-  builder = new Builder(ast, options)
+    throw buildError(err, source, options.filename)
+
+  builder = new Builder(ast, options, source)
   {code, map} = builder.get()

   {code, ast, map}
@@ -52,7 +54,7 @@

 class Builder extends Walker

-  constructor: ->
+  constructor: (ast, options={}, @source) ->
     super
     @_indent = 0

@@ -115,7 +117,7 @@
     new SourceNode(
       node.loc.start.line,
       node.loc.start.column,
-      'input.js',
+      @options.filename,
       output)

   ###
@@ -124,8 +126,12 @@
   ###

   onUnknownNode: (node, ctx) ->
-    console.error node
-    throw new Error("walk(): No handler for #{ctx?.type}")
+    err = buildError({
+      lineNumber: node.loc.start.line,
+      column: node.loc.start.column,
+      description: "#{ctx?.type} is not supported"
+    }, @source, @options.filename)
+    throw err

   ###
   # visitors:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -27,8 +27,7 @@
         height 100

     # => bullet2 will be removed when it
-         goes outside the bounds
-         x: 0
+         goes outside the bou

 ActiveBounds module
 @name ActiveBounds


only set wal for sqlite

only set wal for sqlite

--- app/coffee/db.coffee
+++ app/coffee/db.coffee
@@ -14,7 +14,8 @@
 	options
 )

-sequelize.query("PRAGMA journal_mode=WAL;")
+if Settings.mysql.clsi.dialect == "sqlite"
+	sequelize.query("PRAGMA journal_mode=WAL;")

 module.exports =
 	UrlCache: sequelize.define("UrlCache", {


Render: Missing import.

Render: Missing import.

--- engine/render.coffee
+++ engine/render.coffee
@@ -1,6 +1,6 @@
 {mat3, mat4, vec3, vec4} = require 'vmath'
 timsort = require 'timsort'
-{Framebuffer} = require './framebuffer'
+{Framebuffer, ByteFramebuffer} = require './framebuffer'
 {next_POT} = require './math_utils/math_extra'
 {plane_from_norm_point} = require './math_utils/g3'


Remove ob.alpha uniform hack.

Remove ob.alpha uniform hack.

--- engine/render.coffee
+++ engine/render.coffee
@@ -384,9 +384,6 @@
                 gl.uniform1f mat.u_hardness, shading_params.specular_hardness
             if mat.u_emit?
                 gl.uniform1f mat.u_emit, shading_params.emit
-
-            if mat.u_custom[2]
-                gl.uniform1f(mat.u_custom[2], mesh.alpha)

             for i in [0...mat.u_custom.length]
                 cv = mesh.custom_uniform_values[i]


Fix CompoundPlayer.

Fix CompoundPlayer.

--- application/chrome/content/wesabe/download/CompoundPlayer.coffee
+++ application/chrome/content/wesabe/download/CompoundPlayer.coffee
@@ -1,11 +1,11 @@
-wesabe.provide('download.CompoundPlayer')
+extend = require 'lang/extend'

-class wesabe.download.CompoundPlayer
+class CompoundPlayer
   @register: (params) ->
-    klass = @create(params)
+    klass = @create params

     # make sure we put it where wesabe.require expects it
-    wesabe.provide("fi-scripts.#{klass.fid}", klass)
+    wesabe.provide "fi-scripts.#{klass.fid}", klass

     return klass

@@ -14,7 +14,7 @@
       @fid: params.fid
       @org: params.org

-    wesabe.lang.extend(klass.prototype, params)
+    extend klass.prototype, params

     return klass

@@ -25,18 +25,18 @@
     startNextPlayer = (failureCallback) =>
       nextPlayer()
       while @currentPlayer and not @currentPlayer.canHandleGoal(@job.goal)
-        wesabe.warn("Skipping player ", @currentPlayer, " since it can't handle goal: ", @job.goal)
+        logger.warn "Skipping player ", @currentPlayer, " since it can't handle goal: ", @job.goal
         nextPlayer()

       if @currentPlayer
-        wesabe.info("Starting player ", @currentPlayer)
+        logger.info "Starting player ", @currentPlayer
         @currentPlayer.job = @job
-        @currentPlayer.start(answers, browser)
+        @currentPlayer.start answers, browser
       else if failureCallback
         failureCallback()
       else
-        wesabe.info("No more players to handle goal: ", @job.goal)
-        @job.fail(400, "goal.unreachable")
+        logger.info "No more players to handle goal: ", @job.goal
+        @job.fail 400, "goal.unreachable"

     nextPlayer = =>
       @currentPlayer?.job = null
@@ -50,7 +50,7 @@
     # customize fail to go to the next player
     job_fail_original = @job.fail
     @job.fail = (status, result) =>
-      wesabe.info "Could not complete job with ", @currentPlayer, " (", status, " ", result, ")"
+      logger.info "Could not complete job with ", @currentPlayer, " (", status, " ", result, ")"

       # if we can't, just report the last failure
       startNextPlayer(=> job_fail_original.call(@job, status, result))
@@ -71,3 +71,6 @@

   @::__defineGetter__ 'browser', ->
     @currentPlayer?.browser
+
+
+module.exports = CompoundPlayer


#fix 652

#fix 652

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -49,7 +49,7 @@
   editorElement = editor.element
   scrollTop = editorElement.getScrollTop()

-  foldStartRows = editor.displayLayer.findFoldMarkers({}).map (m) -> m.getStartPosition().row
+  foldStartRows = editor.displayLayer.foldsMarkerLayer.findMarkers({}).map (m) -> m.getStartPosition().row
   ->
     for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row)
       editor.foldBufferRow(row)
@@ -646,7 +646,7 @@
     selection.destroy()

 getLargestFoldRangeContainsBufferRow = (editor, row) ->
-  markers = editor.displayLayer.findFoldMarkers(intersectsRow: row)
+  markers = editor.displayLayer.foldsMarkerLayer.findMarkers(intersectsRow: row)

   startPoint = null
   endPoint = null


OPCE6: getWord returns a Word object, not a string

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -136,7 +136,7 @@
     # Check if they're contiguous
     if selection.endPos.word == @startPos.word or
        (selection.endPos.word == @startPos.word -1 and
-        selection.endPos.loc == rte.getWord(selection.endPos.word).length() and
+        selection.endPos.loc == rte.getWord(selection.endPos.word).word.length() and
         @startPos.loc == 0)
       # boundaries of new selection
       start = selection.startPos
@@ -146,17 +146,17 @@
       rte.getWord(@startPos.word).removeSel(@)
     else if @endPos.word == selection.startPos.word or
        (@endPos.word == selection.startPos.word -1 and
-        @endPos.loc == rte.getWord(@endPos.word).length() and
+        @endPos.loc == rte.getWord(@endPos.word).word.length() and
         selection.startPos.loc == 0)
       start = @startPos
       end = selection.endPos
-      rte.getWord(@endPos.word).removeSel(@)
-      rte.getWord(selection.endPos.word).removeSel(selection)
+      rte.getWord(@endPos.word).word.removeSel(@)
+      rte.getWord(selection.endPos.word).word.removeSel(selection)
     else
       return

     nSelec = new Selection start, end, selection.style
-    rte.getWord()
+    rte.getWord().word

 # Class describing the Rich Text Editor type
@@ -185,7 +185,7 @@
   # unobserve:

   # @overload val()
-  #   Return the value of the Rte instance as a string
+  #   Return the value of the Rte instance as a non formatted string
   #
   # @overload val(content)
   #   Set the content of the Rte instance
@@ -200,15 +200,15 @@
       # TODO: support breaks (br, new paragraph, …)
       (e.word for e in @_rte.words).join('')

-  # Returns the **string representation** of a word.
+  # Returns the word object of a word.
   # @param index [Integer] the index of the word to return
   getWord: (index) ->
     if @_rte.words.length == 0 or index == @_rte.words.length
-      return ""
+      return new Word ""

     if not (0 <= index < @_rte.words.length)
       throw new Error "Index out of bounds #{index}"
-    @_rte.words[index].word
+    @_rte.words[index]

   # Returns the *word objects* within boundaries
   # @param begin [Integer] the first word the return
@@ -292,29 +292,29 @@
   #
   merge: (index) ->
     if 0 <= index < @_rte.words.length
-      word = @getWord(index).trimRight()
+      word = @getWord(index).word.trimRight()
       @deleteWords index
       @insert {startPos: {word: index, pos:0}}, word
     else
       throw new Error "Impossible to merge"

-  # Delete a selection
+  # Delete text undor selection
   #
   # @param [Selection] sel the selection to delete
   #
   deleteSel: (selection) ->
     if not selection.isValid()
       throw new Error "Invalid selection"
-    if not 0 <= selection.startPos.loc <= @getWord(selection.startPos.word).length
+    if not 0 <= selection.startPos.loc <= @getWord(selection.startPos.word).word.length
       throw new Error "Invalid selection"
-    if not 0 <= selection.endPos.loc <= @getWord(selection.endPos.word).length
+    if not 0 <= selection.endPos.loc <= @getWord(selection.endPos.word).word.length
       throw new Error "Invalid selection"

     start = selection.startPos.word
     end = selection.endPos.word

-    newLeft = @getWord(start).substring(0, selection.startPos.pos)
-    newRight = @getWord(end).substring(selection.endPos.pos)
+    newLeft = @getWord(start).word.substring(0, selection.startPos.pos)
+    newRight = @getWord(end).word.substring(selection.endPos.pos)

     if start == end
       @setWord(start, newLeft + newRight)
@@ -332,7 +332,7 @@

   # Insert text at position
   #
-  # @param [Option] position The position where to insert text
+  # @param [Selection] position The position where to insert text
   # @param [String] content the content to insert
   #
   insert: (selection, content)->
@@ -347,7 +347,7 @@
     pos = selection.startPos.pos

     preSpaces = content.match PreSpacesRegExp
-    currWord = @getWord index
+    currWord = @getWord(index).word

     # move the spaces to the previous word if a pos == 0
     if preSpaces isnt null
@@ -355,7 +355,7 @@
         if index == 0
           index += 1
           @insertWord 0, (new Word '')
-        prevWord = @getWord (index-1)
+        prevWord = @getWord(index-1).word
         prevWord += preSpaces
         content = content.substring(preSpaces.length)
         @setWord (index-1), prevWord
@@ -363,7 +363,7 @@
     # insert the content at position
     currWord = currWord.substring(0, pos) + content + currWord.substring(pos)

-    # cut the word
+    # cut the wordcon
     newWords = currWord.match WordRegExp
     tmp = currWord.match PreSpacesRegExp
     if tmp isnt null
@@ -383,7 +383,7 @@
         if pos < jump
           word -= 1
           jump -= pos
-          pos = @getWord(word).length - 1
+          pos = @getWord(word).word.length - 1

           if word < 0
             return null
@@ -413,6 +413,15 @@
     if not selection.isValid()
       throw new Error "Invalid selection"

+    # In case the selection already has a style (which is different)
+    if selection.style != style
+      selection = new Selection(selection.start, selection.end, style)
+
+    if _.isUndefined selection.style
+      selection.style = style
+
+    @getWord(selection.startPos.word).word
+
     # Try to merge with previous / next contiguous selection
     {prevWord, position} = @_jump(selection.startPos, -1)
     {nextWord, position} = @_jump(selection.endPos,   +1)


Broaden the keymap selector used to bind github:submit-comment

This will let cmd/ctrl-enter fire the github:submit-comment event for
review summary editors and existing comment editors, too.

--- keymaps/git.cson
+++ keymaps/git.cson
@@ -144,9 +144,9 @@
   'ctrl-=': 'github:more-context'
   'ctrl--': 'github:less-context'

-'.platform-darwin .github-Review-reply atom-text-editor':
+'.platform-darwin .github-Reviews atom-text-editor':
   'cmd-enter': 'github:submit-comment'
-'.platform-win32 .github-Review-reply atom-text-editor':
+'.platform-win32 .github-Reviews atom-text-editor':
   'ctrl-enter': 'github:submit-comment'
-'.platform-linux .github-Review-reply atom-text-editor':
+'.platform-linux .github-Reviews atom-text-editor':
   'ctrl-enter': 'github:submit-comment'


Dialog is forced to be focused #86

Dialog is forced to be focused #86

--- wzk/ui/grid/Grid.coffee
+++ wzk/ui/grid/Grid.coffee
@@ -299,7 +299,8 @@
     @param {goog.ui.Button} btn
   ###
   showDialog: (btn) ->
-    @dialog.setVisible true
+    @dialog.open()
+    @dialog.focus()
     goog.events.listenOnce @dialog, goog.ui.Dialog.EventType.SELECT, (e) =>
       if e.key is goog.ui.Dialog.DefaultButtonKeys.YES
         @dispatchDeleteItem btn


FAQ update

FAQ update

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -399,7 +399,7 @@
       answer: '''Light is a wave, specifically a wave of electromagnetic energy, and it comes in a wide range of wavelengths. Light that we see, or visible light, are electromagnetic waves that oscillate back and forth every 0.4-0.7 micrometers (there are a thousand micrometers in a millimeter, or a million micrometers in a meter). Infrared is light that has longer wavelengths, from 0.7 micrometers all the way up to about 300 micrometers.  The shorter-wavelength end of that range is called near infrared because it is near to visible light, and the longer-wavelength range is called far infrared.'''
       question: '''What is infrared?'''
     q11:
-      answer: '''These images come from two instruments, both aboard the Spitzer Space Telescope.  One of the instruments took images at wavelengths in the 3-8 micron range (and that light is colored green in these images) and the other took images at a wavelength of 24 micrometers, which is colored red.'''
+      answer: '''These images come from the Spitzer Space Telescope, from an instrument called IRAC, which took images at wavelengths in the 3-8 micron range.'''
       question: '''What wavelength are these images?'''
     q00:
       answer: '''All the stars you see in the night sky are part of a very large group of stars (a few hundred billion) that we call the Milky Way Galaxy. If you've ever looked at the night sky from a dark location, you've probably seen the band of light that we also call the 'Milky Way'. This band of light contains most of the stars in the Milky Way Galaxy, and the images you're inspecting all come from a narrow band only a few full moons wide along the Milky Way.'''


Greek Translation - A few more string translated

--- app/locale/el.coffee
+++ app/locale/el.coffee
@@ -163,10 +163,10 @@
     reject: "Απόρριψη"
     withdraw: "Απόσυρση"
 #    submitter: "Submitter"
-#    submitted: "Submitted"
-#    commit_msg: "Commit Message"
-#    version_history: "Version History"
-#    version_history_for: "Version History for: "
+    submitted: "Υποβλήθηκε"
+    commit_msg: "Υποβολή Μηνύματος"
+    version_history: "Ιστορικό Εκδόσεων"
+    version_history_for: "Ιστορικό Εκδόσεων για: "
     select_changes: "Επιλέξτε δύο αλλαγές από κάτω για να δείτε τη διαφορά."
     undo_prefix: "Αναίρεση"
     undo_shortcut: "(Ctrl+Z)"
@@ -241,7 +241,7 @@
     reload_really: "Είστε σίγουροι ότι θέλετε να φορτώσετε αυτό το επίπεδο από την αρχή;"
     reload_confirm: "Ανανέωση όλων"
     victory: "Νίκη"
-#    victory_title_prefix: ""
+    victory_title_prefix: ""
     victory_title_suffix: " Ολοκληρώθηκε"
     victory_sign_up: "Εγγραφείτε για ενημερώσεις"
     victory_sign_up_poke: "Θέλετε να λαμβάνετε τα τελευταία νέα μέσω e-mail; Δημιουργήστε έναν δωρεάν λογαριασμό και θα σας κρατάμε ενήμερους!"
@@ -254,12 +254,12 @@
     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_experience_gained: "Κερδισμενοι Πόντοι Εμπειρίας"
+    victory_gems_gained: "Κερδισμένα Πετράδια"
     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_viking_code_school: "Μπράβο σου, μόλις πέρασες ένα δύσκολο επίπεδο! Αν δεν είσαι ήδη προγραμματιστής, πρέπει να γίνεις. Είσαι σε καλό δρόμο για να γίνεις δεκτός στο Σχολείο Προγρααματισμού των Βίκινγκ, όπου μπορείς να πας τις ικανότητες στο επόμενο επίπεδο και να γίνεις ένας επαγγελματίας προγραμματιστής ιστού σε 14 weeks."
     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: "Φανταστική δουλειά! Οι ικανότητές σου βελτιώνονται, και κάποιος άρχισε να το προσέχει. Αν το σκέφτεσαι να γίνεις προγραμματιστής, σήμερα μπορεί να είναι η τυχερή σου μέρα. Το Bloc είναι ένα δικτυακό κέντρο εκπαίδευσης που θα σου βρει έναν έμπειρο μέντορα, ο οποίος θα σε εκπαιδεύσει για να γίνεις ένας επαγγελματίας προγραμματιστήςς! Κερδίζοντας το Χαμό των Μωρών, δικαιούσαι για μια μείωση τιμής αξίας 5000 δολλαρίων ΗΠΑ με τον κωδικό: CCRULES"
     victory_bloc_cta: "Συνάντησε το μέντορά σου – μάθε για το Bloc"
     guide_title: "Οδηγός"
     tome_minion_spells: "Ξόρκια για τα τσιράκια σας" # Only in old-style levels.
@@ -309,13 +309,13 @@
     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_harry: "Ναι, ένας Μάγος, "
+    tip_great_responsibility: "Με τις μεγάλες ικανότητες προγραμματισμού έρχονται και οι μεγάλες ευθύνες αποσφαλμάτωσης."
+    tip_munchkin: "Αν δε φας τα δημητριακά σου, ένα μωράκι θα σε κυνηγήσει στον ύπνο σου."
     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_commitment_yoda: "Ένας προγραμματιστής πρέπει να είναι απόλυτα προσηλωμένος και πολύ σοβαρός. ~ Yoda"
+    tip_no_try: "Ή θα κάνεις ή δε θα κάνεις. Δεν υπάρχει προσπάθεια"#"Do. Or do not. There is no try. - Yoda"
+    tip_patience: "Πρέπει να έχεις υπομονή νεαρέ Ακρίδα. - Yoda"
     tip_documented_bug: "Ένα τεκμηριωμένο σφάλμα δεν είναι σφάλμα. Είναι χαρακτηριστικό."
     tip_impossible: "Πάντα φαίνεται αδύνατο μέχρι να γίνει. - Nelson Mandela"
     tip_talk_is_cheap: "Τα λόγια είναι τσάμπα. Δείξε μου των κώδικα. - Linus Torvalds"
@@ -326,7 +326,7 @@
     tip_brute_force: "Όταν αμφιβάλλεις, χρησιμοποίησε ωμή βία. - Ken Thompson"
 #    tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
     tip_superpower: "Ο προγραμματισμός είναι ό,τι κοντινότερο έχουμε σε μια υπερδύναμη."
-#    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
+    tip_control_destiny: "Στον πραγματικό ανοικτό κώδικα, έχεις το δικαίωμα να ελέγχεις τη μοίρα σου. - Linus Torvalds"
     tip_no_code: "Κανένας κώδικας δεν είναι γρηγορότερος από τον καθόλου κώδικα."
     tip_code_never_lies: "Ο κώδικας ποτέ δε λέει ψέμματα, ενώ τα σχόλια καμμιά λένε. — Ron Jeffries"
     tip_reusable_software: "Πριν το λογισμικό γίνει επαναχρησιμοποίησιμο πρέπει να είναι χρησιμοποιήσιμο."
@@ -336,7 +336,7 @@
 #    tip_javascript_java: "Η Java είναι για τη JavaScript what Car is to Carpet. - Chris Heilmann"
     tip_move_forward: "Ό,τι και να κάνεις, συνέχισε να προχωράς μπροστά. - Martin Luther King Jr."
     tip_google: "Έχεις πρόβλημα που δεν μπορείς να λύσεις; Ψάξ' το στο Google!"
-#    tip_adding_evil: "Adding a pinch of evil."
+    tip_adding_evil: "Προσθέτω μια χούφτα κακό."
     tip_hate_computers: "Αυτό είναι το θέμα με τους ανθρώπους που νομίζουν ότι μισούν τους υπολογιστές. Αυτό που μισούν στην πραγματικότητα είναι οι κακοί προγραμματιστές. - Larry Niven"
     tip_open_source_contribute: "Μπορείς να βοηθήσεις το CodeCombat να βελτιωθεί!"
     tip_recurse: "Η επανάληψη είναι ανθρώπινη, η αναδρομή θεία. - L. Peter Deutsch"
@@ -572,9 +572,9 @@
   about:
     why_codecombat: "Γιατί CodeCombat?"
     why_paragraph_1: "Αν θες να μάθεις να προγραμματίζεις δε χρειάζεσαι μαθήματα. Χρειάζεται να γράφεις πολλές γραμμές κώδικα και να περνάς καλά κάνοντάς το."
-    why_paragraph_2_prefix: "Έτσι πρέπει να είναι ο προγραμματισμός. Πρέπει να είναι διασκεδαστικός. Not fun like"
+    why_paragraph_2_prefix: "Έτσι πρέπει να είναι ο προγραμματισμός. Πρέπει να έχει πλάκα αλλά να μην είναι της πλάκας"
     why_paragraph_2_italic: "Ναι ένα σήμα"
-#    why_paragraph_2_center: "but fun like"
+    why_paragraph_2_center: "αλλά όχι της πλάκας"
     why_paragraph_2_italic_caps: "ΟΧΙ ΜΑΜΑ ΠΡΕΠΕΙ ΝΑ ΤΕΛΕΙΏΣΩ ΑΥΤΟ ΕΠΙΠΕΔΟ!"
 #    why_paragraph_2_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
 #    why_paragraph_3: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."


Last merge fix

Last merge fix

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -82,7 +82,7 @@
     choose_your_level: "レベル選択" # The rest of this section is the old play view at /play-old and isn't very important.
     adventurer_prefix: "別のレベルに移動することができます。レベルについて議論するにはこちら: "
     adventurer_forum: "冒険者の掲示板"
-#   adventurer_suffix: "." #is this a period? as in "。"
+#    adventurer_suffix: "." #is this a period? as in "。"
 #    campaign_old_beginner: "Old Beginner Campaign"
     campaign_old_beginner_description: "プログラミングの魔法を学びましょう"
     campaign_dev: "いろんな難しいレベル"


Updated locale

Updated locale

--- app/locale/pl.coffee
+++ app/locale/pl.coffee
@@ -37,7 +37,7 @@
     conventional: "system nauczania oparty na grze z tradycyjnym, różnica jest wyraźna: dzięki grom uczniowie dłużej pamiętają, lepiej się koncentrują "
     perform_at_higher_level: "oraz osiągają lepsze wyniki."
     feedback: "Gry dostarczają także natychmiastową informację zwrotną, które pozwala uczniom dostosować ich rozwiązanie i zrozumieć pomysł całościowo, zamiast być ograniczonym tylko do odpowiedzi dobrze albo źle."
-    real_game: "A real game, played with real coding."
+    real_game: "Prawdziwa gra z prawdziwym kodowaniem."
     great_game: "Dobra gra to coś więcej niż tylko odznaki czy osiągnięcia - to podróż, dobrze dobrane zagadki i możliwość sprostania wyzwaniom samodzielnie z uczuciem pewności."
     agency: "CodeCombat jest grą, która daje graczom tą samodzielność i pewność, to dzięki naszemu silnikowi pisania kodu, który pomaga zarówno początkującym jak i zaawansowanym uczniom pisać poprawny kod."
     request_demo_title: "Namów swoich uczniów już dziś!"
@@ -97,7 +97,7 @@
     faqs: "FAQs"
     help_pref: "Potrzebujesz pomocy? Wyślij email"
     help_suff: "a my się odezwiemy!"
-    resource_hub: "Resource Hub"
+    resource_hub: "Baza Wiedzy"

   modal:
     close: "Zamknij"
@@ -136,10 +136,10 @@
     change_hero: "Wybierz bohatera" # Go back from choose inventory to choose hero
     buy_gems: "Kup klejnoty"
     subscription_required: "Wymagana subskrypcja"
-#    subscribers_only: "Subscribers Only!"
-#    subscribe_unlock: "Subscribe to Unlock!"
-#    subscriber_heroes: "Subscribe today to immediately unlock Amara, Hushbaum, and Hattori!"
-#    subscriber_gems: "Subscribe today to purchase this hero with gems!"
+    subscribers_only: "Tylko subskrybenci!"
+    subscribe_unlock: "Zasubskrybuj by odblokować!"
+    subscriber_heroes: "Zasubskrybuj dziś by od razu odblokować Amarę, Hushbauma i Hattoriego!"
+    subscriber_gems: "Zasubskrybuj dziś by wykupić tego bohatera za pomocą kryształów!"
     anonymous: "Anonimowy gracz"
     level_difficulty: "Poziom trudności: "
     play_classroom_version: "Zagraj w wersję szkolną" # Choose a level in campaign version that you also can play in one of your courses
@@ -150,7 +150,7 @@
     adjust_volume: "Dopasuj głośność"
     campaign_multiplayer: "Kampania dla wielu graczy"
     campaign_multiplayer_description: "... w której konkurujesz z innymi graczami."
-#    brain_pop_done: "You’ve defeated the Ogres with code! You win!"
+    brain_pop_done: "Pokonałeś ogry kodem! Wygrałeś!"

   code:
     if: "jeśli" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -245,7 +245,7 @@
     facebook_exists: "Już posiadasz konto powiązane z Facebook!"
     hey_students: "Uczniowie, wprowadźcie otrzymany od nauczyciela kod klasy."
     birthday: "Dzień urodzenia"
-#    parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
+    parent_email_blurb: "Wiemy że nie możesz się doczekać programowania — też się cieszymy! Twoi rodzice otrzymają dalsze instrukcje jak założyć dla ciebie konto. Napisz emaila na {{email_link}} jeżeli masz jakiekolwiek pytania."
     classroom_not_found: "Żadna klasa nie jest powiązana z tym kodem. Sprawdź czy wprowadziłeś poprawnie kod lub zapytaj nauczyciela."
     checking: "Sprawdzanie..."
     account_exists: "Ten email jest już zajęty:"
@@ -437,7 +437,7 @@
     reload_title: "Przywrócić cały kod?"
     reload_really: "Czy jesteś pewien, że chcesz przywrócić kod startowy tego poziomu?"
     reload_confirm: "Przywróć cały kod"
-#    test_level: "Test Level"
+    test_level: "Poziom testowy"
     victory: "Zwycięstwo"
     victory_title_prefix: ""
     victory_title_suffix: " ukończony"


Some translations weren't done yet.

Some translations weren't done yet.

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -418,13 +418,13 @@
     diplomat_join_suf_github: ", отредактируйте его онлайн и отправьте запрос на включение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!"
     more_about_diplomat: "Узнать больше о том, как стать Дипломатом"
     diplomat_subscribe_desc: "Получать email-ы о i18n разработках и уровнях для перевода."
-    ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас."
+#    ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас."  # Not done yet
 #    ambassador_introduction: "This is a community we're building, and you are the connections. We've got Olark chats, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
 #    ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
 #    ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
 #    ambassador_join_note_strong: "Note"
 #    ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
-    more_about_ambassador: "Узнать больше о том, как стать Ambassador"
+#    more_about_ambassador: "Узнать больше о том, как стать Ambassador"  # Not done yet
 #    ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
 #    counselor_summary: "None of the above roles fit what you are interested in? Do not worry, we are on the lookout for anybody who wants a hand in the development of CodeCombat! If you are interested in teaching, game development, open source management, or anything else that you think will be relevant to us, then this class is for you."
 #    counselor_introduction_1: "Do you have life experience? A different perspective on things that can help us decide how to shape CodeCombat? Of all these roles, this will probably take the least time, but individually you may make the most difference. We're on the lookout for wisened sages, particularly in areas like: teaching, game development, open source project management, technical recruiting, entrepreneurship, or design."
@@ -432,7 +432,7 @@
 #    counselor_attribute_1: "Experience, in any of the areas above or something you think might be helpful."
 #    counselor_attribute_2: "A little bit of free time!"
 #    counselor_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll put you in our contact list and be in touch when we could use advice (not too often)."
-    more_about_counselor: "Узнать больше о том, как стать Counselor"
+#    more_about_counselor: "Узнать больше о том, как стать Counselor"  # Not done yet
 #    changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
     diligent_scribes: "Наши старательные Писари:"
     powerful_archmages: "Наши могущественные Архимаги:"


Update sk.coffee

Update sk.coffee

tips translations

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -388,7 +388,7 @@
     tip_scrub_shortcut: "Ctrl+[ a Ctrl+] pre pretočenie a rychlý presun." # {change}
     tip_guide_exists: "Kliknite na sprievodcu v hernom menu (hore na stránke), pre užitočné informácie."
     tip_open_source: "CodeCombat je 100% open source!"
-#    tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!"
+    tip_tell_friends: "Užívaš si CodeCombat? Povedz to svojím priateľom!"
     tip_beta_launch: "CodeCombat spustil svoju beta verziu v Októbri, 2013."
     tip_think_solution: "Myslite na riešenie, nie na problém."
     tip_theory_practice: "Teoreticky nie je žiaden rozdiel medzi teoriou a praxou. Ale v praxi ten rozdiel je. - Yogi Berra"
@@ -429,19 +429,19 @@
     tip_adding_evil: "Pridanie špitky zla."
     tip_hate_computers: "Ľudia, ktorí si myslia, že nenávidia počítače, v skutočnosti nenávidia mizerných programátorov. - Larry Niven"
     tip_open_source_contribute: "Aj ty môžeš zlepšiť CodeCombat !"
-    tip_recurse: "Iterácia je ľudská, rekurzia božská.. - L. Peter Deutsch"
-#    tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
-#    tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
-#    tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen."
-#    tip_solve_then_write: "First, solve the problem. Then, write the code. - John Johnson"
-#    tip_compiler_ignores_comments: "Sometimes I think that the compiler ignores my comments."
-#    tip_understand_recursion: "The only way to understand recursion is to understand recursion."
-#    tip_life_and_polymorphism: "Open Source is like a totally polymorphic heterogeneous structure: All types are welcome."
-#    tip_mistakes_proof_of_trying: "Mistakes in your code are just proof that you are trying."
-#    tip_adding_orgres: "Rounding up ogres."
-#    tip_sharpening_swords: "Sharpening the swords."
-#    tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille"
-#    tip_nemo: "When life gets you down, want to know what you've gotta do? Just keep swimming, just keep swimming. - Dory, Finding Nemo"
+    tip_recurse: "Iterácia je ľudská, rekurzia božská. - L. Peter Deutsch"
+    tip_free_your_mind: "Zabudni na všetko, Neo. Zabudni na strach, pochybnosti a nedôveru. Osloboď svoju myseľ. - Morpheus"
+    tip_strong_opponents: "Aj najsilnejší súperi majú slabiny. - Itachi Uchiha"
+    tip_paper_and_pen: "Skôr ako začneš programovať, premysli si to za pomoci papiera a pera."
+    tip_solve_then_write: "Najskôr vyrieš problém a až potom píš kód. - John Johnson"
+    tip_compiler_ignores_comments: "Niekedy si myslím, že prekladač ignoruje moje komentáre."
+    tip_understand_recursion: "Jediným spôsobom ako pochopiť rekurziu je pochopiť rekurziu."
+    tip_life_and_polymorphism: "Open Source je ako totálne polymorfická heterogénna štruktúra: Všetky typy sú vítané."
+    tip_mistakes_proof_of_trying: "Chyby v tvojom kóde sú len dôkazom tvojej snahy."
+    tip_adding_orgres: "Počet ogrov sa zaokrúhľuje nahor."
+    tip_sharpening_swords: "Naostri si meče."
+    tip_ratatouille: "Nenechaj nikomu definovať svoje hranice len z dôvodu tvojho pôvodu. Tvojím jediným obmedzením si iba ty sám. - Gusteau, Ratatouille"
+    tip_nemo: "Ak ťa život zráža dolu, chceš vedieť čo ti pomôže? Plávaj, len stále plávaj. - Dory, Finding Nemo"
 #    tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green"
 #    tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green"
 #    tip_self_taught: "I taught myself 90% of what I've learned. And that's normal! - Hank Green"


syncstring: temporarily disable same-user collision detection

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -771,10 +771,17 @@
             @_patches_table.on('before-change', => @emit('before-change'))
             cb()

+        ###
+        TODO/CRITICAL: We are temporarily disabling same-user collision detection, since this seems to be leading to
+        serious issues involving a feedback loop, which may be way worse than the 1 in a million issue
+        that this addresses.  This only address the *same* accout being used simultaneously on the same file
+        by multiple people which isn't something users should ever do (but they do in big demos).
+
         @_patch_list.on 'overwrite', (t) =>
             # ensure that any outstanding save is done
             @_patches_table.save () =>
                 @_check_for_timestamp_collision(t)
+        ###

         @_patches_table.on 'saved', (data) =>
             @_handle_offline(data)


sagews2html: starting to disect output messages

--- src/smc-webapp/printing.coffee
+++ src/smc-webapp/printing.coffee
@@ -28,6 +28,7 @@
 {salvus_client} = require('./salvus_client')
 {redux}         = require('./smc-react')
 {project_tasks} = require('./project_tasks')
+markdown        = require('./markdown')

 # abstract class
 class Printer
@@ -123,16 +124,66 @@
             when 'html'
                 @html(opts.cb)

+    generate_html: (data) ->
+        if not @_html_tmpl?
+            @_html_tmpl = _.template '''
+                <!doctype html>
+
+
+                    <meta charset="utf-8">
+
+                    <title><%= title %></title>
+                    <meta name="description" content="automatically generated from <%= filename %> on SageMathCloud">
+                    <meta name="date" content="<%= timestamp %>">
+
+
+
+
+
+
+
+                <%= content %>
+
+                '''
+        return @_html_tmpl(data)
+
+    html_process_output_mesg: (mesg, mark) ->
+        out = null
+        if mesg.stdout?
+            out = "
#{mesg.stdout}
"
+        else if mesg.stderr?
+            out = "
#{mesg.stderr}
"
+        else if mesg.md?
+            x = markdown.markdown_to_html(mesg.md)
+            $out = $("
")
+            $out.html_noscript(x.s) # don't process mathjax!
+            @editor.syncdoc.process_html_output($out)
+            out = $out.html()
+        else if mesg.file?
+            out = "
#{mark.widgetNode.innerHTML}
"
+        else if mesg.done?
+            # ignored
+        else
+            console.debug "ignored mesg", mesg
+        return out
+
     html: (cb) ->
         # the following fits mentally into sagews.SynchronizedWorksheet
         {MARKERS} = require('smc-util/sagews')
         html = [] # list of elements
         cm = @editor.codemirror
+
         input_lines = []
         input_lines_process = ->
             if input_lines.length > 0
                 html.push("
#{input_lines.join('\n')}
")
                 input_lines = []
+
         for line in [0...cm.lineCount()]
             x = cm.getLine(line)
             console.log "line", x
@@ -142,23 +193,37 @@
                 input_lines.push(x)
             else
                 input_lines_process()
-                mark = marks[0]
-                switch x[0]
+                mark = marks[0] # assumption it's always 1
+                switch x[0]     # first char is the marker
                     when MARKERS.cell
                         x
-
                     when MARKERS.output
+                        # assume, all cells are evaluated and hence mark.rendered contains the html
                         console.log 'output', mark
-                        out = "
#{mark.widgetNode.innerHTML}
"
-                        html.push(out)
+                        for mesg_ser in mark.rendered.split(MARKERS.output)
+                            if mesg_ser.length == 0
+                                continue
+                            try
+                                mesg = misc.from_json(mesg_ser)
+                            catch e
+                                console.warn("invalid output message '#{m}' in line '#{line}'")
+                                continue
+
+                            output_html = @html_process_output_mesg(mesg, mark)
+                            if output_html?
+                                html.push(output_html)

         input_lines_process()
-        html_data = (h for h in html).join('\n')
+        html_data =
+            title     : @filename
+            filename  : @filename
+            content   : (h for h in html).join('\n')
+            timestamp : "#{new Date()}"

         salvus_client.write_text_file_to_project
             project_id : @editor.project_id
             path       : @output_file
-            content    : html_data
+            content    : @generate_html(html_data)
             cb         : (err, resp) =>
                 console.log err, resp
                 @cb?(err, resp)


move envify options into default options so user can override them

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -25,6 +25,9 @@
         message: "Couldn't read JSON data"
         sourcePath: step.inputPath

+  # used twice in creating defaultOptions
+  debug = getDebug()
+
   # sane defaults for options; most important is the baseDir
   defaultOptions =
     # Browserify will look here for npm modules
@@ -40,7 +43,14 @@
     #   Until I determine which is the proper behavior I prefer to maintain
     #   the debug value based on whether it's a dev or prod build.
     #   it can be overridden by the new per file options.
-    debug: getDebug()
+    debug: debug
+
+    # let's put the defaults for envify transform in here as well
+    # TODO: have an option which disables using envify
+    transforms:
+      envify:
+        NODE_ENV: if debug then 'development' else 'production'
+        _:'purge'

   # merge user options with defaults
   browserifyOptions = _.defaults userOptions, defaultOptions


Exponential backoff for chunking test - a failure will take about 4 seconds. A success is detected earlier and the request is aborted.


Update configuration

Update configuration

--- app/scripts/alchemy/defaultConf.coffee
+++ app/scripts/alchemy/defaultConf.coffee
@@ -73,6 +73,7 @@
     nodeOverlap: 25
     nodeRadius: 10 # partially implemented
     nodeTypes: null
+    rootNodes: 'root'
     rootNodeRadius: 15

     # Edges
@@ -87,4 +88,4 @@
     initialScale: 1
     initialTranslate: [0,0]
     scaleExtent: [0.5, 2.4]
-    warningMessage: "There be no data!  What's going on?"+    warningMessage: "There be no data!  What's going on?"


more generic default route

more generic default route

--- src/coffee/routes.coffee
+++ src/coffee/routes.coffee
@@ -8,6 +8,6 @@
 notFound = ->
   $('body').html '404d!'

-page '/:controller/:action/:id?', defaultRoute
+page '/:controller/:action?/:id?', defaultRoute
 page '', defaultRoute
 page '*', notFound


Handling undefined uri parameter example.

--- src/validate-parameters.coffee
+++ src/validate-parameters.coffee
@@ -5,7 +5,7 @@
   }

   for paramName, param of params
-    if param['required'] == true and param['example'] == ''
+    if param['required'] == true and (param['example'] == '' or param['example'] == undefined)
       text = "Required URI parameter '#{paramName}' has no example value."
       result['errors'].push text


backend_pubsub: fix notification matching

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -114,52 +114,52 @@
             # <event xmlns='http://jabber.org/protocol/pubsub#event'>
             if child.is("event", NS.PUBSUB_EVENT)
                 child.children.forEach (child) ->
-                unless child.is
-                    # No element, but text
-                    return
-                node = child.attrs.node
-                unless node
-                    return
-
-                if child.is('items')
-                    items = []
-                    for itemEl in child.getChildren('item')
-                        item =
-                            el: itemEl.children.filter((itemEl) ->
-                                itemEl.hasOwnProperty('children')
-                            )[0]
-                        if itemEl.attrs.id
-                            item.id = itemEl.attrs.id
-                        if item.el
-                            items.push item
-                    updates.push
-                        type: 'items'
-                        node: node
-                        items: items
-
-                if child.is('subscription')
-                    updates.push
-                        type: 'subscription'
-                        node: node
-                        user: child.attrs.jid
-                        subscription: child.attrs.subscription
-
-                if child.is('affiliation')
-                    updates.push
-                        type: 'affiliation'
-                        node: node
-                        user: child.attrs.jid
-                        affiliation: child.attrs.affiliation
-
-                if child.is('configuration')
-                    xEl = child.getChild('x', NS.DATA)
-                    form = xEl and forms.fromXml(xEl)
-                    config = form and forms.formToConfig(form)
-                    if config
+                    unless child.is
+                        # No element, but text
+                        return
+                    node = child.attrs.node
+                    unless node
+                        return
+
+                    if child.is('items')
+                        items = []
+                        for itemEl in child.getChildren('item')
+                            item =
+                                el: itemEl.children.filter((itemEl) ->
+                                    itemEl.hasOwnProperty('children')
+                                )[0]
+                            if itemEl.attrs.id
+                                item.id = itemEl.attrs.id
+                            if item.el
+                                items.push item
                         updates.push
-                            type: 'config'
+                            type: 'items'
                             node: node
-                            config: config
+                            items: items
+
+                    if child.is('subscription')
+                        updates.push
+                            type: 'subscription'
+                            node: node
+                            user: child.attrs.jid
+                            subscription: child.attrs.subscription
+
+                    if child.is('affiliation')
+                        updates.push
+                            type: 'affiliation'
+                            node: node
+                            user: child.attrs.jid
+                            affiliation: child.attrs.affiliation
+
+                    if child.is('configuration')
+                        xEl = child.getChild('x', NS.DATA)
+                        form = xEl and forms.fromXml(xEl)
+                        config = form and forms.formToConfig(form)
+                        if config
+                            updates.push
+                                type: 'config'
+                                node: node
+                                config: config

             # <you-missed-something/>
             if child.is("you-missed-something", NS.BUDDYCLOUD_V1)


updating button - white border is invisible on mobile

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -74,7 +74,7 @@

€ #{ p.cost }

-                    Add to Cart
+                    Add to Cart

"


Add comment

Add comment

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -1,7 +1,7 @@
 define (require) ->
   Backbone = require('backbone')
+  toc = require('cs!collections/toc')
   Collection = require('cs!models/content/collection')
-  toc = require('cs!collections/toc')
   Page = require('cs!models/content/page')
   require('backbone-associations')

@@ -46,6 +46,7 @@
       # Keep the id with the desired version number included
       delete response.id

+      # Only setup a toc for a book
       if type isnt 'book' then return response

       response.contents = response.tree.contents


activityticker : removed the white space

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -225,7 +225,6 @@
   getClassName: (data)->
     {as, source, target} = data
     classKey = "#{source?.bongo_?.constructorName}_#{as}_#{target?.bongo_?.constructorName}"
-
     return itemClassMap[classKey]

 class ActiveTopicItemView extends KDListItemView


Activity: used pistachio the right way

Activity: used pistachio the right way

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -220,13 +220,10 @@

     @actor = new ProfileLinkView {}, data

-    {followers, following} = data.counts
     @followersAndFollowing = new JView
       cssClass  : 'user-numbers'
-      pistachio :
-        """
-          #{followers} followers #{following} following
-        """
+      pistachio : "{{ #(counts.followers)}} followers {{ #(counts.following)}} following"
+    , data

     unless KD.isMine data
       @followButton = new FollowButton


Client: ActivitySidebar: use notification controller instead of bongo

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -75,7 +75,6 @@

         .on 'MessageListUpdated',        @bound 'setPostUnreadCount'
         .on 'ParticipantUpdated',        @bound 'handleGlanced'
-        .on 'NewWorkspaceCreated',       @bound 'updateMachines'
         .on 'WorkspaceRemoved',          @bound 'updateMachines'
         # .on 'ReplyRemoved',              (update) -> log update.event, update
         # .on 'ChannelUpdateHappened',     @bound 'channelUpdateHappened'
@@ -95,7 +94,7 @@

     mainController.ready =>
       environmentDataProvider.ensureDefaultWorkspace @bound 'updateMachines'
-      whoami().on 'NewWorkspaceCreated', @bound 'updateMachines'
+      notificationController.on 'NewWorkspaceCreated', @bound 'updateMachines'

     @localStorageController = kd.singletons.localStorageController.storage 'Sidebar'


Rename method

Rename method

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -91,7 +91,7 @@
         return  unless @settingsEnabled()

         if @machine.isMine() then @handleMachineSettingsClick()
-        else if @machine.isApproved() then @showSidebarSharePopup()
+        else if @machine.isApproved() then @showSharePopup()

   moveSettingsIconLeft : ->
@@ -168,7 +168,7 @@
     return data.workspaces.first?.channelId

-  showSidebarSharePopup: (options = {}) ->
+  showSharePopup: (options = {}) ->

     options.position  = @getPopupPosition 20
     options.channelId = @getChannelId @getData()
@@ -182,7 +182,7 @@

     if not m.isMine() and not m.isApproved()
       kd.utils.stopDOMEvent e
-      @showSidebarSharePopup()
+      @showSharePopup()

     super


Code refactoring

Code refactoring

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -87,8 +87,8 @@
       tagName     : 'span'
       click       : (e) =>
         kd.utils.stopDOMEvent e
-        if @machine.isMine() then @handleMachineSettingsClick
-        else if @machine.isApproved() then @showSidebarSharePopup {}
+        if @machine.isMine() then @handleMachineSettingsClick()
+        else if @machine.isApproved() then @showSidebarSharePopup()

   createSettingsIconPlaceholder: ->
@@ -114,7 +114,7 @@
     return state in [ NotInitialized, Running, Stopped, Terminated, Unknown ]

-  handleMachineSettingsClick: () ->
+  handleMachineSettingsClick: ->

     return  if not @settingsEnabled()


application first opening compile fix

application first opening compile fix

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -174,8 +174,7 @@
     else
       @fetchCompiledAppSource manifest, (err, script)=>
         if err
-          @compileApp name, (err)->
-            callback err, script
+          @compileApp name, callback
         else
           @defineApp name, script
           callback err, script
@@ -322,7 +321,7 @@
               @defineApp name, res
               loader.notificationSetTitle "App compiled successfully"
               loader.notificationSetTimer 2000
-            callback? err
+            callback? err, res
         else
           loader.destroy()


cocalc-kubernetes: be more careful about using the --network option

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -277,9 +277,13 @@
                 args.push('--ephemeral_state')
             if @_ephemeral_disk
                 args.push('--ephemeral_disk')
-            if @_network
-                args.push('--network')
-                args.push(1)
+
+        if @_kubernetes and @_network and (opts.action == 'start' or opts.action == 'restart')
+            # networking for kubernetes is set during start or restart action.
+            # TODO: similar for member hosting, etc., but we don't even have
+            # information about that here.
+            args.push('--network')
+            args.push(1)

         args.push(@project_id)
         dbg("args=#{misc.to_safe_str(args)}")


this is the only possible thing that can be causing this error "Uncaught TypeError: Cannot read property 'isdir' of undefined"

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1200,7 +1200,7 @@

     render_download : ->
         single_item = @props.checked_files.first()
-        if @props.checked_files.size isnt 1 or @props.file_map[misc.path_split(single_item).tail].isdir
+        if @props.checked_files.size isnt 1 or @props.file_map[misc.path_split(single_item).tail]?.isdir
             download_not_implemented_yet = true
         <div>
             <Row>


Handle backbone links in the modal on the site properly

--- core/app/backbone/contrib/default_click_handler.coffee
+++ core/app/backbone/contrib/default_click_handler.coffee
@@ -30,4 +30,4 @@
       window.focus()

 # HACK: this is needed because internal events did not seem to work
-$(document).on "click", ":not(div.discussion-modal) a[rel=backbone]", Backbone.View::defaultClickHandler
+$(document).on "click", ":not(body.client) a[rel=backbone]", Backbone.View::defaultClickHandler


activity: accept an after event for inviting others on chatpane

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -14,6 +14,7 @@
     messages          : null
     isDataLoading     : no
     onLoadMore        : kd.noop
+    afterInviteOthers : kd.noop
     showItemMenu      : yes

@@ -29,13 +30,19 @@
   onTopThresholdReached: -> @props.onLoadMore()

+  afterInviteOthers: -> @props.afterInviteOthers()
+
+
   channel: (key) -> @props.thread.getIn ['channel', key]

   renderChannelInfoContainer: ->

     if @props.thread?.getIn(['flags', 'reachedFirstMessage'])
-      <ChannelInfoContainer key={@channel 'id'} thread={@props.thread} />
+      <ChannelInfoContainer
+        key={@channel 'id'}
+        thread={@props.thread}
+        afterInviteOthers={@bound 'afterInviteOthers'} />

   renderBody: ->


DefineStackView: whole save/apply flow redesigned from scratch [#104711034] [#103436534]

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -171,6 +171,12 @@
       cssClass       : 'solid compact light-gray nav cancel'
       callback       : => @emit 'Cancel'

+    @buttons.addSubView @setAsDefaultButton = new KDButtonView
+      title          : 'Apply to Team'
+      cssClass       : 'solid compact green nav next hidden'
+      loader         : yes
+      callback       : => @handleSetDefaultTemplate()
+
     @buttons.addSubView @previewButton = new KDButtonView
       title          : 'Template Preview'
       cssClass       : 'solid compact light-gray nav next prev-button'
@@ -186,12 +192,6 @@
       loader         : yes
       callback       : @bound 'handleSave'

-    @buttons.addSubView @setAsDefaultButton = new KDButtonView
-      title          : 'Set as Default for Team'
-      cssClass       : 'solid compact green nav next hidden'
-      loader         : yes
-      callback       : => @handleSetDefaultTemplate()
-

   handleSave: ->

@@ -260,6 +260,16 @@

   processTemplate: (stackTemplate) ->

+    setToGroup = (method = 'add') =>
+      @handleSetDefaultTemplate completed = no
+
+      @outputView[method] """
+        Your stack script has been successfully saved and all your team
+        members now will use the stack you have just saved.
+
+        You can now close this window or continue working with your stack.
+      """
+
     @handleCheckTemplate { stackTemplate }, (err, machines) =>

       @saveButton.hideLoader()
@@ -270,22 +280,33 @@
         return

       { groupsController } = kd.singletons
-
-      { stackTemplates } = groupsController.getCurrentGroup()
-
-      @handleSetDefaultTemplate completed = no
-
-      action = if not stackTemplates?.length then 'addAndWarn' else 'add'
-
-      @outputView[action] """
-        Your stack script has been successfully saved and all your team
-        members now will use the stack you have just saved.
-
-        You can now close this window or continue working with your stack.
-      """
+      { stackTemplates }   = groupsController.getCurrentGroup()
+      templateSetBefore    = stackTemplates?.length
+
+      if templateSetBefore
+
+        unless stackTemplate.inuse
+
+          @setAsDefaultButton.show()
+
+          @outputView.add """
+
+            Your stack script has been successfully saved.
+
+            If you want your team members to use this template you need to
+            apply it for your team.
+
+            You can now close this window or continue working with your stack.
+          """
+
+        else
+          setToGroup()
+
+      else
+        setToGroup 'addAndWarn'
+

       @cancelButton.setTitle 'Close'
-      @saveButton.hide()

   checkAndBootstrapCredentials: (callback) ->


FileFinder: Remove tailFile method.

FileFinder: Remove tailFile method.

It's not necessary here.

--- client/ide/lib/views/filefinder/idefilefinder.coffee
+++ client/ide/lib/views/filefinder/idefilefinder.coffee
@@ -167,18 +167,6 @@
       kd.getSingleton('appManager').tell 'IDE', 'openFile', { file, contents }

-  tailFile: (path) ->
-
-    file = FSHelper.createFileInstance { path, @machine }
-
-    file.fetchContents (err, contents) =>
-      return @showWarning 'An error occurred, please try again.'  if err
-
-      @destroy()
-
-      kd.getSingleton('appManager').tell 'IDE', 'tailFile', { file, contents }
-
-
   showWarning: (text) ->

     @content.destroySubViews()


teams: utils teamdata ceration made reusable

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -438,11 +438,10 @@
     return {}

-  createTeam: ->
+  createFormData = (teamData) ->

     teamData = KD.utils.getTeamData()
     formData = {}
-
     for key, value of teamData
       for k, v of value
         if k.search('invitee') >= 0
@@ -450,6 +449,13 @@
           formData['invitees'] += ",#{v}"
         else
           formData[k] = v
+
+    return formData
+
+
+  createTeam: ->
+
+    formData = createFormData()

     # manually add legacy fields - SY
     formData.agree           = 'on'


Use FormChooser to select forms

Use FormChooser to select forms

--- client/skr/screens/customer-maint/CustomerMaint.cjsx
+++ client/skr/screens/customer-maint/CustomerMaint.cjsx
@@ -9,25 +9,8 @@

     modelForAccess: 'customer'

-    componentDidMount: ->
-        @state.commands.toggleEdit()
-        finder = @refs.finder.refs.finder
-        finder._setValue('GOAT')
-        finder.loadCurrentSelection()
-
-    setForm: (type, name) ->
-        @customer.forms = _.extend(@customer.forms, {"#{type}": name})
-
-    FormChooser: (props) ->
-        label = "#{_.field2title(props.type)} Form"
-        choices = Skr.Models[_.classify(props.type)].Templates
-        <LC.FormGroup label={label} sm=3>
-            <Lanes.Vendor.ReactWidgets.DropdownList
-                data={choices}
-                value={@customer.forms?[props.type] or 'default'}
-                onChange={_.partial(@setForm, props.type)}
-            />
-        </LC.FormGroup>
+    setForm: (val, {type}) ->
+        @customer.forms = _.extend({}, @customer.forms, {"#{type}": val})

     render: ->
         <LC.ScreenWrapper identifier="customer-maint">
@@ -48,8 +31,16 @@
                 <SC.GlAccountChooser sm=3 model={@customer}
                     label="Receivables Account" name="gl_receivables_account"/>
                 <SC.TermsChooser model={@customer} sm=3 />
-                <@FormChooser type="sales_order" />
-                <@FormChooser type="invoice" />
+                <SC.PrintFormChooser
+                    label="Sales Order Form" sm=3 model={@customer}
+                    onChange={@setForm} type="sales_order"
+                    value={@customer.forms?.sales_order}
+                    choices={Skr.Models.SalesOrder.Templates} />
+                <SC.PrintFormChooser
+                    label="Invoice Form" sm=3 model={@customer}
+                    onChange={@setForm} type="invoice"
+                    value={@customer.forms?.invoice}
+                    choices={Skr.Models.Invoice.Templates} />
             </BS.Row>
             <BS.Row>
                 <LC.FieldSet sm=12 title="Address">


flash frame only

flash frame only

--- src/ui/views/notifications.coffee
+++ src/ui/views/notifications.coffee
@@ -107,9 +107,9 @@
             if (!notifierSupportsSound || viewstate.forceCustomSound) && !viewstate.muteSoundNotification && audioEl.paused
                 audioEl.play()
         if process.platform is 'win32'
-            if not mainWindow.isVisible()
-                mainWindow.showInactive()
-                mainWindow.minimize()
+            #if not mainWindow.isVisible()
+            #    mainWindow.showInactive()
+            #    mainWindow.minimize()
             mainWindow.flashFrame(true)

 textMessage = (cont, proxied, showMessage = true) ->


Hide trend only if undefined

Hide trend only if undefined

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -64,7 +64,7 @@
             _.each $scope.projects, (project) =>
                 plot = new plottings.SuccessPercentPlot project
                 project.chart = plot.createChartObject()
-                project.prettyTrend = _.sprintf '%.2f', project.trend if project.trend
+                project.prettyTrend = _.sprintf '%.2f', project.trend if not _.isUndefined(project.trend)
                 project.trendClass = if project.trend >= 0.01 then 'success' else
                     if project.trend <= -0.99 then 'failed' else 'neutral'
                 if project.last_task and project.last_task.status == 2


Using zappa provided jQuery in chat example.

--- examples/chat.coffee
+++ examples/chat.coffee
@@ -1,4 +1,6 @@
 require('zappa') ->
+  enable 'serve jquery'
+
   get '/': ->
     render 'index', layout: no

@@ -27,7 +29,7 @@
       head ->
         title 'PicoChat!'
         script src: '/socket.io/socket.io.js'
-        script src: 'http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'
+        script src: '/zappa/jquery.js'
         script src: '/zappa/zappa.js'
         script src: '/index.js'
       body ->


Use Workspace::registerOpener

Use Workspace::registerOpener

--- lib/index.coffee
+++ lib/index.coffee
@@ -20,7 +20,7 @@

 module.exports =
   activate: ->
-    atom.project.registerOpener (uri) ->
+    atom.workspace.registerOpener (uri) ->
       createSettingsView({uri}) if uri is configUri

     atom.workspaceView.command 'settings-view:open', ->


remove encoding stuff, explicitly don't decodeStrings, simplify a few things

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,7 +1,7 @@
 {Duplex} = require 'stream'

 class Strung extends Duplex
-  constructor: (@_source, @_encoding = 'utf8') ->  super()
+  constructor: (@_source) -> super decodeStrings:false

   _read: (size) ->
     unless @_source?.length then return @push null
@@ -9,31 +9,30 @@
     @_source = @_source[size..]
     return

-  _write: (string, encoding, next) ->
-    string = string.toString @_encoding
+  _write: (string, _, next) ->
+    string = string.toString()
     if @string? then @string += string
     else @string = string
     next()

-  pipe: (stringOrStream, encoding = 'utf8') -> # TODO: throw error when undefined?
-    if 'string' is typeof stringOrStream
-      return @reset stringOrStream, encoding
-    else
-      return super stringOrStream
+  pipe: (stringOrStream) ->
+    if 'string' is typeof stringOrStream then @reset stringOrStream
+    else super stringOrStream

-  reset: (string, encoding = 'utf8') ->
-    Duplex.call this # rerun constructor to reconfigure it
+  reset: (string) ->
+    Duplex.call this, decodeStrings:false # rerun constructor to reconfigure it
     @_source = string
-    @_encoding = encoding
     delete @string
     return this

 # export a function which creates a Strung instance
-module.exports = exporter = (string, encoding) -> new Strung string, encoding
+module.exports = (string) -> new Strung string
+
 # export the class as a sub property on the function
-exporter.Strung = Strung
+module.exports.Strung = Strung
+
 # export a helper function on our exported function to start piping a string
-exporter.pipe = (string, encoding = 'utf8') ->
+module.exports.pipe = (string) ->
   unless 'string' is typeof string
-    throw new Error 'exported strung function only accepts a string to pipe()'
-  new Strung string, encoding
+    throw new TypeError 'must provide a string to pipe()'
+  new Strung string


enhance: add dynamic status enums

enhance: add dynamic status enums

--- app/enums.coffee
+++ app/enums.coffee
@@ -35,6 +35,10 @@
     BOOTING: 1
     READY: 2
     SHUTTING_DOWN: 3
+    DOWNLOADING: 4
+    INSTALLING: 5
+    LAUNCHING: 6
+    HOOKING: 7

   MANUAL:
     NONE: 0