storage: avoid any possibility of deep copying in a certain function (which may result in confusing some monitoring); fix a typo in a log message

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -1242,6 +1242,7 @@
 ###
 process_update = (tasks, database, project) ->
     dbg = (m) -> winston.debug("process_update(project_id=#{project.project_id}): #{m}")
+    project = misc.deep_copy(project)  # avoid any possibility of reference issues!
     if tasks[project.project_id]
         # definitely already running in this process
         return
@@ -1294,7 +1295,7 @@
         storage_request.err = err
         update_db()
     else
-        dbg("doing action '#{action}")
+        dbg("doing action '#{action}'")
         tasks[project.project_id] = true
         storage_request.started = new Date()
         update_db()
@@ -1492,7 +1493,7 @@
         return @_synctable.get(project_id).toJS()

     list: () =>
-        return (x for x in @_synctable.get().valueSeq().toJS() when x.storage_request?.requested >= misc.minutes_ago(60))
+        return (x for x in @_synctable.get().valueSeq().toJS() when x.storage_request?.requested >= misc.minutes_ago(5))

     # activity that was requested but not started -- this is BAD!
     ignored: () =>
@@ -1540,9 +1541,9 @@
         console.log "     pending  : #{data.ignored}  #{warn}"

     monitor: () =>
-        @_synctable.on 'change', =>
-            @summary()
-        @summary()
+        f = require('underscore').debounce((=>@summary()), 1500)
+        @_synctable.on('change', f)
+        f()
         return

 ###########################


maintain pagination when marking posts as read by doing unread filtering on the client

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -9,7 +9,8 @@
             post = new NR.Models.NewsFeedArticleInfo
             for k,v of i
                 post[k] = v
-            this.articleList.add post
+            if (!this.localSettings.showOnlyUnread || (this.localSettings.showOnlyUnread && post.unread))
+                this.articleList.add post

     _apiError: (type, desc, data) =>
         errorText = "Communications error with the server. Please try again."
@@ -56,7 +57,7 @@
         if index >= 0
             this._uid = uid
             this._fid = fid
-            this._postPage = 1
+            this._postPage = 0
             this._enableFetch = true
             this.currentArticle = -1

@@ -66,7 +67,7 @@
             this.welcomeView.hide()

             # Get posts from server
-            NR.API.GetPostsForFeed fid, 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+            this.fetchMorePosts()

             # Update nav elements.
             feed = this.feedList.at index
@@ -79,7 +80,7 @@
     @route "news/:uid/feeds", (uid) ->
         this._uid = uid
         this._fid = 0
-        this._postPage = 1
+        this._postPage = 0
         this._enableFetch = true
         this.currentArticle = -1

@@ -89,7 +90,7 @@
         this.welcomeView.hide()

         # Get posts from server
-        NR.API.GetAllPosts 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+        this.fetchMorePosts()

         # Update nav elements.
         this.newsFeedView.allFeedsSelected()
@@ -264,13 +265,13 @@
             NR.API.MarkAllFeedPostsAsRead this._fid, 0, Date.parse(this.articleList.at(0).article.pubDate) / 1000, (data) =>
                 this.articleList.reset()
                 this.updateFeeds()
-                NR.API.GetPostsForFeed this._fid, 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+                NR.API.GetPostsForFeed this._fid, 0, "", false, this._processFeedPosts, this._apiError
             , this._apiError
         else
             NR.API.MarkAllPostsAsRead 0, Date.parse(this.articleList.at(0).article.pubDate) / 1000, (data) =>
                 this.articleList.reset()
                 this.updateFeeds()
-                NR.API.GetAllPosts 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+                NR.API.GetAllPosts 0, "", false, this._processFeedPosts, this._apiError
             , this._apiError

     importSingleFeed: () =>
@@ -309,30 +310,38 @@
             if data.length > 0
                 this._enableFetch = true
                 this._postPage = this._postPage + 1
+                oldPostCount = this.articleList.length
                 this._processFeedPosts data
-
+                if (this.articleList.length - oldPostCount) < 10
+                    this.fetchMorePosts()
+
+        if this._postPage > 0
+            lastArticleId = this.articleList.at(0).article.id
+        else
+            lastArticleId = ""
+
         if this._enableFetch
             this._enableFetch = false
             if this._fid > 0
                 NR.API.GetPostsForFeed(
                     this._fid,
                     this._postPage,
-                    this.articleList.at(0).article.id,
-                    this.localSettings.showOnlyUnread,
+                    lastArticleId,
+                    false,
                     successWrapper,
                     errorWrapper)
             else if this._savedPostsMode
                 NR.API.GetSavedPosts(
                     this._uid,
                     this._postPage,
-                    this.articleList.at(0).article.id,
+                    lastArticleId,
                     successWrapper,
                     errorWrapper)
             else
                 NR.API.GetAllPosts(
                     this._postPage,
-                    this.articleList.at(0).article.id,
-                    this.localSettings.showOnlyUnread,
+                    lastArticleId,
+                    false,
                     successWrapper,
                     errorWrapper)

@@ -386,12 +395,12 @@
         this.articleList.reset()
         if this._fid?
             if this._fid > 0
-                NR.API.GetPostsForFeed this._fid, 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+                NR.API.GetPostsForFeed this._fid, 0, "", false, this._processFeedPosts, this._apiError
                 index = this.feedList.any((i) => i.id.toString() == this._fid.toString())
                 feed = this.feedList.at index
                 this.topNavView.feedSelected feed
             else
-                NR.API.GetAllPosts 0, "", this.localSettings.showOnlyUnread, this._processFeedPosts, this._apiError
+                NR.API.GetAllPosts 0, "", false, this._processFeedPosts, this._apiError
                 this.topNavView.allFeedsSelected

     toggleOptOut: =>


Add analytics

Add analytics

--- app/main.coffee
+++ app/main.coffee
@@ -1,5 +1,10 @@
 $ = window.jQuery
 $.noConflict()
+
+GoogleAnalytics = require 'zooniverse/lib/google-analytics'
+new GoogleAnalytics
+  account: 'UA-1224199-24'
+  domain: 'milkywayproject.org'

 t7e = require 't7e'
 t7e.load require './lib/en-us'


Model.spec: added sync test

Model.spec: added sync test

--- coffee/test/model.spec.coffee
+++ coffee/test/model.spec.coffee
@@ -59,3 +59,24 @@
         expect(model.shellData()).not.toBe sd
         expect(model.shellData(sd)).toBe sd
         expect(model.shellData()).toBe sd
+
+
+  describe 'acorn.Model.sync', ->
+
+    #TODO expand this into comprehensive tests.
+    #TODO find a way to mock the server in the future.
+
+    it 'should be able to sync', ->
+      nyfskeqlyx = new acorn.Model(acornid:'nyfskeqlyx')
+      spy = new athena.lib.util.test.EventSpy nyfskeqlyx, 'change'
+
+      runs ->
+        nyfskeqlyx.fetch()
+
+      waitsFor (-> spy.triggered), 'fetch should complete', 5000
+
+      runs ->
+        'got it!'
+        shell = nyfskeqlyx.shellData()
+        expect(shell.shell).toBe 'acorn.YouTubeShell'
+        expect(shell.link).toBe 'https://www.youtube.com/watch?v=yYAw79386WI'


Updated card titles from Edge's website article

--- coffeescripts/cards-fr.coffee
+++ coffeescripts/cards-fr.coffee
@@ -643,6 +643,7 @@
             name: "Brouilleur de senseurs"
             text: """Quand vous défendez, vous pouvez changer un des résultats %HIT% de l'attaquant en résultat %FOCUS%. L'attaquant ne peut pas relancer le dés au résultat modifié."""
         "Accuracy Corrector": # Traduction non officielle
+            name: "Correcteur de Précision"
             text: """Quand vous attaquez, vous pouvez annuler tous les résultats de dés. Puis, vous pouvez ajouter 2 résultats %HIT%.

Vos dés ne peuvent pas être modifiés de nouveau durant cette attaque."""
         # Équipages
         "Gunner":
@@ -784,6 +785,7 @@

         # Illégal
         "Inertial Dampeners": # Traduction non officielle
+            name: "Amortisseurs Inertiels"
             text: """Quand vous révéler votre cadran de manœuvre, vous pouvez défausser cette carte pour effectuer à la place une manœuvre blanche [0%STOP%]. Puis recevez 1 marqueur de stress."""
         "Dead???":
             text: """Cette carte n'a pas encore été révélée."""


Only show toggle side item in Packages menu

Showing in the View menu just pollutes that menu

--- menus/tree-view.cson
+++ menus/tree-view.cson
@@ -3,7 +3,6 @@
     'label': 'View'
     'submenu': [
       { 'label': 'Toggle Tree View', 'command': 'tree-view:toggle' }
-      { 'label': 'Toggle Tree Side', 'command': 'tree-view:toggle-side' }
     ]
   }
   {


Add focus menu item

Add focus menu item

--- menus/tree-view.cson
+++ menus/tree-view.cson
@@ -11,6 +11,7 @@
     'submenu': [
       'label': 'Tree View'
       'submenu': [
+        { 'label': 'Focus', 'command': 'tree-view:toggle-focus' }
         { 'label': 'Toggle', 'command': 'tree-view:toggle' }
         { 'label': 'Reveal Active File', 'command': 'tree-view:reveal-active-file' }
       ]


Show shadow when resourse under limit

Show shadow when resourse under limit

--- views/components/main/parts/resource-panel.cjsx
+++ views/components/main/parts/resource-panel.cjsx
@@ -9,6 +9,7 @@

   getInitialState: ->
     material: ['??', '??', '??', '??', '??', '??', '??', '??', '??']
+    limit: 30750   # material limit of level 120
   handleResponse: (e) ->
     {method, path, body} = e.detail
     switch path
@@ -19,11 +20,15 @@
         @setState
           material: material
       when '/kcsapi/api_port/port'
-        {material} = @state
+        {material, limit} = @state
         for e in body.api_material
           material[e.api_id] = e.api_value
+        level = parseInt(body.api_basic.api_level)
+        if level > 0
+          limit = 750 + level * 250
         @setState
           material: material
+          limit: limit
       when '/kcsapi/api_req_hokyu/charge'
         {material} = @state
         for i in [0..3]
@@ -83,8 +88,15 @@
       {
         for i in order
           <Col key={i} xs={6} style={marginBottom: 3}>
-
-            {@state.material[i]}
+          {
+            # Show special effect to indicate that meterial is lower than limit.
+            # Only apply to first 4 material (fuel, ammo, steel and bauxite).
+            style = null
+            if i <= 4 and @state.material[i] < @state.limit
+              style = {'-webkit-filter': 'drop-shadow(0px 0px 4px #2196F3)'}
+
+          }
+          {@state.material[i]}
           </Col>
       }
       </Grid>


Add try/catch for JSON.parse

Add try/catch for JSON.parse

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -446,14 +446,24 @@

             # Load options from local storage
             if window.localStorage?.options?
-                @state.options = JSON.parse window.localStorage.options
+                try
+                    @state.options = JSON.parse window.localStorage.options
+                catch err
+                    window.localStorage.removeItem 'options'

             # Load the query that was written in code mirror (that may not have been executed before)
             if typeof window.localStorage?.current_query is 'string'
-                @state.current_query = JSON.parse window.localStorage.current_query
+                try
+                    @state.current_query = JSON.parse window.localStorage.current_query
+                catch err
+                    window.localStorage.removeItem 'current_query'
+

             if window.localStorage?.rethinkdb_history?
-                @state.history = JSON.parse window.localStorage.rethinkdb_history
+                try
+                    @state.history = JSON.parse window.localStorage.rethinkdb_history
+                catch err
+                    window.localStorage.removeItem 'rethinkdb_history'

             @show_query_warning = @state.query isnt @state.current_query # Show a warning in case the displayed results are not the one of the query in code mirror
             @current_results = @state.results


Make the option for electric punctuation works

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -900,6 +900,7 @@
                 @ignore_tab_keyup = false
                 @hide_suggestion_and_description()
                 return false
+
             if @options.electric_punctuation is true
                 @pair_char(event, stack) # Pair brackets/quotes

@@ -1838,18 +1839,30 @@
             move_outside = args.move_outside is true # So default value is false

             ch = @cursor_for_auto_completion.ch+suggestion_to_write.length
-            if suggestion_to_write[suggestion_to_write.length-1] is '(' and @count_not_closed_brackets('(') >= 0
-                @codemirror.setValue @query_first_part+suggestion_to_write+')'+@query_last_part
-                @written_suggestion = suggestion_to_write+')'
+
+            if @options.electric_punctuation is true
+                if suggestion_to_write[suggestion_to_write.length-1] is '(' and @count_not_closed_brackets('(') >= 0
+                    @codemirror.setValue @query_first_part+suggestion_to_write+')'+@query_last_part
+                    @written_suggestion = suggestion_to_write+')'
+                else
+                    @codemirror.setValue @query_first_part+suggestion_to_write+@query_last_part
+                    @written_suggestion = suggestion_to_write
+                    if (move_outside is false) and (suggestion_to_write[suggestion_to_write.length-1] is '"' or suggestion_to_write[suggestion_to_write.length-1] is "'")
+                        ch--
+                @codemirror.focus() # Useful if the user used the mouse to select a suggestion
+                @codemirror.setCursor
+                    line: @cursor_for_auto_completion.line
+                    ch:ch
             else
                 @codemirror.setValue @query_first_part+suggestion_to_write+@query_last_part
                 @written_suggestion = suggestion_to_write
                 if (move_outside is false) and (suggestion_to_write[suggestion_to_write.length-1] is '"' or suggestion_to_write[suggestion_to_write.length-1] is "'")
                     ch--
-            @codemirror.focus() # Useful if the user used the mouse to select a suggestion
-            @codemirror.setCursor
-                line: @cursor_for_auto_completion.line
-                ch:ch
+                @codemirror.focus() # Useful if the user used the mouse to select a suggestion
+                @codemirror.setCursor
+                    line: @cursor_for_auto_completion.line
+                    ch:ch
+

         # Select the suggestion. Called by mousdown .suggestion_name_li
         select_suggestion: (event) =>


get webui ubstuck

get webui ubstuck

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3729,7 +3729,7 @@

         remove: =>
             @view_object?.remove()
-            if @query_result.is_feed
+            if @query_result?.is_feed
                 @query_result.force_end_gracefully()
             if @set_scrollbar_scroll_handler?
                 $(window).unbind 'scroll', @set_scrollbar_scroll_handler


Added goal states listener

Added goal states listener

--- app/views/home_view.coffee
+++ app/views/home_view.coffee
@@ -98,7 +98,7 @@
     super()
     @turnOnStageUpdates()

-  onSimulateButtonClick: (e) ->
+  onSimulateButtonClick: (e) =>
     $.get "/queue/scoring", (data) =>
       levelName = data.sessions[0].levelID
       console.log data
@@ -121,6 +121,11 @@

         god.createWorld()

+        Backbone.Mediator.subscribe 'god:new-world-created', @onWorldCreated, @
+
+  onWorldCreated: (data) ->
+    console.log "GOAL STATES"
+    console.log data.goalStates

   filterProgrammableComponents: (thangs, spellToSourceMap) =>


Set default for key and index

Set default for key and index

--- lib/cache_storage.coffee
+++ lib/cache_storage.coffee
@@ -58,7 +58,7 @@

   # Flush given a NoFlo port, a key of a particular cache object, and the index
   # of the port to send
-  flushCache: (port, key, index) ->
+  flushCache: (port, key = null, index = null) ->
     return false unless @cache[key]
     { groupCache, dataCache } = @cache[key]
     @flush port, index, groupCache, dataCache


commandstream: Expose more functions

commandstream: Expose more functions

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -183,13 +183,11 @@
   return index

 closeCommunication = (payload, buffer, index) ->
-  console.log 'b', buffer, index
   index += writeCmd(buffer, index, cmdFormat.commands.End.id)
   return index

 # TODO: implement the inverse, getting a FBP protocol message from CS
 toCommandStreamBuffer = (message, componentLib, nodeMap, componentMap, buffer, index) ->
-  console.log 'm', message.command, index
   if message.protocol == 'graph'
     # TODO: also support removenode/removeedge/removeinitial
     if message.command == 'clear'
@@ -406,3 +404,6 @@
   format: cmdFormat
   parseReceivedCmd: parseReceivedCmd
   Buffer: Buffer
+  buildMappings: buildMappings
+  initialGraphMessages: initialGraphMessages
+  toCommandStreamBuffer: toCommandStreamBuffer


Pass project / tag phid in an array to projects.add and projects.remove

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -736,7 +736,7 @@
           .then (projectData) =>
             phid = projectData.data.phid
             if phid not in item.projectPHIDs
-              payload.data.push({ type: 'projects.add', value: phid })
+              payload.data.push({ type: 'projects.add', value: [phid] })
               payload.messages.push("been added to #{r[2]}")
             else
               payload.notices.push("T#{item.id} is already in #{r[2]}")
@@ -753,7 +753,7 @@
           .then (projectData) =>
             phid = projectData.data.phid
             if phid in item.projectPHIDs
-              payload.data.push({ type: 'projects.remove', value: phid })
+              payload.data.push({ type: 'projects.remove', value: [phid] })
               payload.messages.push("been removed from #{r[2]}")
             else
               payload.notices.push("T#{item.id} is already not in #{r[2]}")


:fire: Remove status from Editor-Linter

:fire: Remove status from Editor-Linter

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -3,7 +3,6 @@
 class EditorLinter
   constructor: (@editor) ->
     throw new Error("Given editor isn't really an editor") unless @editor instanceof TextEditor
-    @status = true # Overall linting status
     @emitter = new Emitter
     @subscriptions = new CompositeDisposable
     @subscriptions.add @editor.onDidDestroy =>


overall RC 0.51 fr

overall RC 0.51 fr

--- lib/router/main.coffee
+++ lib/router/main.coffee
@@ -38,7 +38,7 @@
       ]
     data: ->
       itemList: items.find({_id: this.params._id}).fetch();
-#    fastRender: true
+    fastRender: true
   @route "MyNotifications",
     path: "/MyNotifications"
     waitOn: ->
@@ -64,4 +64,4 @@
       ]
     data: ->
       Notifications.findOne({_id: this.params._id});
-#    fastRender: true+    fastRender: true


code climate feedback

code climate feedback

--- lib/ewg/generator.coffee
+++ lib/ewg/generator.coffee
@@ -25,7 +25,7 @@
     return true unless @config.hasOwnProperty 'stop_on_error'
     @config.stop_on_error

-  preventStopOnError: (stream) =>
+  preventStopOnError: (stream) ->
     stream.pipe(plumber())

   src: (src) =>
@@ -53,7 +53,7 @@

   generate: (cb) =>
     @task 'generate', =>
-      @repetitive (config, index) =>
+      @repetitive (config, index) ->
         cb(config, index)

@@ -65,7 +65,7 @@

     # for watching we generate numerated tasks of the childs
     @repetitive (config, index) =>
-      @task "generate-#{index}", =>
+      @task "generate-#{index}", ->
         cb(config)

   watchAll: (getSelector, runBaseTask = 'generate') =>


Wrap the arg building in a try

Wrap the arg building in a try

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -115,26 +115,27 @@
       argType = 'Selection Based'
       arg = selectedText

-    if not @runOptions.cmd? or @runOptions.cmd is ''
-      if not (argType of grammarMap[lang])
-        err = $$ ->
-          @p class: 'block', "#{argType} runner not available for #{lang}."
-          @p class: 'block', =>
-            @text 'If it should exist, add an '
-            @a href: "https://github.com/rgbkrk/atom-script/issues/\
-              new?title=Add%20support%20for%20#{lang}", 'issue on GitHub'
-            @text ', or send your own pull request.'
-
-        @handleError err
-        return false
-
-      # Precondition: lang? and lang of grammarMap
-      commandContext.command = grammarMap[lang][argType].command
-    else
-      commandContext.command = @runOptions.cmd
-
-    buildArgsArray = grammarMap[lang][argType].args
-    commandContext.args = buildArgsArray arg
+    try
+      if not @runOptions.cmd? or @runOptions.cmd is ''
+        # Precondition: lang? and lang of grammarMap
+        commandContext.command = grammarMap[lang][argType].command
+      else
+        commandContext.command = @runOptions.cmd
+
+      buildArgsArray = grammarMap[lang][argType].args
+      commandContext.args = buildArgsArray arg
+
+    catch error
+      err = $$ ->
+        @p class: 'block', "#{argType} runner not available for #{lang}."
+        @p class: 'block', =>
+          @text 'If it should exist, add an '
+          @a href: "https://github.com/rgbkrk/atom-script/issues/\
+            new?title=Add%20support%20for%20#{lang}", 'issue on GitHub'
+          @text ', or send your own pull request.'
+
+      @handleError err
+      return false

     # Update header
     @headerView.title.text "#{lang} - #{filename}"


account: add a relationship to the plans of a given account

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -229,6 +229,10 @@
         as          : 'payment method'
         targetType  : 'JPaymentMethod'

+      plan          :
+        as          : 'service plan'
+        targetType  : 'JPaymentPlan'
+
   constructor:->
     super
     @notifyOriginWhen 'PrivateMessageSent', 'FollowHappened'


cleanup

cleanup

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -39,8 +39,7 @@
     klass = class extends this
     Object.defineProperty klass, 'name', get: -> klassName
     klass::inner = inner
-    if allowForwarding
-      klass::allowForwarding = true
+    klass::allowForwarding = true if allowForwarding
     klass.extend()

   constructor: ->
@@ -556,6 +555,7 @@
     if range = @getRange(selection)
       swrap(selection).setBufferRange(range, {reversed: @reversed ? @backward})
       return true
+
 class SearchMatchBackward extends SearchMatchForward
   @extend()
   backward: true


"Indentation error"

"Indentation error"

--- lib/plunker/index.coffee
+++ lib/plunker/index.coffee
@@ -138,7 +138,7 @@
             new_file.mime ||= mime.lookup(filename)
             new_file.content ||= old_file.content
             old_files[new_file.filename] = new_file
-        plunk.files = old_files
+      plunk.files = old_files

     if errors.length then next(errors)
     else next(null, plunk)


Fixed front-matter import in publish-draft command

--- lib/publish-draft.coffee
+++ lib/publish-draft.coffee
@@ -1,5 +1,5 @@
 {$} = require "atom-space-pen-views"
-FrontMatter = require "./models/front-matter"
+FrontMatter = require "./front-matter"
 config = require "./config"
 utils = require "./utils"
 fs = require "fs-plus"


sort artist shows by end_at (seems to give the most shows)

--- models/artist.coffee
+++ models/artist.coffee
@@ -33,7 +33,7 @@
     @relatedPosts = new Backbone.Collection [], model: require('./post.coffee')
     @relatedPosts.url = "#{sd.API_URL}/api/v1/related/posts"
     @relatedShows = new Backbone.Collection [], model: require('./partner_show.coffee')
-    @relatedShows.url = "#{sd.API_URL}/api/v1/related/shows?artist[]=#{@id}"
+    @relatedShows.url = "#{sd.API_URL}/api/v1/related/shows?artist[]=#{@id}&sort=-end_at"
     @artworks = new Artworks
     @artworks.url = "#{@url()}/artworks"


use authenticate, 'cuz it's just better

use authenticate, 'cuz it's just better

--- models/person.coffee
+++ models/person.coffee
@@ -34,6 +34,7 @@
       myHostname: env.hostname
       consumerKey: env.twitter_app_id
       consumerSecret: env.secrets.twitter
+      authorizePath: '/oauth/authenticate'
       findOrCreateUser: (session, accessTok, accessTokExtra, twit) ->
         promise = @Promise()
         twitter = twitterAuthRename accessTok, accessTokExtra, twit


translate homepage messages

translate homepage messages

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -92,11 +92,11 @@

     messages = []
     if customization('frozen')
-      messages.push "The forum host has frozen this forum so no changes can be made."
+      messages.push translator "engage.frozen_message", "The forum host has frozen this forum so no changes can be made."
     if customization('anonymize_everything')
-      messages.push "The forum host has participation set to anonymous."
+      messages.push translator "engage.anonymize_message", "The forum host has participation set to anonymous in this forum, so you won't be able to see the identity of others at this time."
     if customization('hide_opinions')
-      messages.push "The forum host has hidden the opinions of other participants."
+      messages.push translator "engage.hide_opinions_message", "The forum host has hidden the opinions of other participants, so you won't be able to see their specific opinions at this time."

     DIV
       key: "homepage_#{subdomain.name}"


Basic resize implemented. Only works horizontally, but this is due to the fact that the vector we pass are still expressed in the window vector base, not in the local (rotated) base of the content

--- assets/js/BlockView.coffee
+++ assets/js/BlockView.coffee
@@ -2,8 +2,8 @@
 class @BlockView extends Backbone.View

   events:
-    'mousedown .tracker' : 'handlerDispatcher'
-    'mousedown .handleR' : 'handlerDispatcher'
+    'mousedown .tracker' : 'beginMove'
+    'mousedown .handleR' : 'beginRotate'
     'mousedown .dragbar' : 'handlerDispatcher'
     'mousedown .handle' : 'handlerDispatcher'

@@ -31,45 +31,31 @@
     )
     @render()

-  handlerDispatcher: (e) ->
-
-    if $(e.currentTarget).hasClass('tracker')
-      # passing a lot of data, for not having to look it
-      # up inside the handler
-      cr = @selectR.getBoundingClientRect()
-      data =
-        # the origin of the mouseon
-        origin :
-          x: e.pageX
-          y: e.pageY
-          # the initial position of @el
-        initialPosition : @$el.position()
-        bounds:
-          ox: (cr.width - @$el.width())/2
-          oy: (cr.height - @$el.height())/2
-          x: @$page.width() - (cr.width/2 + @$el.width()/2)
-          y: @$page.height() - (cr.height/2 + @$el.height()/2)
+  #
+  # Drag'n'Drop of the block
+  #
+  beginMove: (e)=>
+    # passing a lot of data, for not having to look it
+    # up inside the handler
+    cr = @selectR.getBoundingClientRect()
+    data =
+      # the origin of the mouseon
+      origin :
+        x: e.pageX
+        y: e.pageY
+        # the initial position of @el
+      initialPosition : @$el.position()
+      bounds:
+        ox: (cr.width - @$el.width())/2
+        oy: (cr.height - @$el.height())/2
+        x: @$page.width() - (cr.width/2 + @$el.width()/2)
+        y: @$page.height() - (cr.height/2 + @$el.height()/2)
       # are the 2 following events even possible considering
       # we are 'on' the image? hum....
-      $(document).on('mouseup', @endMove)
-      $(document).on('mouseleave', @endMove)
-      return @$page.on('mousemove', data, @move)
-
-    if $(e.currentTarget).hasClass('handleR')
-      offset = @$el.offset()
-      center =
-        x: offset.left + @$el.width()/2
-        y: offset.top  + @$el.height()/2
-      $(document).on('mouseup', @endRotate)
-      $(document).on('mouseleave', @endRotate)
-      return @$page.on('mousemove', center, @rotate)
-
-    #if $(e.currentTarget).hasClass('dragbar')
-
-    #if $(e.currentTarget).hasClass('handle')
-
-  # NB: from here we need to bind...
-  # (see backbone delegate method)
+    $(document).on('mouseup', @endMove)
+    $(document).on('mouseleave', @endMove)
+    return @$page.on('mousemove', data, @move)
+
   move: (e)=>
     bounds = e.data.bounds
     v =
@@ -85,11 +71,22 @@
     else if pos.top > bounds.y then pos.top = bounds.y
     @$el.css(pos)

-
   endMove: (e)=>
     @$page.off('mouseup',@endMove)
     @$page.off('mouseleave',@endMove)
     @$page.off('mousemove',@move)
+
+  #
+  # Rotation of the selectR subcontainer
+  #
+  beginRotate: (e)=>
+    offset = @$el.offset()
+    center =
+      x: offset.left + @$el.width()/2
+      y: offset.top  + @$el.height()/2
+    $(document).on('mouseup', @endRotate)
+    $(document).on('mouseleave', @endRotate)
+    return @$page.on('mousemove', center, @rotate)

   rotate: (e)=>
     # v is the vector from the center of the content to
@@ -118,6 +115,84 @@
     @$page.off('mouseleave',@endRotate)
     @$page.off('mousemove',@rotate)

+  #
+  # Dispatch events from dragbars and handles
+  # For now this just does a resize
+  #
+  handlerDispatcher: (e)=>
+
+    cards = ['n', 's', 'e', 'w', 'nw', 'ne', 'se', 'sw']
+    # we build a coefficient table, wich indicates the modication
+    # pattern corresponding to each cardinal
+    # pattern: [left,top,width,height]
+    coefs = [
+      [0, 1, 0, -1], #n
+      [0, 0, 0, 1],  #s
+      [0, 0, 1, 0],  #e
+      [1, 0, -1, 0], #w
+      [1, 1, -1, -1],#nw
+      [0, 1, 1, -1],#ne
+      [0, 0, 1, 1],  #se
+      [1, 0, -1, 1], #sw
+    ]
+
+    $t = $(e.currentTarget)
+
+    # we check which of the handler we are dealing with
+    index = null
+    for i,str of cards
+      cl = "ord-#{str}"
+      if($t.hasClass(cl)) then index = i
+
+    data =
+      # the origin of the mouseon
+      origin :
+        x: e.pageX
+        y: e.pageY
+        # the initial position of @el
+      initialPosition : @$el.position()
+      initialDimension:
+        width: @$el.width()
+        height: @$el.height()
+      # if isESES, we only change dimensions
+      # else, we change dimensionis and position of the el
+      coef: coefs[index]
+
+    $(document).on('mouseup', @endResize)
+    $(document).on('mouseleave', @endResize)
+    return @$page.on('mousemove', data, @resize)
+
+
+  resize: (e)=>
+    bounds = e.data.bounds
+    coef = e.data.coef
+    v =
+      x:e.pageX - e.data.origin.x
+      y:e.pageY - e.data.origin.y
+
+    style =
+      left : coef[0]*v.x + e.data.initialPosition.left
+      top : coef[1]*v.y + e.data.initialPosition.top
+      width: coef[2]*v.x + e.data.initialDimension.width
+      height: coef[3]*v.y + e.data.initialDimension.height
+
+    @$el.css(style)
+    ###
+    if pos.left < bounds.ox then pos.left = bounds.ox
+    else if pos.left > bounds.x then pos.left = bounds.x
+
+    if pos.top < bounds.oy then pos.top = bounds.oy
+    else if pos.top > bounds.y then pos.top = bounds.y
+    ###
+
+  endResize: (e)=>
+    $(document).off('mouseup',@endResize)
+    $(document).off('mouseleave',@endResize)
+    @$page.off('mousemove',@resize)
+
+  #
+  # render create all the dom elements and append them
+  #
   render: ->
     #cardinals that will be used throughout the rendering (maybe in other
     # functions too?)
@@ -129,6 +204,7 @@
     @selectR = @$selectR[0]
     @$el.append(@selectR)

+    #
     # dCt : display container that holds the content and the borders of the
     # content
     @$dCt = $('

',{
@@ -140,7 +216,7 @@
     # the content that we display
     @$content = $('',{
       src: 'assets/images/cat.jpg',
-      style:'border: none; visibility: visible; margin: 0px; padding: 0px; position: absolute; top: 0px; left: 0px; width: 100%; height: 100%;'
+      style:'border: none; visibility: visible; margin: 0px; padding: 0px; position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; -webkit-user-select:none;'
     })
     @content = @$content[0]
     @$dCt.append(@content)
@@ -160,7 +236,7 @@
     for e of @borders
       @$dCt.append(@borders[e])

-        #
+    #
     # hCt: Handler container that will hold all the handlers
     #
     @$hCt = $('

',{
@@ -168,7 +244,6 @@
     })
     @hCt = @$hCt[0]
     @$selectR.append(@hCt)
-

     # create the dragbars
     @dragbars = []
@@ -224,8 +299,6 @@
     @$tracker = $('

',{
       style:'cursor: move; position: absolute; z-index: 360;'
     })
-    # ADD A CROSS COMPATIBLE CSS PROPERTY TO MAKE IT UNSELECTABLE
-    @$tracker.css({'-webkit-user-select':'none'})
     @$tracker.addClass('jcrop-tracker')
     @$tracker.addClass('tracker')
     @tracker = @$tracker[0]
@@ -235,11 +308,3 @@

     # we finally attach the Block element to the page
     @$page.append(@el)
-
-
-
-
-
-
-
-


fix: Stop scroll animation for bubbling clicks

--- assets/js/animation.coffee
+++ assets/js/animation.coffee
@@ -16,7 +16,7 @@
       # Using jQuery's animate() method to add smooth page scroll
       # The optional number (800) specifies the number of milliseconds it takes
       # to scroll to the specified area (the speed of the animation)
-      $('html, body').animate
+      $('html, body').stop().animate
         scrollTop: $(hash).offset().top
         800
         ->


Fix a bug that caused the cursor position to be wrong after clearing the terminal

--- lib/terminal-view.coffee
+++ lib/terminal-view.coffee
@@ -66,12 +66,12 @@
     @moveCursorRelativeInCol(rowOffset)

   moveCursorRelativeInRow: (colOffset) ->
-    @cursorColIndex + colOffset
+    @cursorColIndex += colOffset
     leftOffset = colOffset * @charWidth
     @cursor.css('left',parseInt(@cursor.css('left'))+leftOffset)

   moveCursorRelativeInCol: (rowOffset) ->
-    @cursorRowIndex + rowOffset
+    @cursorRowIndex += rowOffset
     topOffset = rowOffset * @lineHeight
     @cursor.css('top',parseInt(@cursor.css('top'))+topOffset)


Set news expiration to a year.

Set news expiration to a year.

--- app/assets/javascripts/init.news.coffee
+++ app/assets/javascripts/init.news.coffee
@@ -11,4 +11,4 @@
   $newsTicker.find(".close-link").click (e) ->
     e.preventDefault()
     $newsTicker.hide()
-    Cookies cookieKey, newsDate, expires: 7
+    Cookies cookieKey, newsDate, expires: 365


link editor: demand profile save

link editor: demand profile save

--- app/coffeescripts/EditorLinkView.coffee
+++ app/coffeescripts/EditorLinkView.coffee
@@ -5,6 +5,13 @@
     'blur #link_name, #road_name, #link_type' : 'save'
     'blur #lanes, #lane_offset, #length, #queue_limit' : 'save'
     'blur #capacity, #capacity_drop, #jam_density, #critical_density' : 'saveFD'
+    'blur #coefficient, #demand_profile' : 'saveDP'
+    'blur #link_demand_start_hour,
+      #link_demand_start_minute,
+      #link_demand_start_second,
+      #link_demand_sample_hour,
+      #link_demand_sample_minute,
+      #link_demand_sample_second' : 'saveDPTime'
     'blur #description' : 'saveDesc'
     'click #record' : 'saveRecord'
     'click #edit-signal' : 'signalEditor'
@@ -92,7 +99,27 @@
     fdp = @model.get('fundamentaldiagramprofile')
     fd = fdp?.get('fundamentaldiagram')[0]
     fd.set(id, $("##{id}").val())
-
+
+  # this saves fields in the demand profiles
+  saveDP: (e) ->
+    id = e.currentTarget.id
+    dp = @model.get('demandprofile')
+    d = fdp?.get('demand')[0]
+    d.set(id, $("##{id}").val())
+
+  # this saves fields in the demand profiles time
+  saveDPTime: (e) ->
+    dp = @model.get('demandprofile')
+    d = dp?.get('demand')[0]
+    start_hour = $("#link_demand_start_hour").val()
+    start_minute = $("#link_demand_start_minute").val()
+    start_second = $("#link_demand_start_second").val()
+    sample_hour = $("#link_demand_sample_hour").val()
+    sample_minute = $("#link_demand_sample_minute").val()
+    sample_second = $("#link_demand_sample_second").val()
+    d.set('start_time', "#{start_hour}:#{start_minute}:#{start:second}")
+    d.set('sample_period', "#{sample_hour}:#{sample_minute}:#{sample:second}")
+
   # These three methods below will be configured to launch various
   # editors in future phases
   signalEditor: (e) ->


ensure that every doc retrieved through search has an id field.

--- app/controllers/index_controller.coffee
+++ app/controllers/index_controller.coffee
@@ -58,7 +58,12 @@
                     send 500
                 else
                     results = []
-                    results.push doc.doc for doc in docs
+                    for doc in docs
+                        if doc.doc?
+                            resDoc = doc.doc
+                            resDoc.id = doc.id
+                            results.push resDoc
+
                     send rows: results, 200


use jeet

use jeet


(fix) removed restify from Deploy and promisifying

--- src/steroids/Deploy.coffee
+++ src/steroids/Deploy.coffee
@@ -1,40 +1,29 @@
 fs = require "fs"
-path = require "path"
-util = require "util"

-restify = require "restify"
+request = require "request"
 restler = require "restler"
-open = require "open"

 paths = require "./paths"
+Login = require "./Login"
+CloudConfig = require "./CloudConfig"
 DeployConverter = require "./DeployConverter"
-Login = require "./Login"
-
-Help = require "./Help"
-
-CloudConfig = require "./CloudConfig"

 class Deploy

   @DeployError: class DeployError extends steroidsCli.SteroidsError

-  constructor: (@options={})->
+  constructor: (@options={}) ->
+    @converter = new DeployConverter paths.application.configs.application
     @cloudConfig = JSON.parse(fs.readFileSync(paths.application.configs.cloud, "utf8")) if fs.existsSync paths.application.configs.cloud
+    @cloudUrl = steroidsCli.options.argv.cloudUrl || "https://cloud.appgyver.com"

-    @converter = new DeployConverter paths.application.configs.application
-
-    ankaURL = steroidsCli.options.argv.ankaURL || "https://anka.appgyver.com"
-
-    @client = restify.createJsonClient
-      url: ankaURL
-
-  run: () =>
+  run: =>
     Updater = require "./Updater"
     updater = new Updater
     updater.check
       from: "deploy"

-    return new Promise (resolve, reject) =>
+    new Promise (resolve, reject) =>
       Project = require "./Project"

       project = new Project
@@ -42,115 +31,121 @@
           onSuccess: =>
               project.package
                 onSuccess: =>
-                  @uploadToCloud ()=>
+                  @deploy().then =>
                     resolve()
-                  , @options
+                  .catch (error) =>
+                    reject error
                 onFailure: =>
                   reject new DeployError "package failed"
             onFailure: =>
               reject new DeployError "make failed"

+  deploy: =>
+    new Promise (resolve, reject) =>
+      steroidsCli.log "Uploading application to AppGyver Cloud."
+      @uploadApplicationJSON()
+        .then(@uploadApplicationZip)
+        .then(@updateConfigurationFile)
+        .then ->
+          resolve()
+        .catch (error) ->
+          reject error

-  uploadToCloud: (callback, options)->
-    @client.basicAuth Login.currentAccessToken(), 'X'
+  uploadApplicationJSON: =>
+    new Promise (resolve, reject) =>
+      @app = @converter.applicationCloudSchemaRepresentation()

-    @uploadApplicationJSON ()=>
-      @uploadApplicationZip ()=>
-        @updateConfigurationFile(callback, options)
+      if fs.existsSync paths.application.configs.cloud
+        cloudConfig = JSON.parse fs.readFileSync(paths.application.configs.cloud, "utf8")
+        @app.id = cloudConfig.id

-  uploadApplicationJSON: (callback)->
-    # util.log "Updating application configuration"
-    util.log "Uploading Application to cloud"
+      requestData =
+        application: @app

-    @app = @converter.applicationCloudSchemaRepresentation()
+      if @app.id?
+        steroidsCli.debug "DEPLOY", "Updating existing app with id #{@app.id}"
+        method = "put"
+        endpoint = "/studio_api/applications/#{@app.id}"
+      else
+        steroidsCli.debug "Creating new app"
+        method = "post"
+        endpoint = "/studio_api/applications"

-    if fs.existsSync paths.application.configs.cloud
-      # util.log "Application has been deployed before"
-      cloudConfig = JSON.parse fs.readFileSync(paths.application.configs.cloud, 'utf8')
-      @app.id = cloudConfig.id
-      # util.log "Using cloud ID: #{cloudConfig.id}"
+      @cloudUpload(method, endpoint, requestData).then (data) =>
+        steroidsCli.debug "DEPLOY", "Got cloud upload response"
+        @cloudApp = data
+        resolve()
+      .catch (error) ->
+        Help = require "./Help"
+        Help.error()
+        reject error

-    # util.log "Uploading #{JSON.stringify(@app)}"
+  cloudUpload: (method, endpoint, json) =>
+    new Promise (resolve, reject) =>
+      request
+        auth:
+          user: Login.currentAccessToken()
+          password: "X"
+        method: method
+        json: json
+        url: "#{@cloudUrl}#{endpoint}"
+      , (err, res, data) ->
+        if err?
+          reject new DeployError "Could not connect to cloud.appgyver.com"
+        else if res.statusCode == 200 or res.statusCode == 201
+          resolve(data)
+        else
+          reject new DeployError """
+          Check that you have correct app id in config/cloud.json. Try removing the file and a new cloud.json file will be created.
+          """

-    requestData =
-      application: @app
+  uploadApplicationZip: =>
+    new Promise (resolve, reject) =>
+      sourcePath = paths.temporaryZip

-    restifyCallback = (err, req, res, obj)=>
-      steroidsCli.debug "RECEIVED APPJSON SYNC RESPONSE"
-      # Way too spammy logging:
-      #steroidsCli.debug "err: #{util.inspect(err)}"
-      #steroidsCli.debug "req: #{util.inspect(req)}"
-      #steroidsCli.debug "res: #{util.inspect(res)}"
-      #steroidsCli.debug "obj: #{util.inspect(obj)}"
+      params =
+        success_action_status: "201"
+        utf8: ""
+        key: @cloudApp.custom_code_zip_upload_key
+        acl: @cloudApp.custom_code_zip_upload_acl
+        policy: @cloudApp.custom_code_zip_upload_policy
+        signature: @cloudApp.custom_code_zip_upload_signature
+        AWSAccessKeyId: @cloudApp.custom_code_zip_upload_access_key
+        file: restler.file(
+          sourcePath, # source path
+          "custom_code.zip", # filename
+          fs.statSync(sourcePath).size, # file size
+          "binary", # file encoding
+          "application/octet-stream") # file content type

-      unless err
-        steroidsCli.debug "RECEIVED APPJSON SYNC SUCCESS"
-        @cloudApp = obj
-        callback()
-      else
-        steroidsCli.debug "RECEIVED APPJSON SYNC FAILURE"
-        # util.log "err: #{util.inspect(err)}"
-        # util.log "obj: #{util.inspect(obj)}"
-        Help.error()
-        console.log "Failed with error: #{err.body.error}."
-        console.log "Check that you have correct app id in config/cloud.json. Try removing the file and a new cloud.json file will be created."
-        process.exit 1
+      uploadRequest = restler.post @cloudApp.custom_code_zip_upload_url, { multipart: true, data:params }

-    if @app.id?
-      steroidsCli.debug "Updating existing app with id #{@app.id}"
-      # util.log "PUT"
-      @client.put "/studio_api/applications/#{@app.id}", requestData, restifyCallback
-    else
-      steroidsCli.debug "Creating new app"
-      # util.log "POST"
-      @client.post "/studio_api/applications", requestData, restifyCallback
+      uploadRequest.on "success", ->
+        steroidsCli.debug "DEPLOY", "Updated application zip to S3"
+        resolve()

-  uploadApplicationZip: (callback)->
-    sourcePath = paths.temporaryZip
-    # util.log "Updating application build from #{sourcePath} to #{@cloudApp.custom_code_zip_upload_url}"
-    # util.log "key #{@cloudApp.custom_code_zip_upload_key}"
+      uploadRequest.on "error", ->
+        reject new DeployError "Error uploading application code to cloud."

-    params =
-      success_action_status: "201"
-      utf8: ""
-      key: @cloudApp.custom_code_zip_upload_key
-      acl: @cloudApp.custom_code_zip_upload_acl
-      policy: @cloudApp.custom_code_zip_upload_policy
-      signature: @cloudApp.custom_code_zip_upload_signature
-      AWSAccessKeyId: @cloudApp.custom_code_zip_upload_access_key
-      file: restler.file(
-        sourcePath, # source path
-        "custom_code.zip", # filename
-        fs.statSync(sourcePath).size, # file size
-        "binary", # file encoding
-        'application/octet-stream') # file content type
+  updateConfigurationFile: =>
+    new Promise (resolve, reject) =>
+      steroidsCli.debug "DEPLOY", "Updating #{paths.application.configs.cloud}"

-    uploadRequest = restler.post @cloudApp.custom_code_zip_upload_url, { multipart: true, data:params }
-    uploadRequest.on 'success', ()=>
-      # util.log "Updated application build"
-      callback()
+      cloudConfig = new CloudConfig
+        id: @cloudApp.id
+        identification_hash: @cloudApp.identification_hash

-  updateConfigurationFile: (callback, options)->
-    # util.log "Updating #{paths.application.configs.cloud}"
+      cloudConfig.saveSync()
+      config = cloudConfig.getCurrentSync()

-    cloudConfig = new CloudConfig
-      id: @cloudApp.id
-      identification_hash: @cloudApp.identification_hash
+      shareBaseUrl = steroidsCli.options.argv.shareURL || "https://share.appgyver.com"
+      shareUrl = "#{shareBaseUrl}/?id=#{config.id}&hash=#{config.identification_hash}"
+      steroidsCli.log "\nShare url: #{shareUrl}"

-    cloudConfig.saveSync()
+      if steroidsCli.options.argv.share
+        open = require "open"
+        open shareUrl

-    config = cloudConfig.getCurrentSync()
-
-    shareProtocolAndHost = steroidsCli.options.argv.shareURL || "https://share.appgyver.com"
-    fullShareURL = "#{shareProtocolAndHost}/?id=#{config.id}&hash=#{config.identification_hash}"
-
-    if options?.sharePage == false
-      # TODO: pull this logic out of here...
-    else
-      util.log "Opening URL #{fullShareURL} in default web browser ..."
-      open fullShareURL
-
-    if callback?
-      callback()
+      resolve()

 module.exports = Deploy


Add api points

Add api points

--- api/points.coffee
+++ api/points.coffee
@@ -3,11 +3,20 @@
 User = require '../models/user'
 Team = require '../models/team'
 Game = require '../models/game'
+Point = require '../models/point'
 GameController = require '../controllers/game_controller'

 # CONTROLLER
 # ----------
 module.exports = (app) ->
+
+
+    # GET /api/points
+    app.get '/api/points', 'points.list', (req, res) ->
+        list = []
+        Point.findAll().then (points) ->
+            list.push formatPoint(point) for point in points
+            res.json list

     # GET /api/points/:id/check
     app.get '/api/points/:code/check', (req, res) ->
@@ -27,9 +36,6 @@
                             point: game_point
                 )

-
-
-
 # METHODS
 # -------


correct count on delete

correct count on delete

--- app/assets/javascripts/effective_assets/asset_box.js.coffee
+++ app/assets/javascripts/effective_assets/asset_box.js.coffee
@@ -3,34 +3,22 @@
   obj.closest('.error').removeClass('error')
   obj.parent().siblings('.help-inline,.inline-errors').remove()

-$(document).on 's3_uploads_complete', (_, uploader) ->
+$(document).on 's3_uploads_complete', (_, uploader) ->
   uploader.closest('.asset-box-input').find('.filter-attachments').val('')

-$(document).on 's3_upload_failed', (_, uploader, content) ->
+$(document).on 's3_upload_failed', (_, uploader, content) ->
   uploader.closest('.asset-box-input').find('.filter-attachments').val('')
   alert("An error occurred while uploading #{content.filename}.\n\nThe returned error message is: '#{content.error_thrown}'\n\nPlease try again.")

 $(document).on 'click', 'a.attachment-remove', (event) ->
   event.preventDefault()
-  attachment_div = $(event.target).closest('.attachment')
+  $attachmentDiv = $(event.target).closest('.attachment')

-  attachment_div.find('input.asset-box-remove').first().val(1)
-  attachment_div.hide()
+  $attachmentDiv.find('input.asset-box-remove').first().val(1)
+  $attachmentDiv.hide()

-  # Show the first 'limit' attachments, hide the rest
-  asset_box_input = attachment_div.closest('div.asset-box-input')
-  limit = asset_box_input.data('limit')
+  # Correct the attachment count
+  $assetBoxInput = $attachmentDiv.closest('div.asset-box-input')
+  count = parseInt($assetBoxInput.attr('data-attachment-count'), 10)
+  $assetBoxInput.attr('data-attachment-count', count - 1)

-  asset_box_input.find("input.asset-box-remove").each (index) ->
-    if "#{$(this).val()}" == '1' # If we're going to delete it...
-      $(this).closest('.attachment').hide()
-      limit = limit + 1
-      return
-
-    if index >= limit
-      $(this).closest('.attachment').hide()
-    else
-      $(this).closest('.attachment').show()
-
-
-


OX-306 fixed wrong decoding of buckets

OX-306 fixed wrong decoding of buckets

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -127,12 +127,14 @@

           @updateConnectionInfo(new Date() - roundTripBeginTime, batch.length)

+          offset = 0
+
           for bucket, i in batch

-            if transmitBuffer[5 * i + 1]
-              bucketData = @decode(responseBuffer.subarray(i * (@cube.BUCKET_LENGTH >> 1), (i + 1) * (@cube.BUCKET_LENGTH >> 1)))
+            if transmitBuffer[i * 5 + 1]
+              bucketData = @decode(responseBuffer.subarray(offset, offset += (@cube.BUCKET_LENGTH >> 1)))
             else
-              bucketData = responseBuffer.subarray(i * @cube.BUCKET_LENGTH, (i + 1) * @cube.BUCKET_LENGTH)
+              bucketData = responseBuffer.subarray(offset, offset += @cube.BUCKET_LENGTH)

             #console.log "Success: ", bucket
             @cube.setBucketByZoomedAddress(bucket, bucketData)
@@ -185,9 +187,9 @@

     new ArrayBufferSocket(
       senders : [
-        # new ArrayBufferSocket.WebWorker("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
-        # new ArrayBufferSocket.WebSocket("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
-        new ArrayBufferSocket.XmlHttpRequest("/binary/ajax?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
+        # new ArrayBufferSocket.WebWorker("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
+        # new ArrayBufferSocket.WebSocket("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
+        new ArrayBufferSocket.XmlHttpRequest("/binary/ajax?dataSetId=#{@dataSetId}&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
       ]
       requestBufferType : Float32Array
       responseBufferType : Uint8Array


Field examples - add link to PDF guide

Field examples - add link to PDF guide

--- content/field-examples.coffee
+++ content/field-examples.coffee
@@ -5,7 +5,7 @@
                  '''

     figures: [{
-      label: ''
+      label: 'Download the idenitification guide (16.5MB PDF)'
       image: "examples/species.jpg"
       alt: "Screenshot of the species identification tool."
     }]


#1 Возможность валидации филда, к которому привязан wysihat

--- app/assets/javascripts/sitemplate_wysihat_handler.js.coffee
+++ app/assets/javascripts/sitemplate_wysihat_handler.js.coffee
@@ -10,9 +10,10 @@
       @editor = WysiHat.Editor.attach($(editor))
       @editor.handler = self
       self.cfg = cfg
+      self.DEFAULT_CONTENT = '
'
       @load()
       if @editor.html().length == 0
-        self.setContent('
')
+        self.setContent(self.DEFAULT_CONTENT)

       @editor.addClass 'clearfix'

@@ -334,7 +335,9 @@
       return 'wysihat-framed' in @cfg.styles

     content: () ->
-      return @editor.html()
+      content = @editor.html()
+      return '' if content == @DEFAULT_CONTENT
+      return content

     setContent: (text) ->
       return @editor.html(text)


Color

Color

--- site/app.coffee
+++ site/app.coffee
@@ -191,23 +191,23 @@

 activity_level = (ili, mean, stdev) ->
   l = (ili-mean)/stdev
-  if l < 1.1
+  if l < 0
     return 1
-  if l < 2.5
+  if l < 1
     return 2
-  if l < 4.0
+  if l < 2
     return 3
-  if l < 4.9
+  if l < 3
     return 4
-  if l < 6.0
+  if l < 4
     return 5
-  if l < 7.6
+  if l < 5
     return 6
-  if l < 8.0
+  if l < 6
     return 7
-  if l < 9.5
+  if l < 7
     return 8
-  if l < 12
+  if l < 8
     return 9
   return 10

@@ -442,7 +442,6 @@
       datestr = datestr + "-" + date2String(date) + ")"
       @dataTimeline.html("Nowcasting epi-week " + epiweek2%100 + " " + datestr)
       callback1 = (epidata) =>
-        alert(epidata.length)
         NonInfluenzaData = calculateNonInfluenzaData(epidata)
         callback2 = (epidata) =>
           @colors = {}


Default settings to empty array

Default settings to empty array

--- src/text-mate-theme.coffee
+++ src/text-mate-theme.coffee
@@ -9,7 +9,8 @@
     @buildRulesets()

   buildRulesets: ->
-    {settings} = plist.parseStringSync(@contents)
+    {settings} = plist.parseStringSync(@contents) ? {}
+    settings ?= []

     for setting in settings
       {scope, name} = setting.settings


oauth: add odesk oauth connection

oauth: add odesk oauth connection

--- client/landing/app/Applications/Account.kdapplication/account/accSettingsPersLinkedAccts.coffee
+++ client/landing/app/Applications/Account.kdapplication/account/accSettingsPersLinkedAccts.coffee
@@ -4,7 +4,8 @@

     data =
       items : [
-        { title : "GitHub",   type : "github",    linked : no, account : ""}
+        { title : "GitHub", type : "github", linked : no, account : ""},
+        { title : "Odesk", type : "odesk", linked : no, account : ""},
       ]

     super options, data
@@ -45,12 +46,14 @@
       @provider = provider
       @viewAppendedHelper()

-  getProvider:-> @provider
+  getProvider:(name)->
+    if name is "github" then @githubProvider else @odeskProvider

   click:(event)->
+    {type} = @getData()
+
     if $(event.target).is "a.delete-icon"
-      {type} = @getData()
-      @getDelegate().emit "UnlinkAccount", accountType : @getData().type
+      @getDelegate().emit "UnlinkAccount", accountType : type

       notify = (message)->
         new KDNotificationView
@@ -63,7 +66,11 @@
           @provider = null
           @viewAppendedHelper()
     else
-      KD.utils.openGithubPopUp()  unless @getProvider()
+      unless @getProvider() # TODO: why is this here?
+        if type is "github"
+          KD.utils.openGithubPopUp()
+        else
+          KD.utils.openOdeskPopUp()

   viewAppendedHelper:->
     @setTemplate @pistachio()
@@ -71,29 +78,30 @@

   viewAppended:->
     KD.remote.api.JUser.fetchUser (err, user)=>
-      @provider = user.foreignAuth?.github
+      @githubProvider = user.foreignAuth?.github
+      @odeskProvider  = user.foreignAuth?.odesk
       @viewAppendedHelper()

-  getLinkedString:->
-    if @getProvider() then "Linked" else "Not linked."
+  getLinkedString:(provider)->
+    if @getProvider(@getData().type) then "Linked" else "Not linked."

-  getLinkedClass:->
-    if @getProvider() then "yes" else "no"
+  getLinkedClass:(provider)->
+    if @getProvider(@getData().type) then "yes" else "no"

-  getAccountString:->
-    if @getProvider() then "" else "Link now."
+  getAccountString:(provider)->
+    if @getProvider(@getData().type) then "" else "Link now."

   pistachio:->
     """

-      Github
+      #{@getData().title}

       #{@getLinkedString()}
-      #{@getAccountString()}
+      #{@getAccountString()}

     """


Use faye for websocket request checks. No need to duplicate them.

--- src/trans-websocket.coffee
+++ src/trans-websocket.coffee
@@ -12,18 +12,10 @@

 exports.app =
     _websocket_check: (req, connection, head) ->
-        # Request via node.js magical 'upgrade' event.
-        if (req.headers.upgrade || '').toLowerCase() isnt 'websocket'
+        if not FayeWebsocket.isWebSocket(req)
             throw {
                 status: 400
-                message: 'Can "Upgrade" only to "WebSocket".'
-            }
-        conn = (req.headers.connection || '').toLowerCase()
-
-        if (conn.split(/, */)).indexOf('upgrade') is -1
-            throw {
-                status: 400
-                message: '"Connection" must be "Upgrade".'
+                message: 'Not a valid websocket request'
             }

     sockjs_websocket: (req, connection, head) ->


Extend range of characters

Extend range of characters

--- app/scripts/components/TextInput.coffee
+++ app/scripts/components/TextInput.coffee
@@ -60,7 +60,7 @@

   _updateText: (name, index, movement) ->
-    chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!$ '
+    chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.=!$& '

     letter = name[index]
     lindex = chars.indexOf(letter) + chars.length


fix typo in context menu

fix typo in context menu

--- menus/blame.cson
+++ menus/blame.cson
@@ -2,7 +2,7 @@
 'context-menu':
   'atom-text-editor': [
     {
-      'label': 'Toggle blamee'
+      'label': 'Toggle blame'
       'command': 'blame:toggle'
     }
   ]


replace the /gc endpoint with a automatic gc every 3 minutes


chenge p10 to past tense

chenge p10 to past tense

--- app/lib/en.coffee
+++ app/lib/en.coffee
@@ -1134,7 +1134,7 @@
     p7: "The Galaxy Zoo site relaunched with an updated design in September 2014. In this version we combined new imaging from Sloan, giving us our best ever view of the local Universe, with the most distant images yet from Hubble's CANDELS survey. The CANDELS survey makes use of the new Wide Field Camera 3 — installed during the final shuttle mission to Hubble — to take ultra-deep images of the Universe. Our first surprise in this data was that contrary to predictions, galactic bars are still found in the most distant galaxies. Stay tuned to see what else we’ll discover!"
     p8: '''The relaunched site also allowed us more flexibility to include smaller sets of galaxies. We have included images taken with the United Kingdom Infrared Telescope (UKIRT), in Hawaii, for the recently completed UKIDSS project. UKIDSS is the largest, deepest survey of the sky at infrared wavelengths. The UKIDSS infrared-light pictures and the SDSS visible-light pictures are different for two reasons. Firstly, older stars are brighter in infrared wavelengths (while in visible-light we see younger stars), and secondly, infrared light, like a fog light, can penetrate the dust and smoke in the centres of galaxies. Examining the UKIDSS images in Galaxy Zoo will enable us to see how old and young stars are arranged differently in the galaxies, and will reveal more of the structure in the inner parts of galaxies.'''
     p9: '''The data being put into Galaxy Zoo can now change quickly, thanks to the dedicated work of volunteers and the "retirement" of images that have already been completed. One of the new sets of images to be classified comes from the Dark Energy Camera Legacy Survey (DECaLS), a survey which uses DECam, one of the most sensitive and widest-area cameras ever built and a key tool in our search to measure and understand dark energy. The camera, which is mounted on the 4-m Blanco telescope at CTIO in Chile, images the sky at similar wavelengths to SDSS, but with better resolution and with the ability to detect much fainter galaxies. These images are extending the reach of the science we began with Galaxy Zoo 2 and SDSS. Recently, the site also had Galaxy Zoo's first-ever images of fully simulated galaxies; these were images that were created entirely on a computer, using sophisticated simulations of the Universe starting just after the Big Bang and then watching as dark matter, gas, and stars evolve to form the galaxies we see today. The classifications of these images, which came from the Illustris project, are a vital test of the physics that go into these simulations.'''
-    p10: '''Starting in March 2016 the site include our second set of DECaLS images, this time taken from the survey's second data release. This new data release covers a larger portion of the sky than the first. Finally, we have included a set of "lost images" from SDSS that should have been included in the Galaxy Zoo 2 sample. These images will fill in the hole that currently exists in the bright galaxy data set.'''
+    p10: '''Starting in March 2016 the site include our second set of DECaLS images, this time taken from the survey's second data release. This new data release coverered a larger portion of the sky than the first. We also included a set of "lost images" from SDSS that should have been included in the Galaxy Zoo 2 sample. These images filled in the hole that existed in the bright galaxy data set.'''
     p11: 'With increasing number of very distant galaxies entering Galaxy Zoo (like the Hubble and CANDELS sets), it was clear that the GZ team needed to find a way to measure and account for any dependence classifications might have on galaxy distance. So, in 2013 a set of 288 nearby SDSS galaxies were simulated using the FERENGI code to create images of these galaxies as viewed at varying distances and brightnesses. The classifications for these were used with great success in debiasing the Galaxy Zoo: Hubble catalog. To continue this effort, in 2016 a second, larger sample of 936 galaxies were simulated again with FERENGI to create 7,488 images of the same galaxies at eight viewing distances. The new FERENGI classifications will aid in the analysis of GZ Hubble data to be used in some exciting high-redshift science projects currently in prep!'

   team:


Css function updated

Css function updated

The css function now updated to recognize objects that are passed into parameter 1
example:
```js
pen("p").Css({
  color:"blue",
  'background-color':"rgba(155,155,255,0.5)"
})

--- lib/Pen.coffee
+++ lib/Pen.coffee
@@ -1,7 +1,7 @@
 ((window) ->
   type = do ->
     classToType = {}
-    ref = "Boolean Number String Function Array Date RegExp Undefined Null".split /\s+/
+    ref = "Boolean Number String Function Array Date RegExp Undefined Null Element Document Window".split /\s+/
     for name,i in ref
       classToType["[object #{name}]"] = name.toLowerCase()
     (obj) ->
@@ -30,9 +30,9 @@
       return pen.cre["el"]
     pen.Attr = (stroobj, str) ->
       el = pen.accesel()
-      if stroobj?
+      if stroobj isnt null
         if type(stroobj) is 'string'
-          if str?
+          if str isnt null
             el.setAttribute stroobj, str
             return el
           else return el.getAttribute stroobj
@@ -52,7 +52,7 @@
       el
     pen.Html = (str, app = false) ->
       el = pen.accesel()
-      if str?
+      if str isnt null
         if app is false
           el.innerHTML = str
         else
@@ -63,15 +63,22 @@
       return el
     pen.Value = (str, app = false) ->
       el = pen.accesel()
-      if str?
+      if str isnt null
         if app is false then el.value = str else el.value += str
         return el
       else
         return el.value
       return el
-    pen.Css = (type, str) ->
+    pen.Css = (rules, str) ->
       el = pen.accesel()
-      el.style[type] = str
+      if type(rules) is 'object'
+        for rule of rules
+          el.style[rule] = rules[rule]
+      else if type(rules) is 'string'
+        el.style[rules] = str
+      else
+        err = new Error "parameter 1 can't be #{type el}"
+        throw err
       return el
     pen.On = (type, func, cp=false) ->
       el = pen.accesel()
@@ -92,20 +99,34 @@
       return el
     pen.Href = (hr) ->
       el = pen.accesel()
-      el.setAttribute "href", hr
-      return el
+      if type(hr) is 'string'
+        el.setAttribute "href", hr
+        return el
+      else
+        err = new Error "main parameter 1, can only be a string"
+        throw err
     pen.options = {}
     pen.options["auto append"] = false
     pen.options["to selector"] = false
     pen.options["normally append to"] = "body"
     pen.setOptions = (optionname, val) ->
-      if val?
-        pen.options[optionname] = val
+      ops = {
+        "auto append"
+        "to selector"
+        "normally append to"
+      }
+      if val isnt null
+        if optionname is ops[optionname]
+          pen.options[optionname] = val
+        else
+          err = new Error "unrecgonized option #{optionname}"
+          throw err
       else
         for option of optionname
           pen.options[option] = optionname[option]
       return undefined
     pen.GetOpitions = (option) -> if option? then pen.options[option] else pen.options
+    pen.Type = (param) -> type(param)
     return pen
   if typeof pen is 'undefined'
     window.pen = Pendef()


nothing

nothing

--- app/views/tagging/update_tags.js.coffee
+++ app/views/tagging/update_tags.js.coffee
@@ -1 +1 @@
-#alert 'Ajax Response!'
+


set font on svg

set font on svg

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -125,7 +125,9 @@
       d.y0 = y0
       d.value = y)

-  svg = d3.select(container).append('svg')
+  svg = d3.select(container)
+    .append('svg')
+    .style({'font-family': '"Helvetica Neue"'})
     .on('mousemove', (d, i) -> mouseMoves.push d3.mouse g.node())

   title = svg.append('text')
@@ -133,7 +135,7 @@

   # TODO font
   g = svg
-    .append("g").style({'font-family': '"Helvetica Neue"'})
+    .append("g")

   xAxisG = g.append('g')
     .attr('class', 'x axis')


Client: Activity: Deleted unnecessary whitespaces

--- client/activity/lib/components/chatinputwidget/index.coffee
+++ client/activity/lib/components/chatinputwidget/index.coffee
@@ -316,8 +316,8 @@

   onResize: ->

-    ChatPaneBody              = document.querySelector '.ChatPane-body'
-    scrollContainer           = ChatPaneBody.querySelector '.Scrollable'
+    ChatPaneBody    = document.querySelector '.ChatPane-body'
+    scrollContainer = ChatPaneBody.querySelector '.Scrollable'

     { scrollTop, scrollHeight } = scrollContainer


Update es-419.coffee

Update es-419.coffee

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -14,8 +14,8 @@
     for_developers: "Para Desarrolladores" # Not currently shown on home page.
     or_ipad: "O descargar para iPad"
     hoc_class_code: "Tengo un Código de Clase"
-#    hoc_enter: "Enter"
-#    hoc_title: "Hour of Code?"
+    hoc_enter: "¿Entrar"
+    hoc_title: " a la Hora de Código?"

   nav:
     play: "Jugar" # The top nav bar entry where players choose which levels to play
@@ -619,9 +619,9 @@
     cost_premium_server: "CodeCombat es gratis para lso primeros 5 niveles, después de ellos cuesta $9.99 USD mensuales para accesar a nuestros otros más de 190 niveles en nuestraos exclusivos servidores específicos para cada país."
     free_1: "Hay más de 110 niveles gratis que cubren cada concepto."
     free_2: "Una suscripción mensual le da acceso a tutoriales en vídeo y niveles extra para practicar."
-#    free_3: "The CodeCombat content is divided into"
+    free_3: "El contenido de CodeCombat está dividido en"
     free_4: "cursos"
-#    free_5: ".  The first course is free, and about an hour of material."
+    free_5: ".  El primer curso es gratis, y aproximadamente una hora de material."
 #    free_6: "Access to the additional courses can be unlocked with a one-time purchase."
     teacher_subs_title: "¡Los amestros obtienen suscripciones gratuitas!" # {change}
     teacher_subs_0: "Ofrecemos suscripciones gratuitas a profesores con propósitos de evaluación." # {change}
@@ -676,14 +676,14 @@
     approved_1: "Su aplicación a una suscripción gratuita fue" # {change}
     approved_2: "aprobada." # {change}
 #    approved_4: "You can now enroll your students on the"
-#    approved_5: "courses"
+    approved_5: "cursos"
 #    approved_6: "page."
     denied_1: "Su aplicación a una suscripción gratuita ha sido" # {change}
     denied_2: "denegada."
     contact_1: "Por favor contactarse"
     contact_2: "si tiene más preguntas."
     description_1: "Ofrecemos suscripciones gratuitas a maestros con propósitos de evaluación. Puede hallar más información en nuestra"
-#    description_1b: "You can find more information on our"
+    description_1b: "Puede hallar más información en nuestra"
     description_2: "página"
     description_3: "de maestros."
     description_4: "Por favor llene esta encuesta rápida y le mandaremos por email las instrucciones de configuración."
@@ -855,7 +855,7 @@
     started_2: "Iniciado"
     not_started_2: "No inciiado"
     view_solution: "Click para ver la solución."
-#    view_attempt: "Click to view attempt."
+    view_attempt: "Click para ver intento."
     latest_achievement: "último logro"
     playtime: "Tiempo de juego"
     last_played: "Último jugado"


Uncommented some translations

Uncommented some translations

--- app/locale/bg.coffee
+++ app/locale/bg.coffee
@@ -413,20 +413,20 @@
     subscribe_title: "Абонирай се"
     unsubscribe: "Прекрати абонамента"
     confirm_unsubscribe: "Подтвърди прекратяване на абонамента"
-#    never_mind: "Няма значение, аз все още те обичам!"
-#    thank_you_months_prefix: "Благодарим за помоща ви през последните"
-#    thank_you_months_suffix: "месеци."
-#    thank_you: "Благодарим ви че подкрепихте CodeCombat."
-#    sorry_to_see_you_go: "Съжалиаваме че ни напускате! Моля да ни предложите какво бихме могли да подобрим."
+    never_mind: "Няма значение, аз все още те обичам!"
+    thank_you_months_prefix: "Благодарим за помоща ви през последните"
+    thank_you_months_suffix: "месеци."
+    thank_you: "Благодарим ви че подкрепихте CodeCombat."
+    sorry_to_see_you_go: "Съжалиаваме че ни напускате! Моля да ни предложите какво бихме могли да подобрим."
     unsubscribe_feedback_placeholder: "O, къде сбъркахме?"
-#    parent_button: "Попитай родител"
-#    parent_email_description: "Ще ги уведомим с email така че ще могат да ти купят абонамент за CodeCombat."
-#    parent_email_input_invalid: "Email адресът е невалиден."
-#    parent_email_input_label: "Родителски email адрес"
-#    parent_email_input_placeholder: "Въведи email на родител"
-#    parent_email_send: "Изпрати Email"
-#    parent_email_sent: "Email изпратен!"
-#    parent_email_title: "Въведете email на родител?"
+    parent_button: "Попитай родител"
+    parent_email_description: "Ще ги уведомим с email така че ще могат да ти купят абонамент за CodeCombat."
+    parent_email_input_invalid: "Email адресът е невалиден."
+    parent_email_input_label: "Родителски email адрес"
+    parent_email_input_placeholder: "Въведи email на родител"
+    parent_email_send: "Изпрати Email"
+    parent_email_sent: "Email изпратен!"
+    parent_email_title: "Въведете email на родител?"
     parents: "За Родители"
     parents_title: "Скъпи Родители: Вашето дете се учи да програмира. Ще му помогнете ли да продължи?"
     parents_blurb1: "Вашето дете изигра __nLevels__ нива и научи основи на програмирането. Развийте интереса у тях като им купите абонамент - така те ще могат да продължат с игрите."


Remove old title translations

Remove old title translations

--- app/locale/cs.coffee
+++ app/locale/cs.coffee
@@ -952,8 +952,6 @@
 #    robin_title: "Senior Product Manager"
 #    nolan_title: "Sales Manager"
 #    lisa_title: "Business Development Manager"
-#    liz_title: "Territory Manager"
-#    jane_title: "Customer Success Manager"
 #    david_title: "Marketing Lead"
 #    retrostyle_title: "Illustration"
     titles_csm: "Správce úspěšných zákazníků"


Finished translating the signup section.

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -233,12 +233,12 @@
     signup_switch: "Voulez-vous créer un compte ?"

   signup:
-#    complete_subscription: "Complete Subscription"
+    complete_subscription: "Abonnement complet"
     create_student_header: "Créer un compte étudiant"
     create_teacher_header: "Créer un compte enseignant"
     create_individual_header: "Créer un compte individuel"
     email_announcements: "Recevoir les annonces par e-mail" # {change}
-#    sign_in_to_continue: "Sign in or create an account to continue"
+    sign_in_to_continue: "Connectez-vous ou créez un compte pour continuer"
     teacher_email_announcements: "Tenez moi informé des nouvelles ressources, curriculum et cours pour les enseignants!"
     creating: "Création du compte en cours..."
     sign_up: "S'abonner"
@@ -303,15 +303,15 @@
     teacher_ready_to_create_class: "Vous êtes prêt à créer votre premiere classe!"
     teacher_students_can_start_now: "Vos étudiants vont pouvoir commencer à jouer leur premier cours, Initiation à l'Informatique, immédiatement."
     teacher_list_create_class: "Au prochain écran, vous pourrez créer une nouvelle classe."
-#    teacher_list_add_students: "Add students to the class by clicking the View Class link, then sending your students the Class Code or URL. You can also invite them via email if they have email addresses."
-#    teacher_list_resource_hub_1: "Check out the"
-#    teacher_list_resource_hub_2: "Course Guides"
-#    teacher_list_resource_hub_3: "for solutions to every level, and the"
-#    teacher_list_resource_hub_4: "Resource Hub"
-#    teacher_list_resource_hub_5: "for curriculum guides, activities, and more!"
-#    teacher_additional_questions: "That’s it! If you need additional help or have questions, reach out to __supportEmail__."
-#    dont_use_our_email_silly: "Don't put our email here! Put your parent's email."
-#    want_codecombat_in_school: "Want to play CodeCombat all the time?" # {change}
+    teacher_list_add_students: "Ajouter des élèves à la classe en cliquant sur le lien Afficher la classe, puis envoyer à vos élèves le code ou le lien URL du cours. Vous pouvez également les inviter par e-mail s'ils ont des adresses e-mail."
+    teacher_list_resource_hub_1: "Aller voir le"
+    teacher_list_resource_hub_2: "Guides de cours"
+    teacher_list_resource_hub_3: "Pour des solutions pour tous les niveaux et au"
+    teacher_list_resource_hub_4: "Centre de ressources"
+    teacher_list_resource_hub_5: "pour les guides de programme, les activités, et plus encore!"
+    teacher_additional_questions: "C'est tout! Si vous avez besoin d'aide supplémentaire ou si vous avez des questions, contactez __supportEmail__."
+    dont_use_our_email_silly: "Ne mettez pas votre email ici! Mettez celui de votre parent."
+    want_codecombat_in_school: "Avez-vous envie de jouer à CodeCombat tout le temps ?" # {change}

   recover:
     recover_account_title: "Récupérer son compte"


Update ja.coffee (#5004)

Update ja.coffee (#5004)

Completed all HOC with the download_PDF

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -2640,7 +2640,7 @@
     get_started: "始まる:"
     download_lesson: "レッスンプランをダウンロード:"
     lesson_copy: "このPDFはHOCのレッスンのやり方や大事なところをじっくり教えてくれます。さらに、クラスで話し合うことがかいてあります。"
-#    download_pdf: "Download PDF"
+    download_pdf: "PDFをダウンロード"
     ready_play: "準備できた？"
     play_activity: "アクティビティをあそぶ"
     about: "Codecombatについて:"


Add legend to pie chart

Add legend to pie chart

--- widgets/pie/pie.coffee
+++ widgets/pie/pie.coffee
@@ -1,62 +1,93 @@
 class Dashing.Pie extends Dashing.Widget
   @accessor 'value'
-
+
   onData: (data) ->
     $(@node).fadeOut().fadeIn()
     @render(data.value)
-
+
   render: (data) ->
     if !data
       data = @get("value")
     if !data
        return
     #console.log "FullPie new"
-# this is a fix because data binding seems otherwise not work
+# this is a fix because data binding seems otherwise not work
+
+
     $(@node).children(".title").text($(@node).attr("data-title"))
     $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
     $(@node).children(".updated-at").text(@get('updatedAtMessage'))
-
+
     width = 260 #width
     height = 260 #height
     radius = 130 #radius
-
-    color = d3.scale.ordinal()
-      .domain([1,10])
-      #.range( ['#222222','#555555','#777777','#999999','#bbbbbb','#dddddd','#ffffff'] )
-      .range( ['#222222','#333333','#444444','#555555','#666666','#777777','#888888','#999999','#aaaaaa'] )
-
+
+    colours = ['#D60303','#ff6700','#F9BC26','#67EF67','#0DBC37','#1dd3a7','#2254f4','#ef3aab','#b13198']
+
     $(@node).children("svg").remove();
-
+
     vis = d3.select(@node).append("svg:svg")
       .data([data])
         .attr("width", width)
         .attr("height", height)
       .append("svg:g")
-        .attr("transform", "translate(" + radius + "," + radius + ")")
-
+        .attr("transform", "translate(" + radius + "," + radius + ")")
+
     arc = d3.svg.arc().outerRadius(radius)
     pie = d3.layout.pie().value((d) -> d.value)
-
+
     arcs = vis.selectAll("g.slice")
       .data(pie)
-      .enter().append("svg:g").attr("class", "slice")
-
-    arcs.append("svg:path").attr("fill", (d, i) -> color i)
-      .attr("fill-opacity", 0.4).attr("d", arc)
-
+      .enter().append("svg:g").attr("class", "slice")
+
+    arcs.append("svg:path").attr("fill", (d, i) -> colours[i])
+      .attr("d", arc)
+
     sum=0
-    for val in data
+    for val in data
       sum += val.value
-
-    arcs.append("svg:text").attr("transform", (d, i) ->
-      procent_val = Math.round(data[i].value/sum * 100)
-      d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
-      d.outerRadius = radius
-      "translate(" + arc.centroid(d) + ")")
-      .attr('fill', "#fff")
-      .attr("text-anchor", "middle").text((d, i) -> data[i].label)
-      .append('svg:tspan')
-      .attr('x', 0)
-      .attr('dy', 15)
-      .attr('font-size', '70%')
-      .text((d,i) -> Math.round(data[i].value/sum * 100) + '%')+
+    legend = d3.select(@node).append("svg:svg")
+      .attr("height", 180)
+      .attr("width", width)
+      .attr("x", 0)
+      .append("g")
+      .attr("class", "legend")
+      .attr("x", 0)
+      .attr("y", height + 30)
+
+    legend.selectAll('g')
+      .data(data)
+      .enter()
+      .append('g')
+      .each((d,i) ->
+        g = d3.select(this);
+        g.append("rect")
+          .attr("x", 0)
+          .attr("y", 17 + (i * 22))
+          .attr("width", 12)
+          .attr("height", 12)
+          .attr("fill", colours[i] )
+
+        g.append("text")
+          .attr("x", 24)
+          .attr("y", 29 + (i * 22))
+          .attr("width", 130)
+          .attr("fill", '#fff')
+          .attr("font-size", "15px")
+          .text((d, i) -> d.label + " - " + Math.round(d.value/sum * 100) + '%')
+      )
+
+
+    # arcs.append("svg:text").attr("transform", (d, i) ->
+    #   procent_val = Math.round(data[i].value/sum * 100)
+    #   d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
+    #   d.outerRadius = radius
+    #   "translate(" + arc.centroid(d) + ")")
+    #   .attr('fill', "#fff")
+    #   #.attr("text-anchor", "middle").text((d, i) -> data[i].label if data[i].value > 0)
+    #   .append('svg:tspan')
+    #   .attr('x', 0)
+    #   .attr('dy', 15)
+    #   .attr('font-size', '70%')
+    #   .text((d,i) -> Math.round(data[i].value/sum * 100) + '%' if data[i].value > 0)


Update pl.coffee

Update pl.coffee

--- app/locale/pl.coffee
+++ app/locale/pl.coffee
@@ -737,7 +737,7 @@
 #    vega_character: "Vega Character"
     click_to_continue: "Kliknij, aby kontynuować"

-#  apis:
+  apis:
     methods: "Medoty"
     events: "Zdarzenia"
 #    handlers: "Handlers"


Fixes issues with the new app generator not properly downloading vendor files.

--- packages/tower-generator/server/generators/tower/app/appGenerator.coffee
+++ packages/tower-generator/server/generators/tower/app/appGenerator.coffee
@@ -130,12 +130,12 @@
           @get "https://raw.github.com/chriso/node-validator/master/validator.js", "validator.js"
           @get "https://raw.github.com/viatropos/node.inflection/master/lib/inflection.js", "inflection.js"
           @get "https://raw.github.com/josscrowcroft/accounting.js/9ff4a4022e5c08f028d652d2b0ba1d4b65588bde/accounting.js", "accounting.js"
-          @get "http://sinonjs.org/releases/sinon-1.3.1.js", "sinon.js"
+          @get "https://raw.github.com/edubkendo/sinon.js/2c6518de91c793344c7ead01a34556585f1b4d2c/sinon.js", "sinon.js"
           @get "https://raw.github.com/chaijs/chai/efc77596338556cca5fb4eade95c3838a743a186/chai.js", "chai.js"
-          @get "http://coffeekup.org/coffeekup.js", "coffeekup.js"
-          @get "http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.pre.js", "ember.js"
+          @get "https://raw.github.com/gradus/coffeecup/2d76a48c1292629dbe961088f756f00b034f0060/lib/coffeecup.js", "coffeekup.js"
+          @get "https://raw.github.com/emberjs/starter-kit/1e6ee1418c694206a49bd7b021fe5996e7fdb14c/js/libs/ember-1.0.pre.js", "ember.js"
           @get "https://raw.github.com/emberjs/starter-kit/1e6ee1418c694206a49bd7b021fe5996e7fdb14c/js/libs/handlebars-1.0.0.beta.6.js", "handlebars.js"
-          @get "http://twitter.github.com/bootstrap/assets/js/google-code-prettify/prettify.js", "prettify.js"
+          @get "https://raw.github.com/edubkendo/prettify.js/544c6cdf16594c85d3698dd0cbe07a9f232d02d9/prettify.js", "prettify.js"
           @get "https://raw.github.com/Marak/Faker.js/master/Faker.js", "faker.js"
           @get "https://raw.github.com/madrobby/keymaster/c61b3fef9767d4899a1732b089ca70512c9d4261/keymaster.js", "keymaster.js"
           @get "https://raw.github.com/mrdoob/stats.js/master/src/Stats.js", "stats.js"


Fix regression

Fix regression

--- app/models/Entity.coffee
+++ app/models/Entity.coffee
@@ -98,7 +98,7 @@
     numYTiles = @map.get 'numYTiles'

     @position.y++
-    @position.y = numYTiles if position.y > numYTiles
+    @position.y = numYTiles if @position.y > numYTiles

     # moveDirection and moveState
     @updateViewAndMove(2)
@@ -113,7 +113,7 @@
     numXTiles = @map.get 'numXTiles'

     @position.x++
-    @position.x = numXTiles if position.x > numXTiles
+    @position.x = numXTiles if @position.x > numXTiles

     # moveDirection and moveState
     @updateViewAndMove(1)
@@ -124,7 +124,7 @@
   ###
   moveLeft: =>
     @position.x--
-    @position.x = 0 if position.x < 0
+    @position.x = 0 if @position.x < 0

     # moveDirection and moveState
     @updateViewAndMove(3)


Update admin page route handler

Update admin page route handler

--- app/pages/admin.cjsx
+++ app/pages/admin.cjsx
@@ -2,7 +2,7 @@
 counterpart = require 'counterpart'
 Translate = require 'react-translate-component'
 ChangeListener = require '../components/change-listener'
-{Link} = require 'react-router'
+{IndexLink, Link} = require 'react-router'

 counterpart.registerTranslations 'en',
   userAdminPage:
@@ -21,20 +21,22 @@

-              <Link to="/admin"
+              <IndexLink to="/admin"
                 type="button"
-                className="secret-button admin-button" >
+                className="secret-button admin-button"
+                activeClassName="active">
                 <Translate content="userAdminPage.nav.createAdmin" />
-              </Link>
+              </IndexLink>
               <Link to="/admin/project_status"
                 type="button"
-                className="secret-button admin-button" >
+                className="secret-button admin-button"
+                activeClassName="active">
                 <Translate content="userAdminPage.nav.projectStatus" />
               </Link>

-            React.cloneElement @props.children, {user: @props.user}
+            {React.cloneElement @props.children, @props}

@@ -48,7 +50,7 @@
       <ChangeListener target={@props.user}>{ =>
         if @props.user?
           if @props.user.admin?
-            <AdminPage user={@props.user}/>
+            <AdminPage {...@props} />
           else

You're not an administrator


cleanup

cleanup

--- assets/app.coffee
+++ assets/app.coffee
@@ -28,7 +28,9 @@
     $("input[type=search]").live 'keydown', (e)->
       # tab key pressed (and wasn't just previously pressed)
       if e.keyCode == 9 and app.previous_keyCode != 9
+        # stop from tabbing out of input box (on first tab event in-a-row only)
         e.preventDefault()
+        # set search input box to first approximate result
         $(@).val(($('.results .approximate li:first-child').text()) or '')
     # search history navigation and enter-key handling
     $("input[type=search]").live 'keyup click', (e)->
@@ -37,13 +39,19 @@
         when 13 then app.commit($(@).val())
         # up key pressed, navigate to previous element
         when 38
+          # subtract one from app.history
           app.history_pos = Math.max(app.history_pos-1, 0)
+          # set value of search input box
           $(@).val(app.history[app.history_pos])
+          # display history list
           app.display(app.history[app.history_pos])
         # down key pressed, navigate to next element
         when 40
+          # subtract one from app.history
           app.history_pos = Math.min(app.history_pos+1, app.history.length-1)
+          # set value of search input box
           $(@).val(app.history[app.history_pos])
+          # display history list
           app.display(app.history[app.history_pos])
       # preview display
       app.preview($(@).val())


FIX: Shorten tutorial description using 3 letter day name

--- src/app/common/services/unit-service.coffee
+++ src/app/common/services/unit-service.coffee
@@ -239,7 +239,7 @@
   #
   unitService.tutorialDescription = (tutorial) ->
     timeDesc = $filter('date')(tutorial.meeting_time, 'shortTime')
-    "#{tutorial.meeting_day}s at #{timeDesc} by #{tutorial.tutor_name} in #{tutorial.meeting_location}"
+    "#{tutorial.meeting_day.slice(0,3)} at #{timeDesc} by #{tutorial.tutor_name} in #{tutorial.meeting_location}"

   #
   # Adds additional unit-related functionality to groups


Fixed typo

Fixed typo


overwrite enter

overwrite enter

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -16,7 +16,6 @@
     @hotkeys = {}
     this._initListeners()
     this._initHotkeys()
-    this._initDeletes()

   addHotkey: (hotkeys, callback) ->
     hotkeys = [hotkeys] unless Array.isArray(hotkeys)
@@ -42,6 +41,19 @@
       @quill.formatText(range, format, value, Quill.sources.USER)
     toolbar = @quill.getModule('toolbar')
     toolbar.setActive(format, value) if toolbar?
+
+  _initEnter: ->
+    this.addHotkey(dom.KEYS.ENTER, (range, hotkey) =>
+      return true unless range?
+      [line, offset] = @quill.editor.doc.findLineAt(range.start)
+      [leaf, offset] = line.findLeafAt(offset)
+      delta = new Delta().retain(range.start).insert('\n', line.formats).delete(range.end - range.start)
+      @quill.updateContents(delta, Quill.sources.USER)
+      _.each(leaf.formats, (format, key) =>
+        @quill.prepareFormat(key, format)
+      )
+      return false
+    )

   _initDeletes: ->
     this.addHotkey([dom.KEYS.DELETE, dom.KEYS.BACKSPACE], (range, hotkey) =>
@@ -76,6 +88,8 @@
         return false
       )
     )
+    this._initDeletes()
+    this._initEnter()

   _initListeners: ->
     dom(@quill.root).on('keydown', (event) =>


Specify interface for hostapd.disable and udhcpd.disable

--- src/app.coffee
+++ src/app.coffee
@@ -123,9 +123,9 @@

 closeHotspot = (wifi) ->
 	console.log("Closing hotspot")
-	udhcpd.disableAsync()
-	.then ->
-		hostapd.disableAsync()
+	udhcpd.disableAsync('wlan0')
+	.then ->
+		hostapd.disableAsync('wlan0')
 	.then ->
 		loadModuleForStation(wifi)


improvement with path rows calculation

improvement with path rows calculation

--- src/viz/types/paths.coffee
+++ src/viz/types/paths.coffee
@@ -35,27 +35,32 @@

       lastHop = nextHop

+  rows = 0
   for pathInt, path of paths
     if pathInt isnt "all"
+      newPath = 0
       for id, i in path
         if i not in [0, path.length-1] and pathLookup[id] is parseFloat(pathInt)
+          newPath = 1
           prev = path[i-1]
           next = path[i+1]
           prevIndex = null
           nextIndex = null
-          for col, colIndex in paths["all"]
+          for col, colIndex in paths.all
             if prev in col
               prevIndex = colIndex
             if next in col
               nextIndex = colIndex
           if prevIndex isnt null and nextIndex is null
-            paths["all"].splice prevIndex + 1, 0, [id]
+            if prevIndex + 1 is paths.all.length - 1
+              paths.all.splice prevIndex + 1, 0, [id]
+            else
+              paths.all[prevIndex + 1].push id
           else if nextIndex - prevIndex is 1
-            paths["all"].splice nextIndex, 0, [id]
+            paths.all.splice nextIndex, 0, [id]
           else if nextIndex - prevIndex > 1
-            paths["all"][nextIndex - 1].push id
-
-  rows = viz.paths.length
+            paths.all[nextIndex - 1].push id
+      rows += newPath

   rowHeight = Math.floor vars.height.viz / rows

@@ -198,7 +203,6 @@

   returnData

-viz.modes        = ["right", "left", "center"]
 viz.nesting      = false
 viz.requirements = [
   (vars) ->


update question if it is a success

update question if it is a success

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -35,6 +35,7 @@
         attempt = $("#input_answer").val()
         if attempt.toLowerCase() == @current.toLowerCase()
           alert("SUCCESS!")
+          update_question(@data)
         else
           alert("FAIL!")
     )


Fixed typo

Fixed typo

--- sample.coffee
+++ sample.coffee
@@ -404,7 +404,7 @@
             $ne: 'd41d8cd98f00b204e9800998ecf8427e'  # md5 sum for zero length file
          'metadata._Resumable':
             $exists: false
-         'metadata.thumbnailOf':
+         'metadata.thumbOf':
             $exists: false
       ).observe(
          added: addedFileJob


:art:

:art:


explicit order for new settings and cleaned description of markdown and links


Handle different kinds of position marker graphics.

--- src/map.coffee
+++ src/map.coffee
@@ -247,13 +247,15 @@
             new ctor count, ICON_SIZE, colors, service_collection.first().id

         create_popup: (offset) ->
-            new widgets.LeftAlignedPopup
+            opts =
                 closeButton: false
                 autoPan: false
                 zoomAnimation: false
                 minWidth: 500
                 className: 'unit'
-                offset: offset
+            if offset? then opts.offset = offset
+            new widgets.LeftAlignedPopup opts
+
         create_marker: (unit) ->
             location = unit.get 'location'
             coords = location.coordinates


Ignore javascript href links

Ignore javascript href links


Minor coffeescript syntax tweak

Minor coffeescript syntax tweak

--- js-library/app/js/jail_iframe/classes/control_iframe.coffee
+++ js-library/app/js/jail_iframe/classes/control_iframe.coffee
@@ -1,7 +1,7 @@

 class ControlIframe
   constructor: ->
-    @el = document.createElement('iframe');
+    @el = document.createElement('iframe')
     #need to append to outer document before we can access frame document.
     FactlinkJailRoot.$factlinkCoreContainer.append(@el)
     @$el = $(@el)


server: added healthcheck

server: added healthcheck

--- server.coffee
+++ server.coffee
@@ -3,6 +3,10 @@
 # Static assets
 express = require 'express'
 app = express()
+
+app.use '/healthcheck', (req, res) ->
+  res.json
+    healthy: true

 if process.env.NODE_ENV is 'production'
   app.use express['static'](__dirname + '/dist')


do not make app crash on uncaught type error

--- server.coffee
+++ server.coffee
@@ -10,9 +10,11 @@
 process.on 'uncaughtException', (err) ->
     console.log err
     console.log err.stack
-    setTimeout ->
-        process.exit 1
-    , 1000
+
+    if typeof(err) isnt TypeError
+        setTimeout ->
+            process.exit 1
+        , 1000

 module.exports = start = (options, cb) ->
     options.name = 'cozy-photos'


Apparently didn't completely remove Fireworks

--- server.coffee
+++ server.coffee
@@ -9,8 +9,6 @@
 MatchMaker = require './lib/matchMaker'

 HeartsBuilder = require './lib/hearts/builder'
-
-FireworksBuilder = require './lib/fireworks/builder'

 createHttp = ->
   app = express()
@@ -24,7 +22,11 @@
       res.locals.pretty = true
       next()

-  app.get '/', (req, res) -> res.send("Hearts
Fireworks")
+  app.get '/', (req, res) ->
+    res.send """
+      Hearts
+

+    """
   return app

 createIoGameServer = (httpServer, entrance) ->
@@ -106,15 +108,13 @@
   logger.info "TCP Server listening on", tcpServer.address()

   heartsArena = buildArena(HeartsBuilder, options)
-  fireworksArena = buildArena(FireworksBuilder, options)

-  ["hearts", "fireworks"].forEach (name) ->
+  ["hearts"].forEach (name) ->
     app.use "/game/#{name}/play", (req, res) ->
       res.render "#{name}/play"

   entrance = new Entrance
     hearts: heartsArena
-    fireworks: fireworksArena
   createIoGameServer(httpServer, entrance)
   createTcpGameServer(tcpServer, entrance, options)


Use environement PORT

Use environement PORT

--- server.coffee
+++ server.coffee
@@ -23,8 +23,8 @@
 api = require './api'
 api app
 # listen
-app.listen net.http.port
-logger.info "HTTP REST API listening on port #{net.http.port}".green
+app.listen process.env.PORT
+logger.info "HTTP REST API listening on port #{process.env.PORT}".green

 # SHOW INDEX
 # -------------------


updating url scheme to be more strict

updating url scheme to be more strict

--- server.coffee
+++ server.coffee
@@ -27,7 +27,7 @@
 				cmd = "phantomjs crawler.coffee http://"+
 						headers.host +
 						request.url +
-						"?no-transition"
+						"?crawler"
 				exec cmd, (error, stdout, stderr)->
 					response.writeHead 200, {"Content-Type": "text/html"}
 					response.write stdout


Debug logging is not visible by default.

--- server.coffee
+++ server.coffee
@@ -52,7 +52,7 @@
       f id, args... if id[0] in PREFIX
     catch e
       Log.error "PeerDB exception: #{ e }: #{ util.inspect args, depth: 10 }"
-      Log.debug e.stack
+      Log.error e.stack

 extractValue = (obj, path) ->
   while path.length


compress all the things

compress all the things

--- server.coffee
+++ server.coffee
@@ -16,6 +16,7 @@
   app.set "views", path.join(basedir, "views")
   app.set "view engine", "jade"
   app.use express.logger() unless process.env.COVERAGE
+  app.use express.compress()
   app.use express.cookieParser()
   app.use express.bodyParser()
   app.use express.methodOverride()


app name and port

app name and port

--- server.coffee
+++ server.coffee
@@ -18,8 +18,8 @@
 # 02110-1301 USA
 #

-PORT    = 3030
-APPNAME = "myapp"
+PORT    = 3040
+APPNAME = "crash-reports"

 require.paths.unshift './node_modules'
 require.paths.push 'server'


fixed build

fixed build

--- gulp/tasks/browserify.coffee
+++ gulp/tasks/browserify.coffee
@@ -63,7 +63,7 @@
     .bundle()
     .on('error', $.util.log )
     .pipe(source("#{fname}.js"))
-    .pipe( $.streamify($.uglifyjs(outSourceMap: true)))
+    .pipe( $.streamify($.uglify()))
     .pipe gulp.dest(compiledPath + "/scripts/")
   )


Use Menlo in Atom

Use Menlo in Atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,41 +1,35 @@
-{
-  "editor": {
-    "fontSize": 15,
-    "showIndentGuide": true
-  },
-  "core": {
-    "excludeVcsIgnoredPaths": true,
-    "ignoredNames": [
-      ".bundle",
-      ".git",
-      "log",
-      "repositories",
-      "tmp",
-      "vendor"
-    ],
-    "themes": [
-      "unity-ui",
-      "glacier-syntax"
-    ],
-    "hideGitIgnoredFiles": true,
-    "disabledPackages": [
-      "background-tips",
-      "exception-reporting",
-      "metrics"
-    ]
-  },
-  "spell-check": {
-    "grammars": [
-      "text.plain",
-      "source.gfm",
-      "text.git-commit"
-    ]
-  },
-  "welcome": {
-    "showOnStartup": false
-  },
-  "release-notes": {
-    "viewedVersion": "0.85.0"
-  },
-  "tree-view": {}
-}
+'editor':
+  'fontSize': 15
+  'showIndentGuide': true
+  'fontFamily': 'Menlo'
+'core':
+  'excludeVcsIgnoredPaths': true
+  'ignoredNames': [
+    '.bundle'
+    '.git'
+    'log'
+    'repositories'
+    'tmp'
+    'vendor'
+  ]
+  'themes': [
+    'unity-ui'
+    'glacier-syntax'
+  ]
+  'hideGitIgnoredFiles': true
+  'disabledPackages': [
+    'background-tips'
+    'exception-reporting'
+    'metrics'
+  ]
+'spell-check':
+  'grammars': [
+    'text.plain'
+    'source.gfm'
+    'text.git-commit'
+  ]
+'welcome':
+  'showOnStartup': false
+'release-notes':
+  'viewedVersion': '0.85.0'
+'tree-view': {}


postgres: add update set option

postgres: add update set option

--- src/smc-hub/postgres.coffee
+++ src/smc-hub/postgres.coffee
@@ -171,6 +171,21 @@
                     fields.push(field)
                     values.push("$#{push_param(param)}")
             opts.query += " (#{fields.join(', ')}) VALUES (#{values.join(', ')}) "
+
+        if opts.set?
+            fields = []
+            values = []
+            for field, param of opts.values
+                if field.indexOf('::') != -1
+                    [field, type] = field.split('::')
+                    type = type.trim()
+                    v.push("#{field.trim()}=$#{push_param(param, type)}::#{type}")
+                    continue
+                else
+                    fields.push(field)
+                    values.push("$#{push_param(param)}")
+
+            opts.query += " SET  (#{fields.join(', ')}) VALUES (#{values.join(', ')}) "

         if opts.conflict?
             if not opts.values?
@@ -844,7 +859,16 @@
                         opts.cb(undefined, (x.action for x in result.rows))

     account_creation_actions_success: (opts) =>
-        throw Error("NotImplementedError")
+        opts = defaults opts,
+            account_id : required
+            cb         : required
+        @_query
+            query : 'UPDATE accounts'
+            set   :
+                "creation_actions_done::BOOLEAN" : true
+            where :
+                "account_id = $::UUID" : opts.account_id
+            cb     : opts.cb

     do_account_creation_actions: (opts) =>
         throw Error("NotImplementedError")