profile: tooltip added for social media handle in place editing

--- client/app/MainApp/lazy/staticprofilecontroller.coffee
+++ client/app/MainApp/lazy/staticprofilecontroller.coffee
@@ -272,6 +272,8 @@

       handleLinks['twitter'] = new KDView
         lazyDomId : 'profile-handle-twitter'
+        tooltip :
+          title : 'Click here to change your Twitter handle'
         click:(event)->
           event.preventDefault()
           event.stopPropagation()
@@ -280,6 +282,8 @@

       handleLinks['github'] = new KDView
         lazyDomId : 'profile-handle-github'
+        tooltip :
+          title : 'Click here to change your GitHub handle'
         click:(event)->
           event.preventDefault()
           event.stopPropagation()


whitespace.

whitespace.


dont trigger save on change from header (uses content instead)

--- client/apps/edit/components/header/index.coffee
+++ client/apps/edit/components/header/index.coffee
@@ -10,7 +10,8 @@
     { @article } = options
     @finished = false
     @user = new User sd.USER
-    @article.on 'change', @saving
+    unless sd.EDIT_2
+      @article.on 'change', @saving
     @article.on 'change', @toggleCheckmarks
     @article.on 'error', @error
     @article.on 'sync', @done


payment: use new "required" helper (instead of the required method)

+ sentence case for titles

--- client/landing/app/MainApp/payment/paymentform.coffee
+++ client/landing/app/MainApp/payment/paymentform.coffee
@@ -6,22 +6,22 @@

       cardFirstName       :
         label             : 'Name'
-        placeholder       : 'First Name'
+        placeholder       : 'First name'
         defaultValue      : KD.whoami().profile.firstName
-        validate          : @required 'First name is required!'
+        required          : 'First name is required!'
         keyup             : @bound 'updateDescription'
         nextElementFlat   :
           cardLastName    :
-            placeholder   : 'Last Name'
+            placeholder   : 'Last name'
             defaultValue  : KD.whoami().profile.lastName
-            validate      : @required 'Last name is required!'
+            required      : 'Last name is required!'

       cardDescription     :
         label             : 'Description'

       cardNumber          :
-        label             : 'Credit Card'
-        placeholder       : 'Credit Card Number'
+        label             : 'Credit card'
+        placeholder       : 'Credit card number'
         blur              : ->
           @oldValue = @getValue()
           @setValue @oldValue.replace /\s|-/g, ''
@@ -179,10 +179,6 @@
         else
           @inputs[key]?.setValue value

-  required:(msg)->
-    rules    : required  : yes
-    messages : required  : msg
-
   clearValidation:->
     inputs = KDFormView.findChildInputs this
     input.clearValidationFeedback()  for input in inputs


Coffee based binding

Coffee based binding

--- app/javascript/src/legacy/similar-thumbnailing.coffee
+++ app/javascript/src/legacy/similar-thumbnailing.coffee
@@ -26,9 +26,9 @@
     @image = image_pool.get()
     @onComplete = onComplete
     @url = URL.createObjectURL(@file)
-    @image.on 'load', @image_load_event.bindAsEventListener(this)
-    @image.on 'abort', @image_abort_event.bindAsEventListener(this)
-    @image.on 'error', @image_error_event.bindAsEventListener(this)
+    @image.on 'load', @image_load_event
+    @image.on 'abort', @image_abort_event
+    @image.on 'error', @image_error_event
     document.documentElement.addClassName 'progress'
     @image.src = @url
     return
@@ -54,7 +54,7 @@

   # When the image finishes loading after form_submit_event sets it, update the canvas
   # thumbnail from it.
-  image_load_event: (e) ->
+  image_load_event: (e) =>
     # Reduce the image size to thumbnail resolution.
     width = @image.width
     height = @image.height
@@ -112,14 +112,14 @@
       idx += 4
     false

-  image_abort_event: (e) ->
+  image_abort_event: (e) =>
     @completed
       success: false
       aborted: true
     return

   # This happens on normal errors, usually because the file isn't a supported image.
-  image_error_event: (e) ->
+  image_error_event: (e) =>
     @completed success: false
     return

@@ -128,10 +128,10 @@
     @similar = null
     @form = form
     @force_file = null
-    form.on 'submit', @form_submit_event.bindAsEventListener(this)
+    form.on 'submit', @form_submit_event
     return

-  form_submit_event: (e) ->
+  form_submit_event: (e) =>
     post_file = @form.down('#file')

     # If the files attribute isn't supported, or we have no file (source upload), use regular
@@ -148,11 +148,11 @@
     e.stop()
     if @similar
       @similar.destroy()
-    @similar = new ThumbnailUserImage(file, @complete.bind(this))
+    @similar = new ThumbnailUserImage(file, @complete)
     return

   # Submit a post/similar request using the image currently in the canvas.
-  complete: (result) ->
+  complete: (result) =>
     if result.chromeFailure
       notice 'The image failed to load; submitting normally...'
       @force_file = @file


fetchData on intialization later so that splash screen is not stuck

--- app/index.coffee
+++ app/index.coffee
@@ -32,7 +32,7 @@

     # Reload data when resumed
     forge.event.appResumed.addListener(@appResumed) if Config.env=='ios'
-    setTimeout(@fetchData, 50)
+    setTimeout(@fetchData, 500)

     # Controllers
     @user_edit = new UserEdit


Refactor list git packages to use user package dir

Signed-off-by: Michelle Tilley <19f7a7eb6786def4c39d6d86c40af5dbc6589663@github.com>

--- src/list.coffee
+++ src/list.coffee
@@ -3,7 +3,6 @@
 _ = require 'underscore-plus'
 CSON = require 'season'
 yargs = require 'yargs'
-Git = require 'git-utils'

 Command = require './command'
 fs = require './fs'
@@ -18,7 +17,6 @@
   constructor: ->
     @userPackagesDirectory = path.join(config.getAtomDirectory(), 'packages')
     @devPackagesDirectory = path.join(config.getAtomDirectory(), 'dev', 'packages')
-    @gitPackagesDirectory = path.join(config.getAtomDirectory(), 'git-packages')
     if configPath = CSON.resolve(path.join(config.getAtomDirectory(), 'config'))
       try
         @disabledPackages = CSON.readFileSync(configPath)?.core?.disabledPackages
@@ -59,11 +57,11 @@
       tree packages, (pack) =>
         packageLine = pack.name
         packageLine += "@#{pack.version}" if pack.version?
-        if pack.sha?
+        if pack.apmInstallSource?.type is 'git'
           repo = getRepository(pack)
-          shaLine = "##{pack.sha}"
+          shaLine = "##{pack.apmInstallSource.sha.substr(0,8)}"
           shaLine = repo + shaLine if repo?
-          packageLine += " (#{shaLine})"
+          packageLine += " (#{shaLine})".grey
         packageLine += ' (disabled)' if @isPackageDisabled(pack.name)
         packageLine
     console.log()
@@ -81,10 +79,6 @@
         try
           manifest = CSON.readFileSync(manifestPath)
       manifest ?= {}
-      try
-        if directoryPath is @gitPackagesDirectory
-          repo = Git.open(path.join(directoryPath, child))
-          manifest.sha = repo.getReferenceTarget(repo.getHead()).substr(0, 7)
       manifest.name = child
       if options.argv.themes
         packages.push(manifest) if manifest.theme
@@ -97,8 +91,9 @@

   listUserPackages: (options, callback) ->
     userPackages = @listPackages(@userPackagesDirectory, options)
+      .filter (pack) -> not pack.apmInstallSource
     unless options.argv.bare or options.argv.json
-      console.log "#{@userPackagesDirectory.cyan} (#{userPackages.length})"
+      console.log "Community Packages (#{userPackages.length})".cyan, "#{@userPackagesDirectory}"
     callback?(null, userPackages)

   listDevPackages: (options, callback) ->
@@ -107,14 +102,15 @@
     devPackages = @listPackages(@devPackagesDirectory, options)
     if devPackages.length > 0
       unless options.argv.bare or options.argv.json
-        console.log "#{@devPackagesDirectory.cyan} (#{devPackages.length})"
+        console.log "Dev Packages (#{devPackages.length})".cyan, "#{@devPackagesDirectory}"
     callback?(null, devPackages)

   listGitPackages: (options, callback) ->
-    gitPackages = @listPackages(@gitPackagesDirectory, options)
+    gitPackages = @listPackages(@userPackagesDirectory, options)
+      .filter (pack) -> pack.apmInstallSource?.type is 'git'
     if gitPackages.length > 0
       unless options.argv.bare or options.argv.json
-        console.log "#{@gitPackagesDirectory.cyan} (#{gitPackages.length})"
+        console.log "Git Packages (#{gitPackages.length})".cyan, "#{@userPackagesDirectory}"
     callback?(null, gitPackages)

   listBundledPackages: (options, callback) ->


refactor refresh attachment method

refactor refresh attachment method

--- app/modules/components/wysiwyg/wysiwyg.service.coffee
+++ app/modules/components/wysiwyg/wysiwyg.service.coffee
@@ -57,36 +57,34 @@
     getAttachmentData: (el, tokens) ->
         deferred = @q.defer()
         @attachmentsService.get(tokens[0], tokens[1]).then (response) =>
-            url = response.data.url
-            url = "#{url}#_taiga-refresh=#{tokens[0]}:#{tokens[1]}"
-            el.setAttribute('src', url)
+            el.setAttribute('src', "#{response.data.url}#_taiga-refresh=#{tokens[0]}:#{tokens[1]}")
             deferred.resolve(el)

         return deferred.promise

     refreshAttachmentURL: (html) ->
-        deferred = @q.defer()
         el = document.createElement( 'html' )
         el.innerHTML = html
-
         regex = /#_taiga-refresh=([a-zA-Z]*\:\d+)/
-        links = el.querySelectorAll('a')
+
+        links = {
+            "elements": el.querySelectorAll('a'),
+            "attr": "href",
+        }
+        images = {
+            "elements": el.querySelectorAll('img'),
+            "attr": "src",
+        }
+
+        deferred = @q.defer()
         promises = []
-
-        for e in links
-            if e.getAttribute('href').indexOf('#_taiga-refresh=') != -1
-                match = e.getAttribute('href').match(regex)
-                if match
-                    tokens = match[1].split(":")
-                    promises.push(@.getAttachmentData(e, tokens))
-
-        links = el.querySelectorAll('img')
-        for e in links
-            if e.getAttribute('src').indexOf('#_taiga-refresh=') != -1
-                match = e.getAttribute('src').match(regex)
-                if match
-                    tokens = match[1].split(":")
-                    promises.push(@.getAttachmentData(e, tokens))
+        _.map [links, images], (tag) =>
+            _.map tag.elements, (e) =>
+                if e.getAttribute(tag.attr).indexOf('#_taiga-refresh=') != -1
+                    match = e.getAttribute(tag.attr).match(regex)
+                    if match
+                        tokens = match[1].split(":")
+                        promises.push(@.getAttachmentData(e, tokens))

         @q.all(promises).then =>
             deferred.resolve(el.innerHTML)


use applications kite to download app sources

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -697,6 +697,7 @@
         return

       @kiteController.run
+        kiteName    : "applications"
         method      : "downloadApp"
         withArgs    :
           owner     : manifest.authorNick


Create appSkeleton for non-blank apps

Create appSkeleton for non-blank apps

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -293,6 +293,7 @@
               appScript = "var appView = KD.instances[\"#{appView.getId()}\"];\n\n"+appScript
               eval appScript
           catch e
+            # if not manifest.ignoreWarnings? # GG FIXME
             warn "App caused some problems:", e
           callback?()
           return appView
@@ -628,20 +629,13 @@
                           """
           , cb

-        # Uncomment followings when we have reachable files for skel of Apps
-        #
-        # stack.push (cb)=>
-        #   @kiteController.run
-        #     withArgs  :
-        #       command : "cp -f /opt/Apps/.default/README #{escapeFilePath fsFolder.path}"
-        #   , cb
-
-        # if not isBlank
-        #   stack.push (cb)=>
-        #     @kiteController.run
-        #       withArgs  :
-        #         command : "cp -rf /opt/Apps/.default/resources #{escapeFilePath fsFolder.path}"
-        #     , cb
+        # Copy default app files (app Skeleton)
+        if type isnt 'blank'
+          stack.push (cb)=>
+            @kiteController.run
+              method        : "copyAppSkeleton"
+              withArgs      : {appPath}
+              , cb

         async.parallel stack, (error, result) =>
           if err then warn err


Teams: Creation: Remember already typed company name in "Create a team" modal page.

--- client/landing/site.landing/coffee/teams/routes.coffee
+++ client/landing/site.landing/coffee/teams/routes.coffee
@@ -27,7 +27,17 @@

     KD.utils.routeIfInvitationTokenIsValid token,
       success   : ({email}) ->
+
+        # Remember already typed companyName when user is seeing "Create a team" page with refresh twice or more
+        if localStorage.teamData
+          storageData = JSON.parse localStorage.teamData
+
+          # Make sure about invitation is same.
+          if token is storageData.invitation?.teamAccessCode and storageData.signup
+            KD.utils.storeNewTeamData 'signup', storageData.signup
+
         KD.utils.storeNewTeamData 'invitation', { teamAccessCode: token, email }
+
         handleRoute { params, query }
       error     : ({responseText}) ->
         new KDNotificationView title : responseText


Added plugins and static to code watch

Added plugins and static to code watch

--- .metalsmith/gulpfile.coffee
+++ .metalsmith/gulpfile.coffee
@@ -19,7 +19,11 @@
 paths =
   code: [
     '!sass/**'
-    '../.metalsmith/*'
+    # I had a hard time ignoring node_modules, so instead
+    # i'm manually adding the dirs i want to watch.
+    '*'
+    'plugins/**'
+    'static/**'
     'templates/**'
   ]
   documents: [


optimization: ensures requester methods are only attached if not already attached.

--- coffee/factories/091_base_requester.tail.coffee
+++ coffee/factories/091_base_requester.tail.coffee
@@ -144,6 +144,8 @@
 	# This is called automatically on all resource instances produced by the actions of this requester; however if you want to attach requester methods
 	# to a resource instance you make, you can do so by calling this with the instance of your resource.
 	$_attachRequesterMethods: ( resourceInstance ) ->
+		return if resourceInstance.$_requesterMethodsAttached
+
 		@$_initResourceStats resourceInstance

 		angular.forEach @$_actions, ( actionDefinition, actionName ) =>
@@ -158,7 +160,7 @@
 			resourceInstance[ "$#{ actionName }" ] = ( parameters, postData, theirSuccess, theirError ) ->
 				unless actionDefinition.$_hasArray
 					parameters = @$_parameters = angular.extend {}, ( actionDefinition.params or {} ), @$_getApiParameters(), parameters
-					postData   = @$_postData   = angular.extend {}, ( actionDefinition.data   or {} ), @$_toJson( @ ),           postData unless actionDefinition.method is 'GET'
+					postData   = @$_postData   = angular.extend {}, ( actionDefinition.data   or {} ), @$_toJson()          , postData unless actionDefinition.method is 'GET'
 				else
 				# If we're calling this on what might be an array of resources, then arg1 is the parameters, and if the action isn't a GET action, then arg2 is the post data, and the remaining arguments are the success and error callbacks
 					parameters = @$_parameters = angular.extend {}, ( actionDefinition.params or {} ), @$_parameters, parameters
@@ -175,6 +177,7 @@

 				actualAction @, actionDefinition, actionName, parameters, postData, theirWrappedSuccess, theirWrappedError

+		resourceInstance.$_requesterMethodsAttached = yes
 		resourceInstance

 	$_action: ( responseResource, actionDefinition, actionName, parameters, postData, theirSuccess, theirError ) =>


Variable name is misleading, use generic 'answer' instead

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -216,9 +216,9 @@
             for choice in value
               @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
           else
-            detailed_solution = @$("#answer_#{key}, #solution_#{key}")
-            detailed_solution.html(value)
-            Collapsible.setCollapsibles(detailed_solution)
+            answer = @$("#answer_#{key}, #solution_#{key}")
+            answer.html(value)
+            Collapsible.setCollapsibles(answer)

         # TODO remove the above once everything is extracted into its own
         # inputtype functions.


Holding down shift adds a comment

Holding down shift adds a comment

--- components/new_comment/client/new_comment_view.coffee
+++ components/new_comment/client/new_comment_view.coffee
@@ -14,6 +14,7 @@
   events:
     'click .new-comment__submit': 'addComment'
     'click' : 'checkClick'
+    'keydown .new-comment__field' : 'onKeyDown'

   initialize: (options)->
     { @block_id, @comments, @autoRender } = options
@@ -25,6 +26,11 @@

   fieldIsEmpty: ->
     @$input.val() is ""
+
+  onKeyDown: (e)->
+    if e.keyCode is 13 and e.shiftKey
+      @addComment()
+      return false

   addComment: ->
     if not @fieldIsEmpty()


added functionality to the Save button

added functionality to the Save button

--- src/main.coffee
+++ src/main.coffee
@@ -114,4 +114,15 @@
   reader.readAsText(file)

 save = ->
-  0
+  console.log('attempting to save')
+  download('file.ctm', textarea.getValue())
+
+download = (filename, text) ->
+  a = $('<a>')
+  a.attr
+    href: 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
+    download: filename
+  a.appendTo($('body'))
+  a[0].click()
+  a.remove()
+  diskOutdated = false


fix concurrency bug

fix concurrency bug


fix hidden inset titleBar on macOS

fix hidden inset titleBar on macOS

--- src/main.coffee
+++ src/main.coffee
@@ -133,7 +133,7 @@
         "min-height": 420
         icon: path.join __dirname, 'icons', icon_name
         show: false
-        titleBarStyle: 'hidden-inset' if process.platform is 'darwin'
+        titleBarStyle: 'hiddenInset' if process.platform is 'darwin'
         frame: false if process.platform is 'win32'
         # autoHideMenuBar : true unless process.platform is 'darwin'
     }


:art:

:art:

--- src/main.coffee
+++ src/main.coffee
@@ -7,6 +7,6 @@
     cachedSpecificity = cache[selector]
     return cachedSpecificity if cachedSpecificity?

-    selectorSpecificity = specificity.calculate(selector)[0]
+    [selectorSpecificity] = specificity.calculate(selector)
     cache[selector] = selectorSpecificity
     selectorSpecificity


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Added dust templating

Added dust templating

--- src/mint.coffee
+++ src/mint.coffee
@@ -27,6 +27,8 @@
         "handlebars"
       when "md", "mkd", "markdown", "mdown"
         "markdown"
+      when "dust"
+        "dust"

   # Pass in path, it computes the extensions and what engine you'll want
   enginesFor: (path) ->
@@ -57,7 +59,7 @@
     @_async engines, iterate, (error) =>
       callback.call(@, error, string)

-  compile: (options, callback) -> # options = { engine: 'jade', template: '<%= %>' }, supported: jade, haml, ejs, eco, handlebars
+  compile: (options, callback) -> # options = { engine: 'jade', template: '<%= %>' }, supported: jade, haml, ejs, eco, handlebars and dust
     engine = require if options.engine is 'haml' then 'hamljs' else options.engine # TODO: make this prettier
     template = options.template or options.content

@@ -200,6 +202,14 @@
       error = e

     callback.call(@, error, result) if callback
+
+  dust: (content, options, callback) ->
+    result = ""
+    require('dust').render content, options.local, (error, data) =>
+      result = data
+      callback.call(@, error, result) if callback
+
+    result

   markdown: (content, options, callback) ->
     error = null


Added abort condition in moloDefine

Added abort condition in moloDefine

--- src/molo.coffee
+++ src/molo.coffee
@@ -47,6 +47,9 @@
       firstScriptElem.parentNode.insertBefore scriptElem, firstScriptElem

   moloDefine = (name, body) ->
+    # Abort if no name or no body
+    return unless name or body
+
     # If name is a function, then just call the function
     if typeof name is 'function' then return name.apply @


Fixed bug causing removeAllDocs() to fire reactively.

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -260,8 +260,12 @@
     df.promise

   removeAllDocs: (collection) ->
-    _.each collection.find().fetch(), (order) ->
-      collection.remove(order._id)
+    docs = null
+    # Non-reactive to ensure this command doesn't re-run when the collection changes.
+    Tracker.nonreactive ->
+      docs = collection.find().fetch()
+    _.each docs, (doc) ->
+      collection.remove(doc._id)

   # @param {Object} doc
   # @param {Object} modifier - A MongoDB modifier object.


make comments about video load events

make comments about video load events

--- app/assets/javascripts/thyme.coffee
+++ app/assets/javascripts/thyme.coffee
@@ -344,6 +344,10 @@
   initialChapters = true
   initialMetadata = true
   video.addEventListener 'canplay', ->
+    # Originally (and more apprpriately, according to the standards),
+    # the 'loadedmetadata' event was user. However, Firefox triggers this event to soon,
+    # i.e. when the readyStates for chapters and elements are 1 (loading) instead of 2 (loaded)
+    # for the events, see https://www.w3schools.com/jsref/event_oncanplay.asp
     # console.log 'chapters.readyState:' + chaptersElement.readyState
     # console.log 'metadata.readyState:' + metadataElement.readyState
     # console.log 'video.readyState:' + video.readyState


Doesn't have to be on the window

Doesn't have to be on the window

--- core/app/backbone/controllers/facts_controller.coffee
+++ core/app/backbone/controllers/facts_controller.coffee
@@ -12,7 +12,7 @@
       success: (model, response) => @withFact(model)

   withFact: (fact)->
-    window.dv = new DiscussionView(model: fact)
+    dv = new DiscussionView(model: fact)
     @main.contentRegion.show(dv)

     window.extended_fact_title_view = new ExtendedFactTitleView( model: fact )


Remove debug output

Remove debug output

--- src/msgc.coffee
+++ src/msgc.coffee
@@ -29,17 +29,9 @@
       "room": envelope.room,
       "text": out.join('')
     }
-    console.log("textMessage:")
-    console.log(textMessage)
-    console.log("End textMessage")
     json = JSON.stringify(textMessage)

-    console.log("json:")
-    console.log(json)
-    console.log("end json")
-
     @wss.clients.forEach (client) =>
-      console.log("Sending #{json}")
       client.send(json)

   emote: (envelope, strings...) ->


Backend can now display word clouds from several games combined

'admin/games/10,9,8' - ids as csv

--- pages/games.coffee
+++ pages/games.coffee
@@ -1,29 +1,31 @@
 module.exports = (app, DbHelper) ->
-  return if !DbHelper.db?
-  app.get '/admin/games', (req, res) ->
-    DbHelper.db('games').select(['id', 'topic', 'started_at']).orderBy('started_at', 'desc').map (row)->
-      "#{row.topic} - #{row.started_at}"
-    .reduce((content, row) ->
-      return content + row + '
 '
-    , '')
-    .then (content) ->
-      res.send(content)
-    .catch (error) ->
-      console.log(error)
+  if !DbHelper.db?
+    app.get '/admin/*', (req, res) ->
+      res.send "database disabled :("
+  else
+    app.get '/admin/games', (req, res) ->
+      DbHelper.db('games').select(['id', 'topic', 'started_at']).orderBy('started_at', 'desc').map (row)->
+        "#{row.topic} - #{row.started_at}"
+      .reduce((content, row) ->
+        return content + row + '
 '
+      , '')
+      .then (content) ->
+        res.send(content)
+      .catch (error) ->
+        console.log(error)

-
-  app.get '/admin/games/:id', (req, res) ->
-    id = parseInt req.params.id
-    DbHelper.db('word_submissions').where({game_id: id})
-    .select(DbHelper.db.raw('word, COUNT(word) as frequency'))
-    .groupBy('word')
-    .map (row)->
-      {text: row.word, size: row.frequency * 10}
-    .then (content) ->
-      # res.send JSON.stringify(content)
-      res.render 'game',
-        words: content
-        helpers:
-          json: (val)-> JSON.stringify(val)
-    .catch (error) ->
-      console.log(error)
+    app.get '/admin/games/:ids', (req, res) ->
+      ids = req.params.ids.split(',').map (e)->(parseInt e)
+      DbHelper.db('word_submissions').whereIn('game_id', ids)
+      .select(DbHelper.db.raw('word, COUNT(word) as frequency'))
+      .groupBy('word')
+      .map (row)->
+        {text: row.word, size: row.frequency * 10}
+      .then (content) ->
+        # res.send JSON.stringify(content)
+        res.render 'game',
+          words: content
+          helpers:
+            json: (val)-> JSON.stringify(val)
+      .catch (error) ->
+        console.log(error)


temp commit for tests to pass

temp commit for tests to pass

--- bokehjs/src/coffee/models/glyphs/glyph.coffee
+++ bokehjs/src/coffee/models/glyphs/glyph.coffee
@@ -193,15 +193,16 @@

     # if we have any coordinates that are categorical, convert them to
     # synthetic coords here
-    xr = @renderer.plot_view.frame.x_ranges[@model.x_range_name]
-    yr = @renderer.plot_view.frame.y_ranges[@model.y_range_name]
-    for [xname, yname] in @model._coords
-      xname = "_#{xname}"
-      yname = "_#{yname}"
-      if xr.v_synthetic?
-        @[xname] = xr.v_synthetic(@[xname])
-      if yr.v_synthetic?
-        @[yname] = yr.v_synthetic(@[yname])
+    if @renderer.plot_view.frame.x_ranges?   # XXXX JUST TEMP FOR TESTS TO PASS
+      xr = @renderer.plot_view.frame.x_ranges[@model.x_range_name]
+      yr = @renderer.plot_view.frame.y_ranges[@model.y_range_name]
+      for [xname, yname] in @model._coords
+        xname = "_#{xname}"
+        yname = "_#{yname}"
+        if xr.v_synthetic?
+          @[xname] = xr.v_synthetic(@[xname])
+        if yr.v_synthetic?
+          @[yname] = yr.v_synthetic(@[yname])

     if @glglyph?
       @glglyph.set_data_changed(@_x.length)


Updated for removed ackbar images

Updated for removed ackbar images

--- src/scripts/ackbar.coffee
+++ src/scripts/ackbar.coffee
@@ -14,18 +14,15 @@
 #   brilliantfantastic

 ackbars = [
-  "http://dayofthejedi.com/wp-content/uploads/2011/03/171.jpg",
-  "http://dayofthejedi.com/wp-content/uploads/2011/03/152.jpg",
+  "http://i.imgur.com/OTByx1b.jpg",
   "http://farm4.static.flickr.com/3572/3637082894_e23313f6fb_o.jpg",
   "http://6.asset.soup.io/asset/0610/8774_242b_500.jpeg",
   "http://files.g4tv.com/ImageDb3/279875_S/steampunk-ackbar.jpg",
   "http://farm6.staticflickr.com/5126/5725607070_b80e61b4b3_z.jpg",
   "http://farm6.static.flickr.com/5291/5542027315_ba79daabfb.jpg",
-  "http://farm5.staticflickr.com/4074/4751546688_5c76b0e308_z.jpg",
   "http://farm6.staticflickr.com/5250/5216539895_09f963f448_z.jpg",
   "http://static.fjcdn.com/pictures/Its_2031a3_426435.jpg",
   "http://www.millionaireplayboy.com/mpb/wp-content/uploads/2011/01/1293668358_bottom_trappy.jpeg",
-  "http://allthingsackbar.com/wp-content/uploads/2010/02/bottom_ackbar_trap-500x446.gif",
   "http://31.media.tumblr.com/tumblr_lqrrkpAqjf1qiorsyo1_500.jpg",
   "https://i.chzbgr.com/maxW500/4930876416/hB0F640C6/",
   "http://i.qkme.me/356mr9.jpg",


[CS2]: Fix #4591: multiple accesses after super (#4592)

* support multiple accesses after super [Fixes #4591]

* addtl tests from code review

--- test/classes.coffee
+++ test/classes.coffee
@@ -1808,3 +1808,28 @@

   {derivedBound} = a
   eq derivedBound(), 3
+
+test "#4591: super.x.y, super['x'].y", ->
+  class A
+    x:
+      y: 1
+      z: -> 2
+
+  class B extends A
+    constructor: ->
+      super()
+
+      @w = super.x.y
+      @v = super['x'].y
+      @u = super.x['y']
+      @t = super.x.z()
+      @s = super['x'].z()
+      @r = super.x['z']()
+
+  b = new B
+  eq 1, b.w
+  eq 1, b.v
+  eq 1, b.u
+  eq 2, b.t
+  eq 2, b.s
+  eq 2, b.r


Tests: Don't override whole env, just add our keys

Probably we were missing some custom PATH or NODE_HOME,
or something like it on Travis CI


do not try to compile traces of coffee within node_modules

--- command.coffee
+++ command.coffee
@@ -95,7 +95,7 @@
         console.error "File not found: #{source}"
         process.exit 1
       return
-    if stats.isDirectory()
+    if stats.isDirectory() and path.dirname(source) isnt 'node_modules'
       watchDir source, base if opts.watch
       fs.readdir source, (err, files) ->
         throw err if err and err.code isnt 'ENOENT'


Remove bootstrap modal dependency

Remove bootstrap modal dependency

--- src/scripts/loader.coffee
+++ src/scripts/loader.coffee
@@ -6,7 +6,6 @@
   analytics = require('cs!helpers/handlers/analytics') # Setup Analytics Handler
   require('cs!helpers/backbone/history') # Extend Backbone.history to support query strings
   require('less!../styles/main')
-  require('bootstrapModal')

   # The root URI prefixed on all non-external AJAX and Backbone URIs
   root = settings.root


Add short responses

Add short responses

--- src/scripts/polite.coffee
+++ src/scripts/polite.coffee
@@ -12,6 +12,14 @@
   "Don't mention it."
 ]

+shortResponses = [
+  'vw',
+  'np',
+]
+
 module.exports = (robot) ->
   robot.respond /(thanks|thank you|cheers|nice one)/i, (msg) ->
     msg.send msg.random responses
+
+  robot.respond /(ty|thx)/i, (msg) ->
+    msg.send msg.random shortResponses


some skit

some skit

--- js/views/destination.coffee
+++ js/views/destination.coffee
@@ -82,4 +82,29 @@
       utils.locations.save()
       $.ajaxSetup({global: true});

+    fonectaLocations: (path) ->
+      distance = 5.0
+      routeBoxer = new RouteBoxer()
+      boxes = routeBoxer.box(path, distance)
+      utils.locations = new Locations()
+      for box in boxes
+        $.ajax
+          url: "http://developer.fonecta.net/osuma/resource/search/osuma/companies/boundingbox"
+          data:
+            minLatitude: box.ca.b
+            maxLatitude: box.ca.f
+            minLongitude: box.ea.b
+            maxLongitude: box.ea.f
+          dataType: "json"
+          async: false
+          global: false
+          success: (data) =>
+            if data.length > 0
+              for poi in data
+                location = new Location(poi)
+                utils.locations.add location
+            return
+      utils.locations.save()
+      $.ajaxSetup({global: true});
+
   views.destination = new Destination


DE24349: fixing test due to ReleasePlanning refactor

--- test/spec/releaseplanning/ReleasePlanningAppSpec.coffee
+++ test/spec/releaseplanning/ReleasePlanningAppSpec.coffee
@@ -36,6 +36,8 @@
       context: Ext.create 'Rally.app.Context',
         initialValues:
           project:
+            Children:
+              Count: 0
             _ref: @releaseData[0].Project._ref
           subscription: Rally.environment.getContext().getSubscription()
           workspace: Rally.environment.getContext().getWorkspace()
@@ -55,7 +57,7 @@

   it 'should order the release columns based on end date', ->
     sortedReleaseNames = _.pluck _.sortBy(@releaseData, (release) -> new Date(release.ReleaseDate)), 'Name'
-    expect(_.map(@getTimeboxColumns(), (column) -> column.getTimeboxRecords()[0].get('Name'))).toEqual sortedReleaseNames
+    expect(_.map(@getTimeboxColumns(), (column) -> column.getTimeboxRecord().get('Name'))).toEqual sortedReleaseNames

   it 'should use rallygridboard custom filter control', ->
     gridBoard = @app.down 'rallygridboard'


Correct namespace for commit request

Correct namespace for commit request

--- app/assets/javascripts/backbone/models/commit_request.js.coffee
+++ app/assets/javascripts/backbone/models/commit_request.js.coffee
@@ -1,4 +1,4 @@
-class CommitRequest extends Backbone.Model
+class Water.CommitRequest extends Backbone.Model
   initialize: () ->
     # do stuff


`Bandango.SessionController#initializeWithStore` bugfix

make sure there is a user ready to login before trying to actually
perform the login

--- app/assets/javascripts/controllers/session_controller.js.coffee
+++ app/assets/javascripts/controllers/session_controller.js.coffee
@@ -1,6 +1,7 @@
 Bandango.SessionController = Ember.Controller.extend
   initializeWithStore: (store) ->
-    @set "user", store.push("user", @get("userProperties"))
+    if userProperties = @get("userProperties")
+      @set "user", store.push("user", userProperties)

   willLoginAs: (userProperties) ->
     @set "userProperties", userProperties


Remove an unnecessary proxy call

Remove an unnecessary proxy call

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -26,7 +26,7 @@
   # routines will also listen for changes on the element to propagate them back
   # to the model.
   bind: =>
-    Rivets.config.adapter.subscribe @model, @keypath, (value) => @set value
+    Rivets.config.adapter.subscribe @model, @keypath, @set

     if Rivets.config.preloadData
       @set Rivets.config.adapter.read @model, @keypath
@@ -144,4 +144,4 @@
 if module?
   module.exports = rivets
 else
-  @rivets = rivets+  @rivets = rivets


router: fix

router: fix

leftover from sync refactoring

--- src/router.coffee
+++ src/router.coffee
@@ -238,7 +238,7 @@
         opts.rsm = new RSM()
         rsmWalk (nextOffset, cb2) =>
             opts.rsm.after = nextOffset
-            router.runRemotely opts, (err, results) =>
+            @runRemotely opts, (err, results) =>
                 if err
                     return cb2 err


fixed typo

fixed typo

--- bokehjs/src/coffee/widget/tabs.coffee
+++ bokehjs/src/coffee/widget/tabs.coffee
@@ -42,7 +42,7 @@
         return panel.id == panelId
       ))
       that.model.save('active', panelIdx)
-      that.model.get('callback')?.execute(@model)
+      that.model.get('callback')?.execute(that.model)

     $panels = html.children(".bk-bs-tab-pane")


namespaced Movable

namespaced Movable


minor: codestyling

minor: codestyling

--- client/app/lib/environment/environmentlistitem.coffee
+++ client/app/lib/environment/environmentlistitem.coffee
@@ -16,6 +16,7 @@
   constructor: (options = {}, data) ->

     options.cssClass = kd.utils.curry 'environment-item', options.cssClass
+
     super options, data

     { machines, _revisionStatus } = stack = @getData()


Runner typo fix

Runner typo fix

--- src/runner.coffee
+++ src/runner.coffee
@@ -75,5 +75,5 @@
         errorCount = do @_errorHandler.getErrorCount
         @logger.log(
           if errorCount then 'error' else 'info',
-          "\nFinished in #{do @_timer.getOverall} sec " + if errorCount then " with error count: #{errorCount}" else '')
+          "\nFinished in #{do @_timer.getOverall} sec" + if errorCount then " with error count: #{errorCount}" else '')
         process.exit errorCount


read schema from env

read schema from env

--- src/plv8.coffee
+++ src/plv8.coffee
@@ -4,12 +4,14 @@
 global.DEBUG="DEBUG"

 conn_string = process.env.DATABASE_URL
+schema = process.env.FB_SCHEMA || 'public'

 unless conn_string
   throw new Error("set connection string \n export DATABASE_URL=postgres://user:password@localhost:5432/test")

 client = new Client
 client.connectSync(conn_string)
+

 coerse = (x)->
   if Array.isArray(x) or typeof x == 'object'
@@ -26,6 +28,8 @@
       else
         obj[k] = x[k]
     obj
+
+execute "SET search_path = '#{schema}'"

 module.exports =
   execute: execute


Put file tree header back. Devrim's special request.

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -195,6 +195,9 @@
       {{> @footerMenu}}

+

+        {{> @finderHeader}}
+

         {{> @finder}}


Use Chrome's keybinding for closing every open tab

--- keymap.cson
+++ keymap.cson
@@ -42,7 +42,7 @@
 	"shift-ctrl-o":   "application:open-dev"
 	"shift-ctrl-r":   "window:reload"
 	"shift-ctrl-b":   "user:toggle-bracket-matcher"
-	"shift-ctrl-w":   "editor:toggle-soft-wrap"
+	"shift-ctrl-w":   "tabs:close-all-tabs"
 	"alt-ctrl-r":     "window:run-package-specs"
 	"ctrl-alt-w":     "window:toggle-invisibles"
 	"ctrl-alt-p":     "project-manager:list-projects"
@@ -134,7 +134,7 @@
 	"alt-shift-w":    "window:close"
 	"shift-cmd-b":    "user:toggle-bracket-matcher"
 	"ctrl-shift-r":   "window:run-package-specs"
-	"shift-cmd-w":    "editor:toggle-soft-wrap"
+	"shift-cmd-w":    "tabs:close-all-tabs"
 	"cmd-alt-w":      "window:toggle-invisibles"
 	"cmd-0":          "unset!"
 	"cmd-k up":       "unset!"


Close #11: Order user by full_name

Close #11: Order user by full_name

--- clients/antranet/app/coffee/controllers/quotes.coffee
+++ clients/antranet/app/coffee/controllers/quotes.coffee
@@ -13,7 +13,7 @@
         }

         rs.listUsers().then (data) ->
-            $scope.employees = data
+            $scope.employees = _.sortBy(data, "full_name")

             rs.listPaginatedQuotes(params).then (data) ->
                 $scope.quotes = data.models


Adds And and Scenario for mocha-cakes compat

--- src/scones.coffee
+++ src/scones.coffee
@@ -17,7 +17,7 @@
       fn()
       suites.shift()

-    context.Feature = (message, fn) =>
+    context.Feature = context.Scenario = (message, fn) =>
       suite = Suite.create(suites[0], message)
       suite.scone_type = 'Feature'
       suites.unshift(suite)
@@ -31,4 +31,7 @@
       Step suites[0],'When ' + message, fn

     context.Then = (message, fn) =>
-      Step suites[0],'Then ' + message, fn+      Step suites[0],'Then ' + message, fn
+
+    context.And = (message, fn) =>
+      Step suites[0], 'And ' + message, fn


More specs

More specs

--- src/spec/inventory-updater.spec.coffee
+++ src/spec/inventory-updater.spec.coffee
@@ -133,3 +133,90 @@
   it 'should return entry based on sku', ->
     entry = @updater.match sku: "foo"
     expect(entry).toEqual { id: "channel123", sku: "foo" }
+
+describe '#update', ->
+  beforeEach ->
+    @updater = new InventoryUpdater config: Config
+
+  it 'should return a promise', ->
+    spyOn(@updater.sync, "buildActions").andReturn(@updater.sync)
+    spyOn(@updater.sync, "update")
+    promise = @updater.update()
+    expect(Q.isPromise(promise)).toBe true
+    expect(@updater.sync.buildActions).toHaveBeenCalled()
+    expect(@updater.sync.update).toHaveBeenCalled()
+
+  it 'should reject if error', ->
+    spyOn(@updater.sync, "buildActions").andReturn(@updater.sync)
+    spyOn(@updater.sync, "update").andCallFake((callback)-> callback("foo", null, null))
+
+    @updater.update().then (result) =>
+      expect(result).not.toBeDefined()
+      done()
+    .fail (msg)->
+      expect(msg).toBe 'Error on updating inventory entry: foo'
+      done()
+
+  it 'should reject if there was a problem during update', ->
+    spyOn(@updater.sync, "buildActions").andReturn(@updater.sync)
+    spyOn(@updater.sync, "update").andCallFake((callback)-> callback(null, {statusCode: 500}, "foo"))
+
+    @updater.update().then (result) =>
+      expect(result).not.toBeDefined()
+      done()
+    .fail (msg)->
+      expect(msg).toBe 'Problem on updating existing inventory entry: foo'
+      done()
+
+  it 'should return message that entry was updated', ->
+    spyOn(@updater.sync, "buildActions").andReturn(@updater.sync)
+    spyOn(@updater.sync, "update").andCallFake((callback)-> callback(null, {statusCode: 200}, null))
+
+    @updater.update().then (result) =>
+      expect(result).toBe 'Inventory entry updated.'
+      done()
+
+  it 'should return message that updated was not necessary', ->
+    spyOn(@updater.sync, "buildActions").andReturn(@updater.sync)
+    spyOn(@updater.sync, "update").andCallFake((callback)-> callback(null, {statusCode: 304}, null))
+
+    @updater.update().then (result) =>
+      expect(result).toBe 'Inventory entry update not neccessary.'
+      done()
+
+describe '#create', ->
+  beforeEach ->
+    @updater = new InventoryUpdater config: Config
+
+  it 'should return a promise', ->
+    spyOn(@updater.rest, "POST")
+    promise = @updater.create()
+    expect(Q.isPromise(promise)).toBe true
+    expect(@updater.rest.POST).toHaveBeenCalled()
+
+  it 'should reject if error', ->
+    spyOn(@updater.rest, "POST").andCallFake((path, payload, callback)-> callback("foo", null, null))
+
+    @updater.create().then (result) =>
+      expect(result).not.toBeDefined()
+      done()
+    .fail (msg)->
+      expect(msg).toBe 'Error on creating new inventory entry: foo'
+      done()
+
+  it 'should reject if there was a problem during create', ->
+    spyOn(@updater.rest, "POST").andCallFake((path, payload, callback)-> callback(null, {statusCode: 500}, "foo"))
+
+    @updater.create().then (result) =>
+      expect(result).not.toBeDefined()
+      done()
+    .fail (msg)->
+      expect(msg).toBe 'Problem on creating new inventory entry: foo'
+      done()
+
+  it 'should return message that entry was created', ->
+    spyOn(@updater.rest, "POST").andCallFake((path, payload, callback)-> callback(null, {statusCode: 201}, null))
+
+    @updater.create().then (result) =>
+      expect(result).toBe 'New inventory entry created.'
+      done()


make partialize return partial instead of parent

--- src/util.coffee
+++ src/util.coffee
@@ -29,6 +29,8 @@
         this

 # takes a function to create a clojure to call partial on an element
-exports.partialize = (partial, moargs...) ->
+exports.partialize = (create, moargs...) ->
     return (args...) ->
-        @partial partial.call(this, moargs..., args...)
+        partial = create.call(this, moargs..., args...)
+        @partial partial
+        return partial


Remove Console Logging

Remove Console Logging

--- src/server.coffee
+++ src/server.coffee
@@ -23,7 +23,6 @@

   for method, commands of singular_commands
     for command in commands
-      console.log command
       router.route method, "/api/queues/:name/#{command}", do (command) ->
         (req, res) ->
           queue = fairy.queue req.params.name


Remove unused methods from tool.

Remove unused methods from tool.

--- app/scripts/controllers/tool.coffee
+++ app/scripts/controllers/tool.coffee
@@ -37,20 +37,7 @@
           for e in @canvas.selection()
             e.remove()
             @run(new DeleteElement(@drawing, e.element))
-          @clearSelection()
+          @canvas.clearSelection()

         else if (event.key is 'z')
-          @commander.undo()
-
-    changeSelectionTo: (nodeOrLink) ->
-      @clearSelection()
-      @select(nodeOrLink)
-
-    clearSelection: ->
-      @drawing.clearSelection()
-
-    select: (nodeOrLink) ->
-      @drawing.select(nodeOrLink)
-
-    selection: ->
-      @drawing.selection
+          @commander.undo()


Change SimpleSet#forEach from the JS forEach implementation to for

--- src/set/simple_set.coffee
+++ src/set/simple_set.coffee
@@ -35,8 +35,7 @@
       return item if f(item)
     return
   forEach: (iterator, ctx) ->
-    container = this
-    @_storage.slice().forEach (key) -> iterator.call(ctx, key, null, container)
+    iterator.call(ctx, key, null, this) for key in @_storage
   isEmpty: -> @length is 0
   clear: ->
     items = @_storage


Add autocomplete:next/previous events

Add autocomplete:next/previous events

--- src/packages/autocomplete/lib/autocomplete-view.coffee
+++ src/packages/autocomplete/lib/autocomplete-view.coffee
@@ -27,6 +27,8 @@
   handleEvents: ->
     @editor.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer())
     @editor.command 'autocomplete:attach', => @attach()
+    @editor.command 'autocomplete:next', => @selectNextItem()
+    @editor.command 'autocomplete:previous', => @selectPreviousItem()

     @miniEditor.preempt 'textInput', (e) =>
       text = e.originalEvent.data


Remove unused require

Remove unused require

--- src/span-skip-list.coffee
+++ src/span-skip-list.coffee
@@ -1,4 +1,4 @@
-{random, isEqual} = require 'underscore'
+{isEqual} = require 'underscore'

 clone = (object) ->
   cloned = {}


Updated example using @epidemian's suggestion

--- documentation/coffee/switch_with_no_expression.coffee
+++ documentation/coffee/switch_with_no_expression.coffee
@@ -1,8 +1,8 @@
 score = 76
 grade = switch
-  when 90 <= score      then 'A'
-  when 80 <= score < 90 then 'B'
-  when 70 <= score < 80 then 'C'
-  when 60 <= score < 70 then 'D'
-  when       score < 60 then 'F'
+  when score < 60 then 'F'
+  when score < 70 then 'D'
+  when score < 80 then 'C'
+  when score < 90 then 'B'
+  else 'A'
 # grade == 'C'


Add support for Balena in local ssh

Add support for Balena in local ssh

Change-Type: minor

--- lib/actions/local/ssh.coffee
+++ lib/actions/local/ssh.coffee
@@ -103,7 +103,8 @@

 			if not options.host
 				shellCmd = '''/bin/sh -c $"'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi'"'''
-				command += " docker exec -ti #{container} #{shellCmd}"
+				dockerCmd = "'$(if [ -f /usr/bin/balena ]; then echo \"balena\"; else echo \"docker\"; fi)'"
+				command += " #{dockerCmd} exec -ti #{container} #{shellCmd}"

 			subShellCommand = getSubShellCommand(command)
 			child_process.spawn subShellCommand.program, subShellCommand.args,


Cleanup

Cleanup


Camera: Added look_at method.

Camera: Added look_at method.

--- engine/camera.coffee
+++ engine/camera.coffee
@@ -69,6 +69,28 @@
         vec3.transformMat4 out, out, @projection_matrix_inv
         vec3.transformQuat out, out, @rotation
         return out
+
+    look_at: (target)->
+        # world up is Z
+        # cam up is Y
+        # Z
+        view = vec3.sub vec3.create(), @.get_world_position(), target
+        vec3.normalize view, view
+        # Y
+        up = vec3.new 0,0,1
+        # X
+        side = vec3.cross vec3.create(), up, view
+        vec3.normalize side, side
+        up = vec3.cross up, view, side
+        vec3.normalize up, up
+        m3 = mat3.fromColumns mat3.create(), side, up, view
+        @rotation_order = 'Q'
+        quat.set @rotation, 0,0,0,1
+        rot = @get_world_rotation()
+        quat.invert rot, rot
+        quat.fromMat3 @rotation, m3
+        quat.mul @rotation, rot, @rotation
+

     is_vertical_fit: ->
         switch @sensor_fit


router: custom handlers should still se the page title

--- core/KD.coffee
+++ core/KD.coffee
@@ -155,19 +155,28 @@
     options.routes       or= null         # <string> or <Object{slug: string, handler: function}>
     options.styles       or= []           # <Array<string>> list of stylesheets

+    wrapHandler = (fn, options) -> ->
+      router = KD.getSingleton 'router'
+      router.setPageTitle? options.navItem.title  if options.navItem.title
+      fn.apply this, arguments
+
     registerRoute = (route, handler)=>
       slug        = if "string" is typeof route then route else route.slug
       route       =
         slug      : slug or '/'
         handler   : handler or route.handler or null

-
       if route.slug isnt '/'

         {slug, handler} = route
         cb = (router)->
-          handler or= ({params:{name}, query})->
-            router.openSection options.name, name, query
+          handler =
+            if handler
+            then wrapHandler handler, options
+            else
+              ({params:{name}, query})->
+                router.openSection options.name, name, query
+
           router.addRoute slug, handler

         if KD.singletons.router


Include colon as a word boundary

Include colon as a word boundary

--- src/packages/spell-check/lib/spell-check-handler.coffee
+++ src/packages/spell-check/lib/spell-check-handler.coffee
@@ -2,7 +2,7 @@

 module.exports =
   findMisspellings: (text) ->
-    wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]]|$)/g
+    wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]:]|$)/g
     row = 0
     misspellings = []
     for line in text.split('\n')


allow dots in shortNames and displayNames

--- server/code/model/user.coffee
+++ server/code/model/user.coffee
@@ -39,8 +39,8 @@

   validate: ->
     # TODO: proper regex, share validation across server & client
-    return 'invalid shortName' unless /^[a-zA-Z0-9-]+$/g.test @shortName
-    return 'invalid displayName' unless /^[a-zA-Z0-9- ]+$/g.test @displayName
+    return 'invalid shortName' unless /^[a-zA-Z0-9-.]+$/g.test @shortName
+    return 'invalid displayName' unless /^[a-zA-Z0-9-. ]+$/g.test @displayName
     return 'invalid email' unless /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/gi.test @email[0]

   checkPassword: (password, callback) ->


make JS logging conditional on hatTrick.debug property

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -4,6 +4,8 @@
   constructor: (formElem, @wizard) ->
     @form = $(formElem)
     this.enableFormwizard()
+
+  debug: true

   linkClass: "_ht_link"

@@ -437,5 +439,5 @@
   result

 log = (msg) ->
-  if window['console']?
+  if window['console']? and hatTrick.debug
     console.log msg


Fixes #162: Handle negated Grunt CLI options when adding to Lineman's CLI options

--- lib/cli/setup-options.coffee
+++ lib/cli/setup-options.coffee
@@ -5,7 +5,7 @@
   commander.option("--skip-examples", "lineman new - Skips the 'hello world' and other example files when creating a new project")
   _(gruntCli.optlist).each (option, name) ->
     unless name in ["help", "version"]
-      desc = "--#{name}"
+      desc = if option.negate then "--no-#{name}" else "--#{name}"
       desc = "-#{option.short}, #{desc}" if option.short
       commander.option(desc, "grunt - #{option.info}")


fix tab-stops

fix tab-stops

--- snippets/functions.cson
+++ snippets/functions.cson
@@ -1,7 +1,7 @@
 ".source.c.scumm":
   "abs()":
     prefix: "abs()"
-    body: "abs( ${1:val} )$1"
+    body: "abs( ${1:val} )$2"
   "actorFace()":
     prefix: "actorFace()"
     body: "actorFace( ${1:actor}, ${2:obj} )$3"
@@ -49,7 +49,7 @@
     body: "cursorOn()$1"
   "delay()":
     prefix: "delay()"
-    body: "delay( ${1:ticks} )$1"
+    body: "delay( ${1:ticks} )$2"
   "delayMinutes()":
     prefix: "delayMinutes()"
     body: "delayMinutes( ${1:min} )$2"
@@ -226,7 +226,7 @@
     body: "isActorInBox( ${1:actor}, ${2:box} )$3"
   "isActorMoving()":
     prefix: "isActorMoving()"
-    body: "isActorMoving( ${1:actor} )$1"
+    body: "isActorMoving( ${1:actor} )$2"
   "isAnyOf()":
     prefix: "isAnyOf()"
     body: "isAnyOf( ${1:val}, ${2:[ ${3:list} ]} )$4"
@@ -478,10 +478,10 @@
     body: "setCharsetColors( ${1:[ ${2:colors} ]} )$3"
   "setCurrentActor()":
     prefix: "setCurrentActor()"
-    body: "setCurrentActor( ${1:actor} )$1"
+    body: "setCurrentActor( ${1:actor} )$2"
   "setCurrentVerb()":
     prefix: "setCurrentVerb()"
-    body: "setCurrentVerb( ${1:verb} )$1"
+    body: "setCurrentVerb( ${1:verb} )$2"
   "setCursorHotspot()":
     prefix: "setCursorHotspot()"
     body: "setCursorHotspot( ${1:x}, ${2:y} )$3"


Rename args to options to match other commands

--- src/test.coffee
+++ src/test.coffee
@@ -38,11 +38,11 @@

   run: (options) ->
     {callback} = options
-    args = @parseOptions(options.commandArgs)
+    options = @parseOptions(options.commandArgs)
     {env} = process

-    if args.argv.path
-      atomCommand = args.argv.path
+    if options.argv.path
+      atomCommand = options.argv.path
     else if process.platform is 'win32'
       atomCommand = @getChocolateyAtomPath()
     atomCommand = 'atom' unless fs.existsSync(atomCommand)


more notes and examples on dot notation

more notes and examples on dot notation

--- tests.coffee
+++ tests.coffee
@@ -23,7 +23,7 @@
   # the most technically thorough but it's
   # more difficult to decypher.
   #
-  # The dot separates stataments.
+  # The semicolon separates stataments.
   "'a ← \"test string\"; 'b ← a; 'c ← 'a; 'a eval print;'b eval print;'c eval print"
   "test stringtest stringa"

@@ -208,7 +208,8 @@
   # "a" doesn't know what to do with it, so it returns itself
   # and "b" remains unconsumed.
   # So the assignment will assign "a" to a, then it will mandate
-  # a new receiver. The new receiver will be "b. a print" (the dot
+  # a new receiver. The new receiver will be "b. a print" (the
+  # semicolon separating the statements
   # comes from the linearisation), which was still
   # there to be consumed. "b." will just return itself and do nothing,
   # so then "a print" will be run, which results in "a"
@@ -570,9 +571,15 @@
   "object_from_a_user_class"

   # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
-  "MyClass=Class new;\
-    MyClass answer(printtwo)by(self print);\
-    myObject=MyClass new;myObject printtwo"
+  """
+  MyClass = Class new
+  MyClass answer
+  ﹍printtwo
+  by
+  ﹍self print
+  myObject = MyClass new
+  myObject printtwo
+  """
   "object_from_a_user_class"

   # ---------------------------------------------------------------------------
@@ -681,7 +688,7 @@
   # its argument, so the passed list
   # is evaluated. If you want to pass
   # a list you need to quote it, see
-  # afterwards.
+  # afterwards
   "('(1)+(2))print"
   "( 1 2 )"

@@ -727,6 +734,7 @@

   # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-

+  # dot notation here
   """
   MyClass = Class new
   MyClass idict = counter
@@ -791,6 +799,7 @@

   # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-

+  # more dot notation
   """
   codeToBeRun ='
   ﹍counter=2
@@ -860,6 +869,36 @@
   "236"

   # ---------------------------------------------------------------------------
+  # note that while the dot notation can be used to access variables,
+  # and in theory it could be used to invoke methods without
+  # parameters, it can't be used to invoke methods with parameters
+  # (you can just omit the dot and it works though)
+
+  """
+  MyClass = Class new
+  MyClass answer
+  ﹍printtwo (argument)
+  by
+  ﹍argument print
+  myObject = MyClass new
+  myObject printtwo "hello"
+  """
+  "hello"
+
+  # -.-.-.-.-.-.-.-.--.-             vs.             .--.-.-.--.-.-.-.-.-.-.-.-
+
+  """
+  MyClass = Class new
+  MyClass answer
+  ﹍printtwo (argument)
+  by
+  ﹍argument print
+  myObject = MyClass new
+  myObject.printtwo "hello"
+  """
+  "! no meaning found for: printtwo was sent message: ( \"hello\" )"
+
+  # ---------------------------------------------------------------------------
   "'MyClass←Class new;MyClass cvar classCounter ← 0;\
     MyClass answer(incrementClassCounterByTwo)by('classCounter←classCounter plus 2);\
     MyClass answer(printClassCounter)by(classCounter print);\
@@ -910,7 +949,7 @@
   # ---------------------------------------------------------------------------
   # note here how the list is evaluated. Because it's a "wrapped" list,
   # its evaluation is the unwrapped content, so it's the list
-  # as you expect it.
+  # as you expect it

   """
   for each word in


Activity: Add showAllChatsModal to AppView

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -170,3 +170,11 @@
     @open 'topic', 'public'  unless @tabs.getActivePane()

     return new ConversationsModal delegate : this
+
+
+  showAllChatsModal: ->
+
+    @open 'topic', 'public'  unless @tabs.getActivePane()
+
+    return new ChatSearchModal delegate : this
+


Better queuing of errors.

Better queuing of errors.

--- lib/docxconv/docxconv.coffee
+++ lib/docxconv/docxconv.coffee
@@ -24,11 +24,11 @@
         msg.log "error", "[!] #{errorCount} files not converted:"
         msg.log "error", "#{error.error}: #{error.file}" for error in @errors
         msg.log "warn", "[@] Trying again..."
-        for error in @errors
-          do (error) =>
-            @convert error.file, (err)->
-              if err
-                msg.log "error", "[!] Still can’t convert #{error.file}"
+        while @errors.length > 0
+          error = @errors.shift()
+          @convert error.file, (err)->
+            if err
+              msg.log "error", "[!] Still can’t convert #{error.file}"

       if @watch
         console.log "Still watching #{args['watch-dir']} for new files..."
@@ -143,8 +143,6 @@
       callback(null, result)

   convert: (batch, callback) =>
-    @errors = []
-
     if Array.isArray(batch)
       batchSize = batch.length
     else


added modal dialog, quality decrased to 20

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -53,72 +53,84 @@
       type:'string'
       default:''
       order:1
-      description:"WARNING!!! NOT FINISHED YET - WORK IN PROGRESS"
+      description:"WARNING!!! NOT FINISHED YET - WORK IN PROGRESS,
+      but if you are really curious try to find 'background loop' in YT"
+    startTime:
+      type:"integer"
+      default:0
+      order:2
+      description:"video start time in miliseconds ( 1s = 1000ms )"
+    endTime:
+      type:"integer"
+      default:10000
+      maximum:30000
+      description:"video end time in miliseconds (max 30 000 = 30s)"
+      order:3
     textBackground:
       type:"color"
       default:"rgb(0,0,0)"
-      order:2
+      order:4
       description:"background color for text/code"
     textBackgroundOpacity:
       type:"integer"
       default:100
-      order:3
+      order:5
     textBackgroundBlurRadius:
       type:"integer"
       default:20
-      order:4
+      order:6
     backgroundSize:
       type:"string"
       default:"original"
       enum:["original","100%","cover","manual"]
       description:"Background size"
-      order:5
+      order:7
     manualBackgroundSize:
       type:"string"
       default:""
       description:"'100px 100px' or '50%' try something..."
-      order:6
+      order:8
     customOverlayColor:
       type:"boolean"
       default:false
-      order:7
+      order:9
       description:"Do you want different color on top of background? check this"
     overlayColor:
       type:'color'
       default:'rgba(0,0,0,0)'
       description:"Color used to overlay background image"
-      order:8
+      order:10
     opacity:
       type:'integer'
       default:'100'
       description:"Background image visibility percent 1-100"
-      order:9
+      order:11
     treeViewOpacity:
       type:'integer'
       default:"35"
       description:"Tree View can be transparent too :)"
-      order:10
+      order:12
     transparentTabBar:
       type:"boolean"
       default:true
       desctiption:"Transparent background under file tabs"
-      order:11
+      order:13
     mouseFactor:
       type:"integer"
       default: 0
       description: "move background with mouse (higher value = slower)
       try 8 or 4 for 3dbox or 20 for wallpaper"
-      order:12
+      order:14
     textShadow:
       type:"string"
       default:"0px 2px 2px rgba(0,0,0,0.3)"
       description:"Add a little text shadow to code"
-      order:13
+      order:15
     style:
       type:"string"
       default:"background:radial-gradient(rgba(0,0,0,0) 30%,rgba(0,0,0,0.75));"
       description:"Your custom css rules :]"
-      order:14
+      order:16
     boxDepth:
       type:"integer"
       default: 0
@@ -216,6 +228,9 @@
     @elements.main=main
     document.querySelector '#editor-background-main'.remove
     @elements.body.insertBefore main,@elements.body.firstChild
+    @elements.modalElement = modal =document.createElement 'div'
+    modal.innerText='hahahhaha';
+    @elements.modal = atom.workspace.addModalPanel  {item:modal,visible:false}

   insertTextBackgroundCss:->
     # CSS for background text
@@ -250,30 +265,53 @@

   timer:{}
   frames:[]
+  frame:0
   videoWidth:0
   videoHeight:0
   playing:true

   getFrame:(canvas,ctx,video,w,h)->
-    console.log 'getting frame'
+    @frame++
+    tick=50
+    if @frame*tick >= atom.config.get('editor-background.endTime')
+      return @getImagesDone
+    console.log 'getting frame',@frame
+    frame=@elements.modalContent.querySelector '#editor-background-frame'
+    frame.innerText=@frame
     ctx.drawImage video,0,0
     video.pause()
-    @elements.encoder.addFrame ctx
-    video.play()
-    if @playing
-      setTimeout =>
-        @getFrame canvas,ctx,video,w,h
-      , 50
-    #@frames.push canvas.toDataURL 'image/png'
+    if @frame*tick>=atom.config.get('editor-background.startTime')
+      if @playing
+        @elements.encoder.addFrame ctx
+        video.play()
+        if @playing
+          setTimeout =>
+            @getFrame.apply @,[canvas,ctx,video,w,h]
+          , 50
+        #@frames.push canvas.toDataURL 'image/png'

   getImages: ->
     @playing=true
+    @frame = 0
     console.log 'getting images...'
     video = @elements.video
     canvas = @elements.videoCanvas
     context = canvas.getContext("2d")
+    ytid = @getYTId atom.config.get 'editor-background.youTubeURL'
+    gifPath = @elements.videoPath+ytid+'.gif'
+    content=document.createElement 'div'
+    @elements.modalContent=content
+    content.innerHTML="
+

+    Getting Frame: 0

+    Please be patient.OK, that's it, let me see what you are doing so long...
"
+    doneBtn = content.querySelector '#editor-background-done'
+    doneBtn.addEventListener 'click',=>@getImagesDone()
+    @elements.modalElement.innerHTML=""
+    @elements.modalElement.appendChild content
+    @elements.encoder.createReadStream().pipe(fs.createWriteStream(gifPath));
     @elements.encoder.start()
     w = @videoWidth
     h = @videoHeight
@@ -308,15 +346,15 @@

     ###

-    gifPath = @elements.videoPath+ytid+'.gif'
-    buf = @elements.encoder.out.getData()
-    fs.writeFile gifPath, buf, (err) =>
-      @elements.videoCanvas.remove()
-      @elements.video.remove()
-      delete @elements.encoder
-      @elements.image.src = gifPath
-      atom.config.set('editor-background.blurRadius',0)
-      atom.config.set('editor-background.imageURL',gifPath)
+    gifPath = @elements.videoPath+ytid+'.gif?timestamp='+Date.now()
+    @elements.videoCanvas.remove()
+    @elements.video.remove()
+    delete @elements.encoder
+    atom.config.set('editor-background.blurRadius',0)
+    atom.config.set('editor-background.imageURL',gifPath)
+    @elements.image.src = gifPath
+    @elements.modal.hide()
+

   playVideo:->
     console.log 'playing video...'
@@ -343,8 +381,8 @@
     videoWidth = @videoWidth
     videoHeight = @videoHeight
     encoder = new GIFEncoder @videoWidth,@videoHeight
-    encoder.setQuality 5
-    encoder.setDelay 100
+    encoder.setQuality 20
+    encoder.setDelay 50
     encoder.setRepeat 0
     @elements.encoder = encoder
     videoCanvas.width = videoWidth
@@ -394,8 +432,9 @@
         console.log error

       if not alreadyExists
-        video = youtubedl url,['--format='+videoFormat],{ cwd: __dirname }
+        video = youtubedl url,['--format='+videoFormat+'/[height <=? 720][filesize<=5M]'],{ cwd: __dirname }
         size = 0
+        @elements.modal.show()
         video.on 'info', (info)=>
           console.log 'Download started'
           console.log info
@@ -404,10 +443,14 @@
           size = info.size
         pos=0
         video.on 'data', (data) =>
+          percent = Math.round(pos / size * 100)
+          @elements.modalElement.innerHTML="Downloading video... #{percent}%"
           pos += data.length
           console.log pos,size
           if pos==size
             console.log 'downloaded'
+
+            @elements.modalElement.innerHTML="Rendering frames..."
             @insertVideo.apply @,[savePath]
         video.pipe fs.createWriteStream(savePath)
     else


changed == to 'is'

changed == to 'is'

to align with the rest of the codebase

--- src/widgets/button.coffee
+++ src/widgets/button.coffee
@@ -43,7 +43,7 @@
       @button = @_prepareButton() unless @button
       @element.append @button

-      if @options.queryState == true
+      if @options.queryState is true
         queryState = (event) =>
           return unless @options.command
           try


Fix subject when replying to all, refs #333

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -121,6 +121,7 @@
                     inReplyTo.get('cc')).filter (dest) ->
                     return dest? and toAddresses.indexOf(dest.address) is -1
                 message.bcc = []
+                message.subject = @getReplySubject inReplyTo
                 message.text = separator + @generateReplyText(text) + "\n"
                 message.html = """

#{separator} …

@@ -372,9 +373,7 @@
     # Add a reply prefix to the current subject. Do not add it again if it's
     # already there.
     getReplySubject: (inReplyTo) ->
-        subject =  """
-        #{inReplyTo.get 'subject'}
-        """
+        subject =  inReplyTo.get('subject') or ''
         replyPrefix = t 'compose reply prefix'
         if subject.indexOf(replyPrefix) isnt 0
             subject = "#{replyPrefix}#{subject}"


Candidates for customWords, but likely too slow to actually use

--- client/codemirror/spellchecker.coffee
+++ client/codemirror/spellchecker.coffee
@@ -2,5 +2,6 @@

 require('@edemaine/codemirror-spell-checker')
   codeMirrorInstance: CodeMirror
+  #customWords: ['PSPACE', 'Eulerian', 'hypergraph', 'hypergraphs', 'antiprism', 'antiprisms']

 #require 'codemirror-spell-checker/src/css/spell-checker.css'


Fix comments. Related to #162

Fix comments. Related to #162

--- client/components/EditablePage.coffee
+++ client/components/EditablePage.coffee
@@ -7,30 +7,22 @@

 import callApi from '../lib/callApi'

-copy2clipboardcode = () ->
-    'ondblclick="copyText=this.textContent;navigator.clipboard.writeText(copyText).then(res=>alert(\'Текст успешно скопирован в буфер\')).catch(error=>alert(error));" title="Двойной клик для копирования в буфер"'
+class SimplePage extends React.Component
+    componentDidMount: () =>
+        preses = @dangerEl.querySelectorAll("pre")
+        preses.forEach (pre) ->
+            copy = document.createElement "button"
+            copy.className = "btn btn-secondary"
+            copy.appendChild document.createTextNode "скопировать"
+            copy.onclick = () =>
+                navigator.clipboard.writeText pre.textContent
+            pre.after(copy)

-addCopy2Clipboard = (content) ->
-    ind = content.lastIndexOf("sample-tests")
-    if ind > 0
-        part1 = content.substring(0, ind)
-        part2 = content.substring(ind, content.length)
-    else
-        part1 = ''
-        part2 = content
-    ind = part2.indexOf("
= 0
-        part3 = part2.substring(0, ind + 5)
-        part1 = part1 + part3 + copy2clipboardcode() + " "
-        part2 = part2.substring(ind + 5)
-        ind = part2.indexOf("

-

-

+    render: () ->
+

+
 @dangerEl = el}>
+

-

 class EditablePage extends React.Component
     constructor: (props) ->


rtm: add lifecycle events for despose

rtm: add lifecycle events for despose

--- client/ide/lib/realtimemanager.coffee
+++ client/ide/lib/realtimemanager.coffee
@@ -290,8 +290,12 @@

   dispose: ->

+    @emit 'RealtimeManagerWillDespose'
+
     @realtimeDoc?.close()
     @isDisposed = yes
     @destroy()
     @readyState = 0
     @isReady = no
+
+    @emit 'RealtimeManagerDidDispose'


add minimap:toggle

add minimap:toggle

--- toolbar.cson
+++ toolbar.cson
@@ -34,6 +34,11 @@
     tooltip: "Toggle Diff List"
     style:
       color: "#FA4F28"
+  }
+  {
+    type: "button"
+    tooltip: "Minimap"
+    callback: "minimap:toggle"
   }
   {
     type: "button"


bug fix

bug fix

--- src/tree.coffee
+++ src/tree.coffee
@@ -119,7 +119,7 @@
         e.preventDefault()
         $node = $(e.currentTarget).closest('.node')
         @select $node
-        @tree.trigger "nodeselected", [$node, $node.data("node")]
+        @trigger "nodeselected", [$node, $node.data("node")]

   refresh: (opts) ->


Allow debug utils to take multiple args.

--- src/util.coffee
+++ src/util.coffee
@@ -132,13 +132,13 @@
 root.ERROR = 1
 root.log_level ?= root.DEBUG

-root.log = (level, message) ->
+root.log = (level, msgs...) ->
   if level <= root.log_level
-    console[if level == 1 then 'error' else 'log'] message
+    console[if level == 1 then 'error' else 'log'] msgs...

-root.debug = (message) -> root.log root.DEBUG, message
+root.debug = (msgs...) -> root.log root.DEBUG, msgs...

-root.error = (message) -> root.log root.ERROR, message
+root.error = (msgs...) -> root.log root.ERROR, msgs...

 # Java classes are represented internally using slashes as delimiters.
 # These helper functions convert between the two representations.


sammy was upgraded, correct test

sammy was upgraded, correct test

--- tests/assets.coffee
+++ tests/assets.coffee
@@ -186,7 +186,7 @@
     c = t.client(zapp.server)
     c.get '/zappa/sammy.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
-      t.equal 'length', res.headers['content-length'], '18528'
+      t.equal 'length', res.headers['content-length'], '19228'

   'socket.io': (t) ->
     t.expect 'content-type', 'length'


Updated tenderloin.coffee hubot script

Updated tenderloin.coffee hubot script

--- tenderloin.coffee
+++ tenderloin.coffee
@@ -18,7 +18,7 @@
     state = msg.match[3]
     msg.http("#{TENDERLOIN_ROOT}/offices/#{office}/commands/microphone_#{state}")
        .get() (err, res, body) ->
-         return msg.send("Microphone in #{office} #{if state is "on" then "enabled" else "disabled"}") if  res.responseCode is 200
+         return msg.send("Microphone in #{office} #{if state is "on" then "enabled" else "disabled"}") if  res.statusCode is 200

   ## Control the camera
   robot.respond /(.*) (cam|camera) (off|on)/i, (msg) ->
@@ -26,22 +26,27 @@
     state = msg.match[3]
     msg.http("#{TENDERLOIN_ROOT}/offices/#{office}/commands/camera_#{state}")
        .get() (err, res, body) ->
-         return msg.send("Camera in #{office} #{if state is "on" then "enabled" else "disabled"}") if  res.responseCode is 200
+         return msg.send("Camera in #{office} #{if state is "on" then "enabled" else "disabled"}") if res.statusCode is 200

   ## Play an alert noise
   robot.respond /(.*) alert/i, (msg) ->
     office = msg.match[1]
     msg.http("#{TENDERLOIN_ROOT}/offices/#{office}/commands/alert")
        .get() (err, res, body) ->
-         return msg.send("That got their attention") if  res.responseCode is 200
+         return msg.send("That got their attention") if res.statusCode is 200
          return msg.send("Oh noes! It broke!")

   ## Execute a gist in their G+ session
   robot.respond /(.*) gist (https?:\/\/gist\.github\.com\/.*\/[0-9a-f]+)/i, (msg) ->
-    office msg.match[1]
+    office = msg.match[1]
     gist_url = msg.match[2] + '/raw'
     msg.http("#{TENDERLOIN_ROOT}/offices/#{office}/gist")
        .query(q: gist_url)
        .get() (err, response, body) ->
          msg.send body

+  robot.respond /(.*) sound stop/i, (msg) ->
+    office = msg.match[1]
+    msg.http("#{TENDERLOIN_ROOT}/offices/#{office}/stop_sound")
+       .get() (err, response, body) ->
+         return msg.send "And there was silence" if res.statusCode is 200


Allow templates to be in <script> tag (or divided over multiple tags) instead of requiring them to be elements in the DOM.

--- src/view.coffee
+++ src/view.coffee
@@ -11,7 +11,7 @@
 	@parseTemplates: ->
 		templateContainer = $('.templates')
 		templateContainer.remove()
-		templateEls = templateContainer.find('[data-template]')
+		templateEls = $(templateContainer.html()).filter('[data-template]')
 		@templates = {}

 		for template in templateEls


Fixes #22

Fixes #22

--- src/wage.coffee
+++ src/wage.coffee
@@ -1,4 +1,5 @@
 env = self.Wage ?= {}
+env.autoload = true
 env.clock = new THREE.Clock()

 include = (srcs, callback, params) ->
@@ -50,7 +51,10 @@
     window.oRequestAnimationFrame      ||
     window.msRequestAnimationFrame)

-self.onload = ->
+document.addEventListener "DOMContentLoaded", (e) ->
+    {autoload} = Wage
+    if not autoload
+        return
     if self.app isnt undefined
         Wage.app = app
     else


make 'Select All' work by using 'prop' instead of 'attr' for checked/unchecked in filters

--- pivot.coffee
+++ pivot.coffee
@@ -533,9 +533,9 @@
                 else
                     btns = $("<p>").css("text-align": "center")
                     btns.append $("<button>").text(opts.localeStrings.selectAll).bind "click", ->
-                        valueList.find("input").attr "checked", true
+                        valueList.find("input").prop "checked", true
                     btns.append $("<button>").text(opts.localeStrings.selectNone).bind "click", ->
-                        valueList.find("input").attr "checked", false
+                        valueList.find("input").prop "checked", false
                     valueList.append btns
                     for k in keys.sort()
                          v = axisValues[c][k]


call app real-time

call app real-time

--- services/real-time/app.coffee
+++ services/real-time/app.coffee
@@ -5,7 +5,7 @@
 console.log Settings.redis

 logger = require "logger-sharelatex"
-logger.initialize("real-time-sharelatex")
+logger.initialize("real-time")
 Metrics.event_loop.monitor(logger)

 express = require("express")
@@ -19,10 +19,6 @@
 RedisStore = require('connect-redis')(session)
 SessionSockets = require('session.socket.io')
 CookieParser = require("cookie-parser")
-
-
-sessionRedisClient.set "hello-a", "hello-there", (err)->
-	console.log "setting hello-a", err

 DrainManager = require("./app/js/DrainManager")


Use bleeding edge express-partials

Use bleeding edge express-partials

--- lib/skeleton/template.coffee
+++ lib/skeleton/template.coffee
@@ -18,6 +18,7 @@
         , "dependencies": {
               "express": "3.0.x"
             , "connect-assets": "2.1.x"
+            , "express-partials": "git://github.com/publicclass/express-partials.git"
             , "stylus": "*"
             , "ejs": "*"
             , "coffee-script": "*"
@@ -47,6 +48,7 @@
       # Modules
       express = require 'express'
       http = require 'http'
+      partials = require 'express-partials'
       app = express()

       # Boot setup
@@ -66,6 +68,7 @@
         app.use express.logger('dev')
         app.use express.bodyParser()
         app.use express.methodOverride()
+        app.use partials()
         app.use require('connect-assets')(src: "\#{__dirname}/assets")
         app.use app.router


Regex power fixes dem bugs

Regex power fixes dem bugs

--- templates/example-aerix/javascript/invoice-view.coffee
+++ templates/example-aerix/javascript/invoice-view.coffee
@@ -5,10 +5,10 @@
   filesystemName: ->
     customer = @model.get("client").organization
     customer = customer || @model.get("client").contactPerson
-    customer = customer.replace ' ', '-'
+    customer = customer.replace /\s/g, '-' # Spaces to dashes using regex
     project = @model.get("projectName")
     if project?
-      project = project.replace ' ', '-'
+      project = project.replace /\s/g, '-' # Spaces to dashes using regex
       "#{@getFullID()}_#{customer}_#{project}.pdf"
     else
       "#{@getFullID()}_#{customer}.pdf"


Dispose of the emitter (#31)

Dispose of the emitter (#31)

--- lib/views/share-setup.coffee
+++ lib/views/share-setup.coffee
@@ -30,6 +30,7 @@
     return unless @hasParent()
     @detaching = true
     @miniEditor.setText('')
+    @emitter.dispose()
     super
     @detaching = false


Replace window.innerHeight by $(window).height to improve compatibility

--- app/assets/javascripts/martian_components/components/utils/full_height_header.coffee
+++ app/assets/javascripts/martian_components/components/utils/full_height_header.coffee
@@ -20,6 +20,6 @@
     $(window).on 'resize', @compute

   compute: =>
-    return false if @lastWidth == window.innerWidth
-    @inner.css('height', window.innerHeight - @offset)
-    @lastWidth = window.innerWidth
+    return false if @lastWidth == $(window).width()
+    @inner.css('height', $(window).height() - @offset)
+    @lastWidth = $(window).width()


set referer when creating a new window after setting document.location

--- src/zombie/history.coffee
+++ src/zombie/history.coffee
@@ -189,7 +189,7 @@
       window._eventQueue.enqueue ->
         window.dispatchEvent(event)
     else
-      window = createWindow(browser: @browser, history: this, name: name, url: url, parent: parent)
+      window = createWindow(browser: @browser, history: this, name: name, url: url, parent: parent, referer: @current.window.document.referrer)
       @addEntry(window, url)
     return


Adds missing module import in tasks/pack

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -1,4 +1,5 @@
 cp = require 'child_process'
+async = require 'async'
 del = require 'del'

 gulp = require 'gulp'


test pack for getDocUpdates with time

test pack for getDocUpdates with time

--- test/unit/coffee/MongoManager/MongoManagerTests.coffee
+++ test/unit/coffee/MongoManager/MongoManagerTests.coffee
@@ -249,14 +249,41 @@
 	describe "getDocUpdates", ->
 		beforeEach ->
 			@results = [
-				{foo: "mock-update", v: 56, meta: {end_ts: 110}},
-				{foo: "mock-update", v: 55, meta: {end_ts: 100}},
-				{foo: "mock-update", v: 42, meta:{end_ts: 90}}
-			]
-			@updates = [
-				{foo: "mock-update", v: 55, meta: {end_ts: 100}},
-				{foo: "mock-update", v: 42, meta:{end_ts: 90}}
-			]
+				{foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
+				{pack: [
+					{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
+					{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
+					{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1}	]
+					, v: 52, meta: {end_ts: 100}, doc_id: 300, project_id: 1},
+				{pack: [
+					{foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
+					{foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
+					{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1}	]
+					, v: 52, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
+				{foo: "mock-update", v: 42, meta:{end_ts: 90}, doc_id: 100, project_id: 1}
+			]
+			@updates_before = [
+				{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
+				{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1},
+			]
+			@updates_all = [
+				{foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
+				{foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
+				{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
+				{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
+				{foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1}
+			]
+
+
 			@db.docHistory = {}
 			@db.docHistory.find = sinon.stub().returns @db.docHistory
 			@db.docHistory.sort = sinon.stub().returns @db.docHistory
@@ -287,7 +314,7 @@
 					.called.should.equal false

 			it "should call the call back with the updates", ->
-				@callback.calledWith(null, @updates).should.equal true
+				@callback.calledWith(null, @updates_before).should.equal true

 		describe "without a before timestamp", ->
 			beforeEach ->
@@ -301,7 +328,7 @@
 					.should.equal true

 			it "should call the call back with the updates", ->
-				@callback.calledWith(null, @results).should.equal true
+				@callback.calledWith(null, @updates_all).should.equal true

 		describe "with a limit", ->
 			beforeEach ->


Revert "only consider audio for the say action"

This reverts commit 9fbb4b78bf1f8fb05f51156dfe28c43e95df09e1.

--- static/coffee/flows/controllers.coffee
+++ static/coffee/flows/controllers.coffee
@@ -183,7 +183,7 @@

     # make sure its an audio file
     file = $files[0]
-    if action.type == 'say' and file.type != 'audio/wav' and file.type != 'audio/x-wav'
+    if file.type != 'audio/wav' and file.type != 'audio/x-wav'
       showDialog('Wrong File Type', 'Audio files need to in the WAV format. Please choose a WAV file and try again.')
       return


Remove obsolete code in DomainRepository

--- lib/domain_repository.coffee
+++ lib/domain_repository.coffee
@@ -131,7 +131,6 @@
   _commit: (callback) ->
     return callback null if Object.keys(@aggregateEvents).length is 0

-    events      = [];
     savedEvents = [];

     aggregateQueue = async.queue (aggregateAppliedEvents, aggregateTaskCallback) =>
@@ -143,7 +142,6 @@
           queue.push nextEvent if nextEvent?

           @logger.log "commit", "saving event \"#{event.name}\" for aggregate #{event.aggregateUid}"
-          events.push event
           @store.saveEvent event, (err, event) =>
             savedEvents.push(event);
             if err?
@@ -161,12 +159,12 @@

     aggregateQueue.drain = (err) =>
       return callback err if err?
-      return callback null unless events.length > 0
+      return callback null unless savedEvents.length > 0
       publicationQueue = async.queue (event, publicationCallback) =>
         @_publishEvent event, publicationCallback
       , Infinity
       publicationQueue.drain = callback
-      publicationQueue.push events
+      publicationQueue.push savedEvents

     for aggregateUid, aggregateEvents of @aggregateEvents
       aggregateQueue.push [aggregateEvents]


activity/options: keyBindings and commands are obsolete

--- client/activity/lib/options.coffee
+++ client/activity/lib/options.coffee
@@ -1,13 +1,3 @@
 module.exports =
   name         : 'Activity'
   searchRoute  : '/Activity?q=:text:'
-  commands     :
-    'next tab'     : 'goToNextTab'
-    'previous tab' : 'goToPreviousTab'
-  keyBindings: [
-    { command: 'next tab',      binding: 'alt+]',    global: yes }
-    { command: 'next tab',      binding: 'alt+down', global: yes }
-    { command: 'previous tab',  binding: 'alt+up',   global: yes }
-    { command: 'previous tab',  binding: 'alt+[',    global: yes }
-  ]
-


clean comments

clean comments

--- static/news/src/views/micropost.coffee
+++ static/news/src/views/micropost.coffee
@@ -111,7 +111,7 @@
     $(@el).removeClass("selected")
     $("#news-preview").html(null)

-  # Check if post contains a youtube or a dailymotion link. If it is the case,
+  # Check if post contains a youtube link. If it is the case,
   # it displays the embedded version of this video in the preview column.
   checkForVideo: ->
     youtubeRegExp = /http:\/\/\S+?youtube.com\S+\]/


client: tests: fixed old homepage tests

client: tests: fixed old homepage tests

--- client/test/lib/login/login.coffee
+++ client/test/lib/login/login.coffee
@@ -49,37 +49,18 @@
     browser.end()

-  loginFromHomepageSignupForm: (browser) ->
-
-    user = utils.getUser()
-
-    helpers.attemptEnterEmailAndPasswordOnRegister(browser, user)
-
-    browser
-      .waitForElementVisible '[testpath=main-sidebar]', 10000 # Assertion
-      .end()
-
-
   loginFromSignupModal: (browser) ->

     user = utils.getUser()
-    url = helpers.getUrl()
-
-    if HUBSPOT
-       url = "#{url}/Register"
+    url  = helpers.getUrl()
+    url  = "#{url}/Register"

     browser
       .url(url)
       .maximizeWindow()

-    unless HUBSPOT
-      browser
-        .waitForElementVisible  '[testpath=main-header]', 50000
-        .click                  'nav:not(.mobile-menu) [testpath=login-link]'
-        .waitForElementVisible  '[testpath=login-container]', 50000
-        .click                  '.login-footer .signup-link a.register'
-
     browser
+      .waitForElementVisible  '[testpath=login-container]', 50000
       .setValue               '.main-part [testpath=register-form-email]', user.email
       .setValue               '.main-part input[name=password]', user.password
       .click                  '.main-part [testpath=signup-button]'


Update fr.coffee

Update fr.coffee

Corrections and typo changes to match French rules.

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -388,7 +388,7 @@
     tip_scrub_shortcut: "Ctrl+[ and Ctrl+] : rembobinage et avance rapide." # {change}
     tip_guide_exists: "Cliquez sur le guide en haut de la page pour des informations utiles."
     tip_open_source: "CodeCombat est 100% open source !"
-    tip_tell_friends: "Vous aimez CodeCombat? Parlez de nous à vos amis!"
+    tip_tell_friends: "Vous aimez CodeCombat? Parlez de nous à vos amis !"
     tip_beta_launch: "La beta de CodeCombat a été lancée en Octobre 2013"
     tip_think_solution: "Reflechissez à propos de la solution et non du problème."
     tip_theory_practice: "En théorie, il n'y a pas de différence entre la théorie et la pratique. Mais en pratique, il y en a. - Yogi Berra"
@@ -908,7 +908,7 @@
     leave_clan: "Partir du clan"
     join_clan: "Rejoindre le clan"
     invite_1: "Inviter:"
-    invite_2: "*Inviter des joueurs au Caln en leur envoyant le lien suivant."
+    invite_2: "*Inviter des joueurs au Clan en leur envoyant le lien suivant."
     members: "Membres"
     progress: "Progression"
     not_started_1: "Non commencé"
@@ -960,8 +960,8 @@
     average_time: "Temps moyen de jeu par niveau:"
     total_time: "Temps total de jeu:"
     average_levels: "Nombre de niveaux moyen complétés:"
-    total_levels: "Total de niveaux complétés:"
-    furthest_level: "Meilleur niveau complété:"
+    total_levels: "Total de niveaux complétés :"
+    furthest_level: "Meilleur niveau complété :"
     concepts_covered: "Conceptes Couverts"
     students: "Elèves"
     students1: "éleves"
@@ -995,7 +995,7 @@
     creating_for: "Vous créez une classe pour"
     for: "pour" # Like in 'for 30 students'
 #    receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
-    free_trial: "Essai gratuit pour les professeurs!"
+    free_trial: "Essai gratuit pour les professeurs !"
     get_access: "pour obtenir un accès individuel à tous les cours pour évaluation."
     questions: "Questions?"
     teachers_click: "Professeurs, Clickez Ici"
@@ -1005,7 +1005,7 @@
     more_in_less: "Apprenez plus en moins de temps"
     no_experience: "Aucune expérience en développement requise"
     easy_monitor: "Suivez facilement la progression des élèves"
-    purchase_for_class: "Achetez un cours pour votre classe. C'est très simple de rajouter vos élèves!"
+    purchase_for_class: "Achetez un cours pour votre classe. C'est très simple de rajouter vos élèves !"
 #    see_the: "See the"
     more_info: "pour plus d'informations."
     choose_course: "Choisissez votre cours:"
@@ -1016,13 +1016,13 @@
     enter: "Entrer"
     or: "Ou"
     topics: "Sujets"
-    hours_content: "Heures de contenu:"
+    hours_content: "Heures de contenu :"
     get_free: "Obtenez des cours GRATUITS"
     enroll_paid: "Inscrire des élèves aux cours payants"
     you_have1: "Vous avez"
     you_have2: "inscriptions payantes non utilisées"
     use_one: "Utilisez 1 inscription payante pour"
-    use_multiple: "Utilisez des inscriptions payantes pour les élèves suivants:"
+    use_multiple: "Utilisez des inscriptions payantes pour les élèves suivants :"
     already_enrolled: "déjà inscrit"
     licenses_remaining: "licenses restantes:"
     insufficient_enrollments: "inscriptions payantes insuffisantes"
@@ -1038,7 +1038,7 @@
     back_classrooms: "Retour à mes classes"
     back_courses: "Retour à mes cours"
     edit_details: "Modifier les informations de la classe"
-    enrolled_courses: "inscrit aux cours payants:"
+    enrolled_courses: "inscrit aux cours payants :"
     purchase_enrollments: "Acheter des inscriptions"
     remove_student: "enlever l'élève"
     assign: "Assigner"
@@ -1048,42 +1048,42 @@
     none: "Aucun"
     save: "Sauvegarder"
     play_campaign_title: "Jouer la Campagne"
-    play_campaign_description: "Vous êtes prêts pour la prochaine étape! Explorez des centaines de niveaux stimulants, apprenez al programmation avancée, et rivalisez avec d'autres dans les arènes multijoueurs!"
+    play_campaign_description: "Vous êtes prêts pour la prochaine étape ! Explorez des centaines de niveaux stimulants, apprenez al programmation avancée, et rivalisez avec d'autres dans les arènes multijoueurs !"
     create_account_title: "Créer un compte"
-    create_account_description: "Inscrivez-vous GRATUITEMENT à CodeCombat et accédez à plus de niveaux, plus de compétences de programmation et plus de fun!"
+    create_account_description: "Inscrivez-vous GRATUITEMENT à CodeCombat et accédez à plus de niveaux, plus de compétences de programmation et plus de fun !"
     preview_campaign_title: "Campagne de démonstration"
     preview_campaign_description: "Ayez un aperçu de tout ce dont CodeCombat a à offrir avant de s'enregistrer GRATUITEMENT."
     arena: "Arène"
-    arena_soon_title: "L'Arene arrive prochainement"
-    arena_soon_description: "Nous travaillons sur une arene multijoueur pour les classes à la fin de"
+    arena_soon_title: "L'Arène arrive prochainement"
+    arena_soon_description: "Nous travaillons sur une arène multijoueur pour les classes à la fin de"
     not_enrolled1: "Non inscrit"
     not_enrolled2: "Demandez à votre professeur de vous inscrire au cours suivant."
     next_course: "Cours suivant"
     coming_soon1: "Prochainement"
-    coming_soon2: "Nous travaillons dur pour faire plus de nouveaux cours!"
+    coming_soon2: "Nous travaillons dur pour faire plus de nouveaux cours !"
     available_levels: "Niveaux disponibles"
-    welcome_to_courses: "Aventurier, bienvenu dans les leçons!"
-    ready_to_play: "Prêt à jouer?"
+    welcome_to_courses: "Aventurier, bienvenu dans les leçons !"
+    ready_to_play: "Prêt à jouer ?"
     start_new_game: "Nouvelle Partie"
     play_now_learn_header: "Jouer pour apprendre"
     play_now_learn_1: "la syntaxe basique pour controller votre personnage"
     play_now_learn_2: "les boucles while pour résoudre des problèmes complexes"
     play_now_learn_3: "des chaines de caractères & des variables pour personnaliser des actions"
-    play_now_learn_4: "comment battre un ogre (compétence importante dans la vie!)"
-    welcome_to_page: "Bienvenu sur la page des Cours!"
+    play_now_learn_4: "comment battre un ogre (compétence importante dans la vie !)"
+    welcome_to_page: "Bienvenu sur la page des Cours !"
 #    completed_hoc: "Amazing! You've completed the Hour of Code course!"
-    ready_for_more_header: "Motivé pour plus? Jouez au mode campagne!"
-    ready_for_more_1: "Utilisez des gemmes pour débloquer de nouveaux acessoires!"
+    ready_for_more_header: "Motivé pour plus ? Jouez au mode campagne !"
+    ready_for_more_1: "Utilisez des gemmes pour débloquer de nouveaux acessoires !"
     ready_for_more_2: "Jouez à travers de nouveaux mondes et challenges"
-    ready_for_more_3: "Apprenez encore plus de programmation!"
+    ready_for_more_3: "Apprenez encore plus de programmation !"
     saved_games: "Parties enregistrées"
     hoc: "Heures de Code"
     my_classes: "Mes Classes"
-    class_added: "Classe ajoutée avec succès!"
+    class_added: "Classe ajoutée avec succès !"
     view_class: "voir la classe"
     view_levels: "voir les niveaux"
     join_class: "Rejoindre une classe"
-    ask_teacher_for_code: "Demandez à votre professeur si vous avez un code de classe CodeComabt! Si c'est le cas, veuillez l'entrer ci-dessous:"
+    ask_teacher_for_code: "Demandez à votre professeur si vous avez un code de classe CodeCombat ! Si c'est le cas, veuillez l'entrer ci-dessous :"
     enter_c_code: "<Entrer le Code de la Classe>"
     join: "Rejoindre"
     joining: "En train de rejoindre la classe"
@@ -1092,32 +1092,32 @@
     start: "Démarrer"
     last_level: "Dernier Niveau"
 #    welcome_to_hoc: "Adventurers, welcome to our Hour of Code!"
-    logged_in_as: "Connecté en tant que:"
-    not_you: "Pas vous?"
-    welcome_back: "Salut aventurier, content de te revoir!"
+    logged_in_as: "Connecté en tant que :"
+    not_you: "Pas vous ?"
+    welcome_back: "Salut aventurier, content de te revoir !"
     continue_playing: "Continuer à Jouer"
     more_options: "Plus d'options:"
-    option1_header: "Option 1: Inviter les élèves par email"
+    option1_header: "Option 1 : Inviter les élèves par email"
     option1_body: "Les élèves recevront automatiquement une invitation à rejoindre cette classe, et devront créer un compte avec un nom d'utilisateur et mot de passe."
-    option2_header: "Option 2: Envoyez l'URL à vos élèves"
+    option2_header: "Option 2 : Envoyez l'URL à vos élèves"
     option2_body: "Il sera demandé aux élèves de saisir une adresse email, un nom d'utilisateur et un mot de passe pour créer un compte."
-    option3_header: "Option 3: Dirigez les élèves sur codecombat.com/courses"
+    option3_header: "Option 3 : Dirigez les élèves sur codecombat.com/courses"
     option3_body: "Donnez aux élèves le code suivant à spécifier avec une adresse email, un nom d'utilisateur, et un mot de passe lors de la création de compte."
-    thank_you_pref: "Merci pour votre achat! Vous pouvez maintenant assigner"
+    thank_you_pref: "Merci pour votre achat ! Vous pouvez maintenant assigner"
     thank_you_suff: "plus d'élèves à des cours payants."
     return_to_class: "Retourner à la classe"
-    return_to_course_man: "Retourn à la gestion des cours."
-    students_not_enrolled: "élève non inscrits"
-    total_all_classes: "Total Dans Toutes Les Classes"
-    how_many_enrollments: "De combien d'inscriptions payantes supplémentaire avez-vous besoin?"
-    each_student_access: "Chaque élève dans une classe aura accès aux cours 2 à 4 une fois qu'ils sont inscrits dans les cours payants. Vous pouvez assignez chaque cours à chacun des élèves individuellement."
+    return_to_course_man: "Retourner à la gestion des cours."
+    students_not_enrolled: "élèves non inscrits"
+    total_all_classes: "Total dans toutes les classes"
+    how_many_enrollments: "De combien d'inscriptions payantes supplémentaires avez-vous besoin ?"
+    each_student_access: "Chaque élève dans une classe aura accès aux cours 2 à 4 une fois qu'il sera inscrit dans les cours payants. Vous pouvez assignez chaque cours à chacun des élèves individuellement."
     purchase_now: "Achetez Maintenant"
     enrollments: "inscriptions"
-    remove_student1: "Supprimer l'Elève"
-    are_you_sure: "Etes-vous certain de vouloir supprimer cet élève de cette classe?"
+    remove_student1: "Supprimer l'élève"
+    are_you_sure: "Êtes-vous certain de vouloir supprimer cet élève de cette classe ?"
     remove_description1: "L'élève n'aura plus accès à cette classe et les classes associées. L'évolution et les personnages ne seront pas supprimés, et l'élève pourra de nouveau rejoindre la classe n'importe quand."
     remove_description2: "Une licence payante activée ne sera pas remboursée."
-    keep_student: "Conserver l'Elève"
+    keep_student: "Conserver l'élève"
     removing_user: "Suppression de l'utilisateur"
     to_join_ask: "Pour rejoindre une classe, veuillez demander un code de déverouillage à votre professeur."
     join_this_class: "Rejoindre cette classe"
@@ -1130,10 +1130,10 @@
     unique_name: "un nom unique que personne n'a choisi"
     pick_something: "choisissez quelque chose dont vous pourrez vous souvenir"
     class_code: "Code de la Classe"
-    optional_ask: "facultatif - demandez à votre professeur de vous en donner un!"
+    optional_ask: "facultatif - demandez à votre professeur de vous en donner un !"
     optional_school: "facultatif - à quelle école allez-vous?"
     start_playing: "Commencer à Jouer"
-    skip_this: "Ignorer cette étape, je créSkip this, I'll create an account later!"
+    skip_this: "Ignorer cette étape, je créerai un compte ultérieurement !"
     welcome: "Bienvenu"
     getting_started: "Démarrer les cours"
     download_getting_started: "Télécharger le guide de démarrage [PDF]"
@@ -1158,13 +1158,13 @@
 #    educator_wiki_mid: "educator wiki"
 #    educator_wiki_suff: "to browse the guide online."
     your_classes: "Vos Classes"
-    no_classes: "Aucune classe pour le moment!"
+    no_classes: "Aucune classe pour le moment !"
     create_new_class1: "créer une nouvelle classe"
     available_courses: "Cours Disponibles"
-    unused_enrollments: "Inscriptions non utilisées disponibles:"
+    unused_enrollments: "Inscriptions non utilisées disponibles :"
     students_access: "Tous les élèves peuvent accéder gratuitement à l'Introduction aux Sciences Informations. Une inscription par élève est nécessaire pour rejoindre les cours payants de CodeCombat. Une seule inscription est nécessaire par élève pour accéder à tous les cours payants."
     active_courses: "cours actifs"
-    no_students: "Aucun élève pour le moment!"
+    no_students: "Aucun élève pour le moment !"
     add_students1: "ajouter des élèves"
     view_edit: "voir/modifier"
     students_enrolled: "élèves inscrits"
@@ -1237,7 +1237,7 @@
     config_thang: "Double-cliquez pour configurer un Thang"
     delete: "Supprimer"
     duplicate: "Dupliquer"
-    stop_duplicate: "Arreter la duplication"
+    stop_duplicate: "Arrêter la duplication"
     rotate: "Pivoter"
     level_settings_title: "Paramètres"
     level_component_tab_title: "Composants actuels"
@@ -1366,7 +1366,7 @@
     games_simulated_by: "Parties que vous avez simulées :"
     games_simulated_for: "Parties simulées pour vous :"
     games_in_queue: "Parties actuellement en attente :"
-    games_simulated: "Partie simulée"
+    games_simulated: "Parties simulées"
     games_played: "Parties jouées"
     ratio: "Moyenne"
     leaderboard: "Classement"
@@ -1475,11 +1475,11 @@
     amount: "Montant (Dollars US)"
     declined: "Votre carte a été refusée"
     invalid_amount: "Entrez un montant en dollars US."
-    not_logged_in: "Connectez vous ou créez un compte pour accéder aux factures."
+    not_logged_in: "Connectez-vous ou créez un compte pour accéder aux factures."
     pay: "Paiement de facture"
     purchasing: "Achat..."
     retrying: "Erreur interne, réessayez"
-    success: "Paiement accepté, Merci !"
+    success: "Paiement accepté, merci !"

 #  account_prepaid:
 #    purchase_code: "Purchase a Subscription Code"