Rewrite filter Func, fix #38

Rewrite filter Func, fix #38

--- views/akashic-records-log.cjsx
+++ views/akashic-records-log.cjsx
@@ -46,48 +46,38 @@
   configChecked: [true, true, false, false]
   rowChooseChecked: [true, true, true, true, true, true, true, true, true, true, true, true,
                       true, true]
-  _testFilter: (log, keyWords)->
+
+  _filterReg: (data, index, reg)->
+    data.filter (row)=>
+      if index is 0
+        reg.test dateToString(new Date(row[0]))
+      else
+        reg.test "#{row[index]}"
+  _filterString: (data, index, keyword)->
+    data.filter (row)=>
+      if index is 0
+        dateToString(new Date(row[0])).toLowerCase().trim().indexOf(keyword) >= 0
+      else
+        "#{row[index]}".toLowerCase().trim().indexOf(keyword) >= 0
+  _filter: (rawData, keyWords)->
     {rowChooseChecked} = @state
-    regFlag = []
-    filterKeys = []
-    for item, index in keyWords
-      regFlag[index] =false
-      filterKeys[index] = item
-      if item isnt ''
-        res = item.match /^\/(.+)\/([gim]*)$/
+    retData = rawData
+    for item, index in @props.tableTab
+      continue if index is 0
+      if rowChooseChecked[index] and keyWords[index] isnt ''
+        regFlag = false
+        res = keyWords[index].match /^\/(.+)\/([gim]*)$/
         if res?
           try
             reg = new RegExp res[1], res[2]
-            regFlag[index] = true
+            regFlag = true
           catch e
-            regFlag[index] = false
-            # ...
-          finally
-            if regFlag[index]
-              filterKeys[index] = reg
-    for item, index in log
-      if rowChooseChecked[index+1]
-        if keyWords[index+1] isnt ''
-          if index is 0
-            testText = dateToString(new Date(item)).toLowerCase().trim()
-          else
-            testText = "#{item}".toLowerCase().trim()
-          if regFlag[index+1]
-            match = filterKeys[index+1].test testText
-          else
-            match = testText.indexOf(filterKeys[index+1].toLowerCase().trim()) >= 0
-          if not match
-            return false
-    return true
-  _filter: (rawData, keyWords)->
-    enableFilter = false
-    for item, index in @props.tableTab
-      continue if index is 0
-      enableFilter = true if keyWords[index] isnt ''
-    if enableFilter
-      rawData.filter (row)=>
-        @_testFilter row, keyWords
-    else rawData
+            console.log "Failed to resolve RegExp #{keyWords[index]}." if process.env.DEBUG?
+        if regFlag
+          retData = @_filterReg retData, index - 1, reg
+        else
+          retData = @_filterString retData, index - 1, keyWords[index].toLowerCase().trim()
+    retData
   refreshDataShow: (data, filterKeys, activePage, showAmount)->
     dataAfterFilter = @_filter data, filterKeys
     dataAfterFilterLength = dataAfterFilter.length


handling errors in localforage

handling errors in localforage

--- src/loader/loader.coffee
+++ src/loader/loader.coffee
@@ -295,7 +295,7 @@
     onUpdateFailed: (event, error)->
     onUpdateCompleted: (manifest) -> return true
     onModuleBeginDownload: (module) ->
-    onModuleDownloadFailed: (event, module) ->
+    onModuleDownloadFailed: (event, module, error) ->
     onModuleDownloadProgress: (event, module) ->
     onTotalDownloadProgress: (progress) ->
     onModuleDownloaded: (module) ->
@@ -465,7 +465,7 @@
                 return
             @set_content @make_key(module), module_source, (err, content) =>
                 if err?
-                    @emit("ModuleDownloadFailed", new DBError(module.url, err), module)
+                    @emit("ModuleDownloadFailed", null, module, new DBError(module.url, err))
                     return
                 module.source = module_source
                 module.loaded = module.size
@@ -506,6 +506,7 @@
             return unless key? # wierd error
             return unless key.indexOf(@prefix) is 0
             return if key in useful
-            @del_content(key)
+            @del_content key, (error) =>
+                @logger.error(error)
             return
         return


#75: notes

#75: notes

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -80,7 +80,11 @@

       unless is_nodejs?
         scrolls = $(window).asEventStream 'scroll'
-        scroll_to = notebook.cell_run.flatMapLatest (input_cell) -> input_cell.output_cell.done.delay(0).takeUntil scrolls
+        # FIXME if anything else subscribes to output_cell.done, scroll_to never gets any events
+        scroll_to = notebook.cell_run.flatMapLatest (input_cell) ->
+          # TODO without the delay, this can happen before there is a dom node.
+          # not sure if the ordering now is guaranteed
+          input_cell.output_cell.done.delay(0).takeUntil scrolls
         scroll_to.onValue (output_cell) ->
           # FIXME
           $('html, body').scrollTop $(output_cell.dom_node).offset().top
@@ -270,7 +274,6 @@
       output_cell.done = no_longer_pending.take(1).map -> output_cell
       context.run_in_context run_context, fn

-      # FIXME since render isn't called, there's never a "changes" event, so scrolling never happens
       # TODO don't access _lead_render
       output_cell.component_model.set run_context.component_list._lead_render


fixed 'humanize header'

fixed 'humanize header'

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -186,19 +186,25 @@
   ###

   Handlebars.registerHelper 'human_header', (abbr) ->
-    switch abbr
+    abbArr = /(\w)(\d)?/.exec(abbr)[1..]
+    char = abbArr[0]
+
+    abbArr[0] = switch char
       when "C"
-        "Chorus "
+        "Chorus"
       when "V"
-        "Verse "
+        "Verse"
       when "B"
-        "Bridge "
+        "Bridge"
       when "T"
-        "Tag "
+        "Tag"
       when "P"
-        "Pre-Chorus "
+        "Pre-Chorus"
       else
-        abbr
+        char
+
+    abbArr.pop() unless abbArr[1]? # remove num if empty
+    abbArr.join " "

   Handlebars.registerHelper 'transpose', (chord) ->
     chord


pricing: fix router usage

pricing: fix router usage

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -140,12 +140,15 @@

   planSelected: (options) ->

-    return window.location.replace '/Login'  unless KD.isLoggedIn()
-    return @loadPlan @bound 'planSelected'   unless @state.currentPlan?
+    return KD.singletons
+      .router
+      .handleRoute '/Login'  unless KD.isLoggedIn()
+
+    return @loadPlan @bound 'planSelected'  unless @state.currentPlan?

     @setState options

-    @workflowController = new PaymentWorkflow { @state, delegate: this}
+    @workflowController = new PaymentWorkflow { @state, delegate: this }

     @workflowController.on 'PaymentWorkflowFinishedSuccessfully', (state) =>

@@ -157,9 +160,9 @@

       @state.currentPlan = state.planTitle

-      { router } = KD.singletons
-
-      router.handleRoute '/'
+      KD.singletons
+        .router
+        .handleRoute '/'

   setState: (obj) -> @state = KD.utils.extend @state, obj


removed log

removed log

--- client/collection_view.coffee
+++ client/collection_view.coffee
@@ -66,7 +66,6 @@
       field_name = $this.data('field')
       update_dict = {}
       update_dict[field_name] = updated_val
-      console.log update_dict
       get_collection().update(id, $set: update_dict)

   'change .column_filter': (event...) ->


Move error handling to after mounting sub-apps

--- client/lib/setup/index.coffee
+++ client/lib/setup/index.coffee
@@ -27,17 +27,17 @@

 module.exports = (app) ->

+  # Configure Sentry
+  if SENTRY_PRIVATE_DSN
+    RavenServer.config(SENTRY_PRIVATE_DSN).install()
+    app.use RavenServer.requestHandler()
+
   # Override Backbone to use server-side sync
   Backbone.sync = require 'backbone-super-sync'

   # Route to ping for system up
   app.get '/system/up', (req, res) ->
     res.send 200, { nodejs: true }
-
-  # Error Reporting
-  if SENTRY_PRIVATE_DSN
-    RavenServer.config(SENTRY_PRIVATE_DSN).install()
-    app.use RavenServer.requestHandler()

   # Mount generic middleware & run setup modules
   if 'production' is NODE_ENV or 'staging' is NODE_ENV
@@ -54,10 +54,6 @@
   app.use bucketAssets()
   setupAuth app
   app.use locals
-
-  if SENTRY_PRIVATE_DSN
-    app.use RavenServer.errorHandler()
-
   app.use helpers
   app.use ua
   app.use sameOrigin
@@ -77,4 +73,7 @@
   app.use express.static(path.resolve __dirname, '../../public')

   # Error handler
+  if SENTRY_PRIVATE_DSN
+    app.use RavenServer.errorHandler()
+
   require('../../components/error/server') app


Just need to support index.js as default js file if none defined in package.json

Node supports index.js and index.node, so we'll just do index.js

--- tests/main.coffee
+++ tests/main.coffee
@@ -165,10 +165,6 @@
 			test.done()

 	noPackageJsonWithIndexJs : (test)->
-		test.expect 1
-		test.done()
-
-	noPackageJsonWithMainJs : (test)->
 		test.expect 1
 		test.done()


allow local data in node search

allow local data in node search


Remove empty directories when building

Remove empty directories when building

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -17,11 +17,13 @@
   locals.contents = contents # all pages have access to the content-tree

   renderPlugin = (content, callback) ->
+    ### render *content* plugin, calls *callback* with true if a file is written;
+        otherwise false. ###
     destination = path.join location, content.filename
     logger.verbose "writing content #{ content.url } to #{ destination }"
     content.render locals, contents, templates, (error, result) ->
       if error
-        callback error
+        callback error, false
       else if result instanceof fs.ReadStream or result instanceof Buffer
         # TODO: use in-memory readstreams instead of buffers if possible
         writeStream = fs.createWriteStream destination
@@ -30,29 +32,41 @@
         else
           writeStream.write result
           writeStream.end()
-        callback()
+        callback null, true
       else
         logger.verbose "skipping #{ content.url }"
-        callback()
+        callback null, false

   renderTree = (tree, callback) ->
     logger.verbose "rendering: #{ tree.filename }"
+    directory = path.join location, tree.filename
     async.waterfall [
       (callback) ->
-        fs.mkdir path.join(location, tree.filename), (error) ->
+        fs.mkdir directory, (error) ->
           if not error or error.code == 'EEXIST'
             callback()
           else
             callback error
       (callback) ->
-        async.forEach Object.keys(tree), (key, callback) ->
+        async.map Object.keys(tree), (key, callback) ->
           item = tree[key]
           if item instanceof ContentTree
             renderTree item, callback
           else
             renderPlugin item, callback
         , callback
-    ], callback
+      (written, callback) ->
+        # remove directory if no files where written to it
+        detector = (didWrite, callback) ->
+          callback !didWrite
+        async.every written, detector, (isEmpty) ->
+          if isEmpty
+            logger.verbose "removing empty directory #{ directory }"
+            fs.rmdir directory, callback
+          else
+            callback()
+    ], (error) ->
+      callback error, true

   renderTree contents, callback


[grunt] fix how `PAGE_DEFAULTS` injected in matter data to ensure that it is never overrides anything, but only completes missing values

--- tasks/data.coffee
+++ tasks/data.coffee
@@ -11,7 +11,7 @@
   Extract data from specified files with Gray Matter
   ###

-  pageDefaults = @config.process @config('data')().PAGE_DEFAULTS
+  { PAGE_DEFAULTS } = @config.process @config('data')()

   @config 'grayMatter',
     build:
@@ -25,14 +25,14 @@
           [breadcrumb..., prop] = path
           url = urljoin('/', breadcrumb...)

-          return merge {
+          return merge {}, PAGE_DEFAULTS, {
             slug:       path.slice(-2)[0]
             url:        if url == '/index' then '/' else url
             breadcrumb: breadcrumb
             depth:      breadcrumb.length
             dirname:    basename(dirname(src))
             basename:   basename(src, extname(src))
-          }, pageDefaults, data
+          }, data

       files: [
         src: ['<%= path.source.templates %>/{,**/}*.{nj,html}', '!<%= path.source.templates %>/{,**/}_*.{nj,html}']


Removed unnecessary check datesAreEqualToMinute

origin #39

--- src/ng-quick-date.coffee
+++ src/ng-quick-date.coffee
@@ -281,8 +281,7 @@
           if !tmpDateAndTime
             throw 'Invalid Time'
           tmpDate = tmpDateAndTime
-        unless datesAreEqualToMinute(ngModelCtrl.$viewValue, tmpDate)
-          if !scope.selectDate(tmpDate, false)
+        if !scope.selectDate(tmpDate, false)
             throw 'Invalid Date'

         if closeCalendar


helps datawheel/datausa-site#71

helps datawheel/datausa-site#71

--- src/number/format.coffee
+++ src/number/format.coffee
@@ -62,6 +62,9 @@
     else
       ret = format.numberFormat(".3g") number

+  if ret.length > 2 and "" + ret.indexOf(".0") is ret.length - 2
+    ret = ret.slice(0, ret.length - 2)
+
   if labels and key and "format" of vars and key of vars.format.affixes.value
     affixes = vars.format.affixes.value[key]
     affixes[0] + ret + affixes[1]


handle membership changes

handle membership changes

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -152,6 +152,15 @@
     ipc.send 'renameconversation', conv_id, convsettings.name if needsRename

 handle 'conversation_rename', (c) -> conv.rename c, c.conversation_rename.new_name
+handle 'membership_change', (e) ->
+    conv_id = e.conversation_id.id
+    ids = (id.chat_id or id.gaia_id for id in e.membership_change.participant_ids)
+    if e.membership_change.type == 'LEAVE'
+        if entity.self.id in ids
+            return conv.deleteConv conv_id
+        return conv.removeParticipants conv_id, ids
+    ipc.send 'getentity', ids, {add_to_conv: conv_id}
+
 handle 'createconversationdone', (c) ->
     convsettings.reset()
     conv.add c


adds comment on implementation choice

adds comment on implementation choice

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -192,6 +192,9 @@
             notr "Ignoring file of type #{ext}"
             return
         # store image in preview-container and open it
+        #  I think it is better to embed than reference path as user should
+        #   see exactly what he is sending. (using the path would require
+        #   polling)
         element.src = nativeImage.createFromPath(file.path).toDataURL()
         document.querySelector('#preview-container').classList.toggle('open')
     else


Limit loaded posts to 250

Limit loaded posts to 250

--- resources/assets/coffee/_classes/forum.coffee
+++ resources/assets/coffee/_classes/forum.coffee
@@ -35,6 +35,8 @@
     @posts = document.getElementsByClassName('js-forum-post')
     @loadMoreLinks = document.getElementsByClassName('js-forum-posts-show-more')

+    @maxPosts = 250
+
     $(document).on 'turbolinks:load osu:page:change', @boot

     $(window).on 'throttled-scroll', @refreshCounter
@@ -239,26 +241,33 @@

     $.get(window.canonicalUrl, options)
     .done (data) =>
+      scrollReference = $refPost[0]
+      scrollReferenceTop = scrollReference.getBoundingClientRect().top
+
       if mode == 'previous'
-        scrollReference = $refPost[0]
-        scrollReferenceTop = scrollReference.getBoundingClientRect().top
-
         $linkDiv.after data

-        # Restore scroll position after prepending the page.
-        # Called after refreshLoadMoreLinks to allow header changes
-        # to be included in calculation.
-        restoreScrollPosition = =>
-          x = window.pageXOffset
-          currentScrollReferenceTop = scrollReference.getBoundingClientRect().top
-          currentDocumentScrollTop = window.pageYOffset
-          targetDocumentScrollTop = currentDocumentScrollTop + currentScrollReferenceTop - scrollReferenceTop
-          window.scrollTo x, targetDocumentScrollTop
+        if @posts.length > @maxPosts
+          parent = @posts[0].parentNode
+          parent.removeChild(post) for post in _.slice(@posts, @maxPosts)
       else
         $linkDiv.before data

+        toRemove = @posts.length - @maxPosts
+        if toRemove > 0
+          parent = @posts[0].parentNode
+          parent.removeChild(post) for post in _.slice(@posts, 0, toRemove)
+
       @refreshLoadMoreLinks()
-      restoreScrollPosition?()
+
+      # Restore scroll position after adding/removing posts.
+      # Called after refreshLoadMoreLinks to allow header changes
+      # to be included in calculation.
+      x = window.pageXOffset
+      currentScrollReferenceTop = scrollReference.getBoundingClientRect().top
+      currentDocumentScrollTop = window.pageYOffset
+      targetDocumentScrollTop = currentDocumentScrollTop + currentScrollReferenceTop - scrollReferenceTop
+      window.scrollTo x, targetDocumentScrollTop

       osu.pageChange()
       $link.attr 'data-failed', '0'


closes datawheel/datausa-site#12

closes datawheel/datausa-site#12

--- src/viz/types/bar.coffee
+++ src/viz/types/bar.coffee
@@ -1,3 +1,4 @@
+buckets    = require "../../util/buckets.coffee"
 fetchValue = require "../../core/fetch/value.coffee"
 graph      = require "./helpers/graph/draw.coffee"
 nest       = require "./helpers/graph/nest.coffee"
@@ -53,7 +54,7 @@
       maxSize /= divisions
       offset   = space/2 - maxSize/2 - padding

-      x = d3.scale.linear()
+      x = d3.scale.ordinal()
         .domain [0, divisions-1]
         .range [-offset, offset]

@@ -64,7 +65,7 @@
   zero = 0

   maxBars = d3.max nested, (b) -> b.values.length
-  for p in nested
+  for p, i in nested

     if vars.axes.stacked
       bars = 1
@@ -72,6 +73,10 @@
     else if vars[discrete].persist.position.value
       bars = divisions
       newSize = maxSize
+      unless i
+        ids = p.values.map (d) -> fetchValue(vars, d, vars.id.value)
+        x.domain(ids).range(buckets(x.range(), ids.length))
+
     else
       bars = p.values.length
       if vars[discrete].persist.size.value
@@ -85,7 +90,13 @@

     for d, i in p.values

-      mod = if vars.axes.stacked then 0 else x(i % bars)
+      if vars.axes.stacked
+        mod = 0
+      else if vars[discrete].persist.position.value
+        mod = x fetchValue(vars, d, vars.id.value)
+      else
+        mod = x(i % bars)
+
       oppMethod = vars[opposite]

       if vars.axes.stacked


Fixed client side bug with registration

Fixed client side bug with registration

--- web/coffee/registration.coffee
+++ web/coffee/registration.coffee
@@ -3,7 +3,7 @@

   registrationData = $("#user-registration-form").serializeObject()
   registrationData["create-new-team"] = $("#new-team").hasClass("active")
-  registrationData["create-new-teacher"] = $("new-teacher").hasClass("active")
+  registrationData["create-new-teacher"] = $("#new-teacher").hasClass("active")

   $.post "/api/user/create", registrationData
   .done (data) ->


Cloud hints

Cloud hints

--- web/scripts/gameManager.coffee
+++ web/scripts/gameManager.coffee
@@ -151,6 +151,7 @@
     addEventListeners: ->
         jQuery('#compileAndRun').click @runStudentCode
         jQuery('#resetState').click @reset
+        jQuery('#help').click @helpTips
         @codeEditor.onStudentCodeChangeListener @startGame.bind @, false
         @codeEditor.onCommandValidation @commandsValid
         return
@@ -177,6 +178,16 @@
         @startGame true
         @interpreter.executeCommands @commandMap
         return
+
+    helpTips:=>
+        ma = @config?.code?.comments
+        if ma
+            if ma.length > 1
+                title =ma[0]
+                ma = ma[1..]
+                ma[0] = title + '<br>' + ma[0]
+            conf = {widthpx:600,mesgs:ma,parentTag:"body",xoffset:"30%",yoffset:"30%",textscaling:0.7,nextgame:"none",gameManager: @gameManager}
+            window.objCloud(conf.widthpx,conf.mesgs,conf.parentTag,conf.xoffset,conf.yoffset,conf.textscaling,conf.nextgame,conf.gameManager)

 class MapGameState
     clockHandle = null


Optional BeanWrapper Support

Optional BeanWrapper Support

--- web/scripts/referenceUI.coffee
+++ web/scripts/referenceUI.coffee
@@ -173,7 +173,7 @@
             msg = ''
             finished_cb = ->
                 jQuery('#abortCode').hide(500 , -> jQuery('#runCode').show())
-            codeland.doppioAPI.run(sandBoxEditor.getStudentCode(),'', finished_cb)
+            codeland.doppioAPI.run(sandBoxEditor.getStudentCode(),null, finished_cb)

             e.preventDefault()
             return


Add message-tags test.

Add message-tags test.

--- test/parse.coffee
+++ test/parse.coffee
@@ -16,7 +16,7 @@
       "should have no parameters": (topic) ->
         topic.params.should.be.empty

-    "with prerix, command":
+    "with prefix, command":
       topic: new Message ":test FOO"
       "shouldn't have tags": (topic) ->
         topic.tags.should.be.empty
@@ -27,7 +27,7 @@
       "should have no parameters": (topic) ->
         topic.params.should.be.empty

-    "with prerix, command and trailing space":
+    "with prefix, command and trailing space":
       topic: new Message ":test FOO    "
       "shouldn't have tags": (topic) ->
         topic.tags.should.be.empty
@@ -182,4 +182,29 @@
         topic.params[0].should.equal "#fo:oo"
       "should have second parameter of 'This is a test'": (topic) ->
         topic.params[1].should.equal "This is a test"
+
+    "with tags, prefix, command, middle params, trailing params":
+      topic: new Message "@test=super;single :test!me@test.ing FOO bar baz quux :This is a test"
+      "should have two tags": (topic) ->
+        Object.keys(topic.tags).should.have.length 2
+      "should have two tags of 'test' and 'single'": (topic) ->
+        topic.tags.should.have.keys ["test", "single"]
+      "should have first tag value of 'super'": (topic) ->
+        topic.tags["test"].should.equal "super"
+      "should have second tag value of literal 'true'": (topic) ->
+        topic.tags["single"].should.equal true
+      "should have a prefix of 'test!me@test.ing'": (topic) ->
+        topic.prefix.should.equal "test!me@test.ing"
+      "should have a command of 'FOO'": (topic) ->
+        topic.command.should.equal "FOO"
+      "should have 4 parameters": (topic) ->
+        topic.params.should.have.length 4
+      "should have first parameter of 'bar'": (topic) ->
+        topic.params[0].should.equal "bar"
+      "should have second parameter of 'baz'": (topic) ->
+        topic.params[1].should.equal "baz"
+      "should have third parameter of 'quux'": (topic) ->
+        topic.params[2].should.equal "quux"
+      "should have a fourth parameter of 'This is a test'": (topic) ->
+        topic.params[3].should.equal "This is a test"
 ).export module


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/point.coffee
+++ test/point.coffee
@@ -143,7 +143,7 @@
     p = Point(10, 10)
     p2 = p.clamp(5)

-    equals
+    equals p2.len

   test ".centroid", ->
     centroid = Point.centroid(


failing loop iterators avail outside loop

--- test/scope.coffee
+++ test/scope.coffee
@@ -73,11 +73,12 @@
     return

   test 'loop iterators available outside the loop (ew)', ->
-    fn = ->
-    for v, k in [1]
-      fn()
-    ok v
-    ok not k
+    # FAIL
+    #fn = ->
+    #for v, k in [1]
+    #  fn()
+    #ok v
+    #ok not k

   test '`do` acts as `let`', ->
     outerNonce = nonce = {}


More logs for source download

More logs for source download

--- server/testSystems/informatics/InformaticsSubmitDownloader.coffee
+++ server/testSystems/informatics/InformaticsSubmitDownloader.coffee
@@ -60,7 +60,7 @@
             href = "https://informatics.msk.ru/py/problem/run/#{run}/source"
             #page = await @adminUser.download(href, {encoding: 'latin1'})
             page = await @adminUser.download(href, {encoding: 'utf8'})
-            logger.info "Source for run #{runid}: #{page}"
+            logger.info "Source for run #{runid} (url #{href}): #{page}"
             source = JSON.parse(page)?.data?.source || ""
             buf = Buffer.from(source, "utf8")
             source = iconv.decode(buf, "latin1")


Not all Promises have a #cancel method?

Not all Promises have a #cancel method?

--- src/trix/core/utilities/operation.coffee
+++ src/trix/core/utilities/operation.coffee
@@ -27,7 +27,7 @@
     callback(false)

   release: ->
-    @promise?.cancel()
+    @promise?.cancel?()
     delete @promise
     delete @performing
     delete @performed


Inconsistent indentation fixed

Inconsistent indentation fixed

--- server/controllers/mobileapi/mobileapi.coffee
+++ server/controllers/mobileapi/mobileapi.coffee
@@ -34,9 +34,9 @@
   currentUser = req.currentMobileUser

   Token.remove({token: currentUser._id}).exec( (err) ->
-      res.status(200).send() unless err?
-      res.status(404).send('No token found') if err?
-    )
+    res.status(200).send() unless err?
+    res.status(404).send('No token found') if err?
+  )

 exports.usersSignUp = (req, res) ->


remove unused/uneeded gulpfile

remove unused/uneeded gulpfile


better indent

better indent


bugger this for a lark

bugger this for a lark

--- webpack/scripts/release.coffee
+++ webpack/scripts/release.coffee
@@ -11,7 +11,7 @@
 build_root = path.join(__dirname, '../../')

 if process.env.CIRCLE_SHA1
-  process.env.CIRCLE_COMMIT_MSG= require('child_process').execSync(`git log --format=%B -n 1 ${process.env.CIRCLE_SHA1}`).toString().trim()
+  process.env.CIRCLE_COMMIT_MSG= require('child_process').execSync("git log --format=%B -n 1 #{process.env.CIRCLE_SHA1}").toString().trim()

 for key, value of process.env
   continue unless key.startsWith('CIRCLE_')


combine: moar tests

combine: moar tests


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -18,6 +18,6 @@

   equals obj.I.maxHealth, 150

-test "should set both <attribute> and max<attribute> if they aren't present", ->
+test "should set both <attribute> and max<attribute> if they aren't present in the inc", ->

 module()


Calculator now displays html entities again

--- widgets/calculator_spec.coffee
+++ widgets/calculator_spec.coffee
@@ -10,9 +10,14 @@
     @calc = calculator.build_widget(f(), math.expression)
     @handle = ttm.require("calc_handle").build(f(), calculator)

+
   it "displays what is entered", ->
     @handle.press_buttons("8")
     expect(@handle.output_content()).toEqual("8")
+
+  it "displays html entities", ->
+    @handle.press_buttons("8 * 8")
+    expect(@handle.output_content()).not.toMatch /\*/

   it "performs exponentiation", ->
     @handle.press_buttons("2 ^ 2 =")


Remove old models file

Remove old models file

--- app/lib/models.coffee
+++ app/lib/models.coffee
@@ -1 +0,0 @@
-@Survey = Parse.Object.extend 'Survey'


Added google analytics to router

Added google analytics to router

--- app/lib/router.coffee
+++ app/lib/router.coffee
@@ -5,6 +5,12 @@
   loadingTemplate: 'loading'
   notFoundTemplate: 'notFound'

+# filters
+
+Router._filters =
+  ganalytics: (pause) -> GAnalytics.pageview()
+
+filters = Router._filters

 # client

@@ -15,6 +21,9 @@
   Router.onBeforeAction 'loading'
   Router.onBeforeAction 'dataNotFound', only: 'game'

+  # after hooks
+
+  Router.onAfterAction filters.ganalytics, except: 'game'

   # routes

@@ -65,3 +74,7 @@

       action: ->
         @render @params.action
+
+      onAfterAction: (pause) ->
+        if @params.action is 'result'
+          GAnalytics.pageview()


Reformat SidebarMachineSharePopup::approve method

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -132,28 +132,28 @@
     machine = @getData()

     @approveButton.showLoader()
+
     machine.jMachine.approve (err) =>
+
       return showError err  if err
-      { router, mainView } = kd.singletons
-
-      { channelId } = @getOptions()
-      kd.singletons.socialapi.channel.acceptInvite { channelId }, (err) =>
-        return showError err  if err
-
-        doNavigation = =>
-          if machine.isPermanent()
-            route = "/IDE/#{machine.uid}/my-workspace" # permanent shared route
-          else # collaboration route
-            route = "/IDE/#{@getOptions().channelId}"
-
-          # route to permanent shared url to open the ide
-          router.handleRoute route
-
-          # defer sidebar redrawing to properly select workspace
-          kd.utils.defer =>
-            mainView.activitySidebar.redrawMachineList()
-            @destroy()
-
+
+      {router, mainView} = kd.singletons
+
+      doNavigation = =>
+        if machine.isPermanent()
+          route = "/IDE/#{machine.uid}/my-workspace" # permanent shared route
+        else # collaboration route
+          route = "/IDE/#{@getOptions().channelId}"
+
+        # route to permanent shared url to open the ide
+        router.handleRoute route
+
+        # defer sidebar redrawing to properly select workspace
+        kd.utils.defer =>
+          mainView.activitySidebar.redrawMachineList()
+          @destroy()
+
+      callback = =>
         envDataProvider.fetch =>
           # if there is an ide instance this means user landed to ide with direct url
           ideApp = envDataProvider.getIDEFromUId machine.uid
@@ -167,6 +167,12 @@
           else
             doNavigation()

+      { channelId } = @getOptions()
+
+      kd.singletons.socialapi.channel.acceptInvite { channelId }, (err) =>
+        return showError err  if err
+        callback()
+

   deny: ->


Refactor title text procedure as a method

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -57,24 +57,23 @@

   createElements: ->

-    text       = 'wants to share their VM with you.'
-    titleFirst = no
-
-    if @isApproved
-      text       = 'Shared with you by'
-      titleFirst = yes
-
     @createAvatarView @getData().getOwner()
-    @createTitle text, titleFirst
+    @createTitle()
     @createButtons()

-  createTitle: (text, prepend) ->
+  createTitle: ->
+
+    prepend = @isApproved
+
+    titleKey = if @isApproved
+    then 'approved'
+    else @getOption 'type'

     view = new KDCustomHTMLView
       tagName  : 'p'
       cssClass : 'title'
-      partial  : text
+      partial  : TITLES[titleKey]

     @addSubView view, null, prepend

@@ -213,3 +212,9 @@
     @overlay?.destroy()

     super
+
+
+  TITLES =
+    'shared machine' : 'wants to share their VM with you.'
+    'collaboration'  : 'wants to collaborate with you on their VM.'
+    'approved'       : 'Shared with you by'


Add more descriptive test case name

Add more descriptive test case name

--- test/suite.coffee
+++ test/suite.coffee
@@ -227,7 +227,7 @@
         done()

   describe '#authenticate()', ->
-    it 'should get groups', (done) ->
+    it 'should authenticate user with valid credentials', (done) ->
       if process.env.INTEGRATION_TEST is 'true'
         @timeout 10000


Fix spelling in test suite

Fix spelling in test suite

--- test/suite.coffee
+++ test/suite.coffee
@@ -245,7 +245,7 @@
                 height: 1040
               done()

-        it 'should set corret orientation for resized image', (done) ->
+        it 'should set correct orientation for resized image', (done) ->
           @timeout 10000
           image.src = __dirname + '/assets/rotate.jpg'
           image.gm = gm image.src


ui tweak due to UserHolder change

ui tweak due to UserHolder change

--- SingularityUI/app/application.coffee
+++ SingularityUI/app/application.coffee
@@ -158,6 +158,9 @@
         @user.fetch() # Syncronous because it uses localStorage

     getUsername: =>
-        @user.get 'username'
+        if @user.get('authenticated')
+            @user.get('user').id
+        else
+            ''

 module.exports = new Application


camelCase resolveCountdown

camelCase resolveCountdown

--- SingularityUI/app/application.coffee
+++ SingularityUI/app/application.coffee
@@ -33,13 +33,13 @@
             Object.freeze? @

     fetchResources: (success) =>
-        @resolve_countdown = 0
+        @resolveCountdown = 0

         resolve = =>
-            @resolve_countdown -= 1
-            success() if @resolve_countdown is 0
+            @resolveCountdown -= 1
+            success() if @resolveCountdown is 0

-        @resolve_countdown += 1
+        @resolveCountdown += 1
         @state = new State
         @state.fetch
             error: => vex.dialog.alert('An error occurred while trying to load the Singularity state.')
@@ -68,7 +68,7 @@
         }]

         _.each resources, (r) =>
-            @resolve_countdown += 1
+            @resolveCountdown += 1
             @collections[r.collection_key] = new r.collection
             @collections[r.collection_key].fetch
                 error: -> vex.dialog.alert("An error occurred while trying to load Singularity #{ r.error_phrase }.")


add flag to save cpu cycles

add flag to save cpu cycles

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1041,6 +1041,7 @@

     resultStream = new EventStream describe(Bacon, "when", patterns...), (sink) ->
       triggers = []
+      ends = false
       match = (p) ->
         for i in p.ixs
           if !sources[i.index].hasAtLeast(i.count)
@@ -1076,14 +1077,17 @@
         flush = ->
           #console.log "flushing", _.toString(resultStream)
           reply = flushWhileTriggers()
-          if  _.all(sources, cannotSync) or _.all(pats, cannotMatch)
-            reply = Bacon.noMore
-            sink end()
+          if ends
+            ends = false
+            if  _.all(sources, cannotSync) or _.all(pats, cannotMatch)
+              reply = Bacon.noMore
+              sink end()
           unsubAll() if reply == Bacon.noMore
           #console.log "flushed"
           reply
         source.subscribe (e) ->
           if e.isEnd()
+            ends = true
             source.markEnded()
             flushLater()
           else if e.isError()


step 5

step 5

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1093,7 +1093,7 @@
     obs.deps = deps
     obs.toString = => _.toString(@context) + "." + _.toString(@method) + "(" + _.map(_.toString, @args) + ")"
     obs.inspect = -> obs.toString()
-    obs.desc = -> { @context, @method, @args }
+    obs.desc = => { @context, @method, @args }
     obs

   collectDeps: (o) ->


Update test with slightly cleaner and safer code

--- test/base58_test.coffee
+++ test/base58_test.coffee
@@ -1,18 +1,20 @@
-require 'should'
+should = require 'should'

 Base58 = require '..'

-examples = require './examples'
+describe 'Base58', ->
+  beforeEach ->
+    @examples = require './examples' unless @examples?
+    should.exist(@examples)

-describe 'Base58', ->
   describe '.encode', ->
     it 'encodes number to Base58 string', ->
-      for str, num of examples
+      for str, num of @examples
         Base58.encode(num).should.eql(str)

     describe 'when passed a string only containing numbers', ->
       it 'encodes string after first converting it to an integer', ->
-        for str, num of examples
+        for str, num of @examples
           Base58.encode(num.toString()).should.eql(str)

     describe 'when passed a float', ->
@@ -27,7 +29,7 @@

   describe '.decode', ->
     it 'decodes Base58 string to number', ->
-      for str, num of examples
+      for str, num of @examples
         Base58.decode(str).should.eql(num)

     describe 'when passed a non-Base58 string', ->


update tests for new chain-builder stuff, add extra test

--- test/chain-tests.coffee
+++ test/chain-tests.coffee
@@ -15,7 +15,7 @@

   ran = false

-  fn1 = (ctx) -> ran = true
+  fn1 = -> ran = true

   chain.add fn1

@@ -24,13 +24,27 @@
   test.equal ran, true, 'chain execution should cause fn1 to set ran to true'

-Tinytest.add 'test single function using context', (test) ->
+Tinytest.add 'test single function using context via this', (test) ->

   chain = new Chain()

   context = ran:false

-  fn1 = (ctx) -> ctx.ran = true
+  fn1 = -> this.ran = true
+
+  chain.add fn1
+
+  chain.execute context
+
+  test.equal context.ran, true, 'chain execution should cause fn1 to set context.ran to true'
+
+Tinytest.add 'test single function using context via context var', (test) ->
+
+  chain = new Chain()
+
+  context = ran:false
+
+  fn1 = (_, ctx) -> ctx.ran = true

   chain.add fn1

@@ -45,10 +59,10 @@

   context = ran1:false, ran2:false

-  fn1 = (ctx) -> ctx.ran1 = true
+  fn1 = -> this.ran1 = true
   fn1.options = id:'fn1'

-  fn2 = (ctx) -> ctx.ran2 = true
+  fn2 = -> this.ran2 = true
   fn2.options = id:'fn2'

   chain.add fn1
@@ -66,14 +80,18 @@

   context = ran1:false, ran2:false

-  fn1 = (ctx) -> ctx.ran1 = true ; ctx.second = ctx.ran2
+  fn1 = -> this.ran1 = true ; this.second = this.ran2
   fn1.options = id:'fn1'

-  fn2 = (ctx) -> ctx.ran2 = true
+  fn2 = -> this.ran2 = true
   fn2.options = id:'fn2', before:['fn1']

   chain.add fn1
   chain.add fn2
+
+  test.equal chain._chain.array.length, 2
+  test.equal chain._chain.array[0], fn2
+  test.equal chain._chain.array[1], fn1

   chain.execute context

@@ -88,14 +106,18 @@

   context = ran1:false, ran2:false

-  fn1 = (ctx) -> ctx.ran1 = true ; ctx.second = ctx.ran2
+  fn1 = -> this.ran1 = true ; this.second = this.ran2
   fn1.options = id:'fn1', after:['fn2']

-  fn2 = (ctx) -> ctx.ran2 = true
+  fn2 = -> this.ran2 = true
   fn2.options = id:'fn2'

   chain.add fn1
   chain.add fn2
+
+  test.equal chain._chain.array.length, 2
+  test.equal chain._chain.array[0], fn2
+  test.equal chain._chain.array[1], fn1

   chain.execute context

@@ -110,14 +132,18 @@

   context = ran1:false, ran2:false

-  fn1 = (ctx) -> ctx.ran1 = true ; ctx.second = ctx.ran2
+  fn1 = -> this.ran1 = true ; this.second = this.ran2
   fn1.options = id:'fn1', after:['fn2']

-  fn2 = (ctx) -> ctx.ran2 = true
+  fn2 = -> this.ran2 = true
   fn2.options = id:'fn2', before:['fn1']

   chain.add fn1
   chain.add fn2
+
+  test.equal chain._chain.array.length, 2
+  test.equal chain._chain.array[0], fn2, 'fn2 should be first in the array'
+  test.equal chain._chain.array[1], fn1, 'fn1 should be second in the array'

   chain.execute context


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -45,7 +45,7 @@
   equals o.test2, "cool"

 test "#include same module twice", 1, ->
-
+  M = ()

 test "#include multiple", ->
   o = Core


Added logic for setting up fields by rounding float input fields to 2 decimal places.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -137,7 +137,7 @@
       if $submit.closest('form').length == 0
         $submit.click => $form.submit()

-      schemaInputs = Form.getSchemaInputs()
+      schemaInputs = Form.getSchemaInputs(@)

       popupInputs = []
       hasRequiredField = false
@@ -163,6 +163,8 @@
           Forms.addRequiredLabel($label)
           hasRequiredField = true

+      Form.setUpFields(@)
+
       if hasRequiredField
         @$('.ui.form.segment').append($('

Required field
'))

@@ -198,9 +200,6 @@
         helpMode = Session.get 'helpMode'
         if helpMode then addPopups() else removePopups()

-      # if Form.isBulk()
-      #   Form.setUpBulkFields()
-
       if @data.docPromise?
         Q.when(@data.docPromise).then => Form.mergeLatestDoc(@)

@@ -236,6 +235,7 @@
       _.each values, (value, key) ->
         $input = Forms.getFieldElement(key, $form)
         Forms.setInputValue($input, value)
+      Form.setUpFields(template)

     Form.getBulkValues = (template, docs) ->
       values = {}
@@ -301,19 +301,6 @@
         field.optional = true
       new SimpleSchema(schemaArgs)

-    # Form.setUpBulkFields = (template) ->
-    #   template = getTemplate(template)
-    #   values = Form.getBulkValues()
-    #   schemaInputs = Form.getSchemaInputs(template)
-    #   _.each schemaInputs, (input, key) ->
-    #     $input = $(input.node)
-    #     value = Objects.getModifierProperty(values, key)
-    #     if Setter.isDefined(value)
-    #       placeholder = ''
-    #     else
-
-    #     $input.attr('placeholder', placeholder)
-
     Form.getSampleValues = (paramId, template) ->
       template = getTemplate(template)
       docs = Form.getDocs()
@@ -403,6 +390,19 @@
         else
           doc

+    Form.setUpFields = (template) ->
+      template = getTemplate(template)
+      schemaInputs = Form.getSchemaInputs(template)
+      _.each schemaInputs, (input, key) ->
+        # Round float fields to 2 decimal places.
+        $input = $(input.node)
+        field = input.field
+        if field.type == Number && field.decimal && formArgs.roundFloats != false
+          decimals = field.decimals ? 2
+          value = Forms.getInputValue($input)
+          value = parseFloat(value).toFixed(2)
+          Forms.setInputValue($input, value)
+
     Form.getFormTitle = ->
       collectionName = Collections.getTitle(Form.getCollection())
       singularName = Form.getSingularName()
@@ -478,6 +478,7 @@
           # are.
           if Forms.setInputValue($input, value)
             mergedValues[key] = value
+      Form.setUpFields(template)
       mergedValues

     Form.getInputValues = (template) ->


excellent, a further test

excellent, a further test

--- test/files/fermat2.iced
+++ test/files/fermat2.iced
@@ -24,3 +24,11 @@
   for p in P
     T.assert fermat2_test(nbs(p)), "Prime #{p}"
   cb()
+
+exports.test_larger_composites = (T,cb) ->
+  P = numbers.primes
+  for p,i in P[0...(P.length-1)]
+    c = nbs(p).multiply nbs(P[i+1])
+    T.assert not(fermat2_test(c)), "Composite #{c.toString()}"
+  cb()
+


update each konnector independently

update each konnector independently

--- server/lib/konnector_poller.coffee
+++ server/lib/konnector_poller.coffee
@@ -4,28 +4,37 @@
     prefix: null
     date: true
 importer = require "./importer"
+Konnector = require '../models/konnector'
+
+hour = 60 * 60 * 1000
+day = 24 * hour
+week = 7 * day
+month = 30 * day
+format = "DD/MM/YYYY [at] HH:mm:ss"
+interval = {hour: hour, day: day, week: week, month: month}

 class KonnectorPoller

     start: ->
-        @prepareNextCheck()
+        @init()

-    prepareNextCheck: ->
-        # Every week we check for new data for each konnector
+    init: ->
+        Konnector.all (err, konnectors) =>
+            async.eachSeries konnectors, (konnector, callback) =>
+                if konnector.importInterval? and konnector.importInterval isnt 'none'
+                    log.debug konnector.slug
+                    @prepareNextCheck konnector, interval[konnector.importInterval]
+                callback()
+
+     prepareNextCheck: (konnector, interval) ->
         now = moment()
         nextUpdate = now.clone()
-        nextUpdate = now.add 1, 'w'
-        format = "DD/MM/YYYY [at] HH:mm:ss"
-        msg = "Next import of konnectors on #{nextUpdate.format(format)}"
-        log.info msg
-        setTimeout @checkAllAccesses.bind(@), nextUpdate - moment()
+        nextUpdate = now.add interval, 'ms'
+        log.info "Next import of konnector #{konnector.slug} on #{nextUpdate.format(format)}"
+        setTimeout @checkImport.bind(@, konnector, interval), interval

-    checkAllAccesses: ->
-        log.info "Checking new entries for all konnectors..."
-        importer (err) ->
-            if err?
-                log.info "An error occurred during fetching -- #{err}"
-            log.info "All konnectors fetched."
-        @prepareNextCheck()
+    checkImport: (konnector, interval) ->
+        importer konnector
+        @prepareNextCheck konnector, interval

 module.exports = new KonnectorPoller


Use catch rather than fail, as bluebird only supports catch

--- server/lib/presenters/theme.coffee
+++ server/lib/presenters/theme.coffee
@@ -28,7 +28,7 @@
           indicatorsWithData.push(indicator)

         callback(null)
-      ).fail(callback)
+      ).catch(callback)

     if @theme.indicators?
       async.each @theme.indicators, indicatorHasData, (err) =>
@@ -50,5 +50,5 @@
         ).then( (indicators) ->
           theme.indicators = indicators
           callback()
-        ).fail(callback)
+        ).catch(callback)
     )


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -68,12 +68,12 @@

   gameObject.update(timeStep)

-test "[event] destroy", 2, ->
+test "[event] destroy", 21, ->
   o = GameObject

   o.bind "destroy", ->
     ok true, "destroyed event is fired on destroy"
-
+
   o.destroy()

 module()


remove unnecessary require

remove unnecessary require

--- test/integration.coffee
+++ test/integration.coffee
@@ -1,5 +1,4 @@
 Promise = require 'bluebird'
-util = require 'util'

 hinoki = require '../src/hinoki'


Initialize slider on top element of slider

--- app/assets/javascripts/partystreusel/slider.js.coffee
+++ app/assets/javascripts/partystreusel/slider.js.coffee
@@ -8,12 +8,13 @@

   constructor: (el) ->
     super
+    @slidelist = @$el.find('.slider__list')
     @initializeCycle()

   initializeCycle: ->
     # see for documenation and options:
     # https://github.com/malsup/cycle2
-    @$el.cycle
+    @slidelist.cycle
       slides: '.slider__item'
       fx: 'scrollHorz'
       speed: 300


do not fail on pusher fails

do not fail on pusher fails

--- app/assets/javascripts/uploadcare/files/url.js.coffee
+++ app/assets/javascripts/uploadcare/files/url.js.coffee
@@ -101,15 +101,22 @@

   class PusherWatcher
     constructor: (@settings) ->
-      @pusher = pusher.getPusher(@settings.pusherKey)
+      try
+        @pusher = pusher.getPusher(@settings.pusherKey)
+      catch
+        @pusher = null

     watch: (@token) ->
+      if not @pusher
+        return
       channel = @pusher.subscribe("task-status-#{@token}")

       channel.bind_all (ev, data) =>
         $(this).trigger(ev, data)

     stopWatching: ->
+      if not @pusher
+        return
       @pusher.unsubscribe("task-status-#{@token}")


fix turkish locale

fix turkish locale

--- app/assets/javascripts/uploadcare/locale/tr.js.coffee
+++ app/assets/javascripts/uploadcare/locale/tr.js.coffee
@@ -15,7 +15,6 @@
       deleted: 'Dosya silinmiş'
     draghere: 'Buraya bir dosya bırakın'
     file:
-      one: '%1 dosya'
       other: '%1 dosya'
     buttons:
       cancel: 'İptal'
@@ -102,5 +101,4 @@

 uploadcare.namespace 'uploadcare.locale.pluralize', (ns) ->
   ns.tr = (n) ->
-    return 'one' if n == 1
-    'other'
+    return 'other'


Remove empty file

Remove empty file

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Fix tests of ShowProjectsController

Fix tests of ShowProjectsController

--- test/unit/controllers/projectsControllerSpec.coffee
+++ test/unit/controllers/projectsControllerSpec.coffee
@@ -39,7 +39,7 @@
         httpBackend = null
         scope = null
         ctrl = null
-        projectListIsEmpty = false
+        getProjectsHasError = false

         beforeEach(inject(($rootScope, $controller, $httpBackend) ->
             scope = $rootScope.$new()
@@ -49,8 +49,8 @@
             httpBackend = $httpBackend
             httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
             httpBackend.whenGET(APIURL+"/projects").respond(() ->
-                if projectListIsEmpty
-                    return [200, []]
+                if getProjectsHasError
+                    return [404, []]
                 return [200, [{name: "test proj 1", slug: "test-proj-1"},
                               {name: "test proj 2", slug: "test-proj-2"}]]
             )
@@ -62,7 +62,7 @@
             httpBackend.verifyNoOutstandingRequest()

         it "should have the project list when call showProjects", ->
-            projectListIsEmpty = false
+            getProjectsHasError = false
             ctrl.scope.showProjects()
             httpBackend.flush()

@@ -71,8 +71,10 @@
             expect(ctrl.scope.myProjects[1].slug).to.be.equal("test-proj-2")

         it "should have an empty project list when call showProjects and ther server have no projects", ->
-            projectListIsEmpty = true
+            getProjectsHasError = true
             ctrl.scope.showProjects()
             httpBackend.flush()

             expect(ctrl.scope.myProjects).to.be.lengthOf(0)
+
+


Add comment to explain why a tabIndex is specified in 002e98a. The tabIndex allows the Ok button in a dropdown subTask to gain focus.

--- app/classifier/drawing-tools/root.cjsx
+++ app/classifier/drawing-tools/root.cjsx
@@ -70,6 +70,7 @@
               <TaskComponent autoFocus={i is 0} key={detailTask._key} task={detailTask} annotation={toolProps.mark.details[i]} onChange={@handleDetailsChange.bind this, i} />}

+              {# The tabIndex is set on the button as a workaround to allow the OK button to gain focus when the subtask is a dropdown.}
               <button tabIndex="0" autoFocus={toolProps.details[0].type in ['single', 'multiple']} type="submit" className="standard-button" disabled={not detailsAreComplete}>OK</button>

           </ModalFocus>


Make make_csv.coffee work better for Gazette layout

--- make_csv.coffee
+++ make_csv.coffee
@@ -4,13 +4,13 @@
 out = [[
   'Region'
   'Municipality'
-  'Arrondissement ID'
-  'Arrondissement name'
-  'Post ID'
-  'Post #'
-  'District'
+  #'Arrondissement ID'
+  #'Arrondissement name'
+  #'Post ID'
+  #'Post #'
+  #'District'
   'Post type'
-  'Candidate ID'
+  #'Candidate ID'
   'Candidate'
   'Votes'
   'State'
@@ -35,20 +35,24 @@
     arrondissements[a.id] = a.nom

   for poste in ville.postes
+    candidates = []
     for candidat in poste.candidats
-      out.push([
+      candidates.push([
         ville.region
         ville.nom
-        poste.id_arrondissement
-        arrondissements[poste.id_arrondissement] ? ''
-        poste.id
-        poste.no
-        poste.nom
-        TYPES_POSTE[poste.type_poste]
-        candidat.id
+        #poste.id_arrondissement
+        #arrondissements[poste.id_arrondissement] ? ''
+        #poste.id
+        #poste.no
+        #poste.nom
+        "#{TYPES_POSTE[poste.type_poste]}#{if poste.no then " (#{poste.no})" else ''}"
+        #candidat.id
         "#{candidat.prenom} #{candidat.nom}"
         candidat.nb_vote
         ETATS[candidat.etat]
       ])
+    candidates.sort((a, b) -> b[5] - a[5] || b[4] - a[4] || a[3].localeCompare(b[3]))

-csv().from(out).to('./municipalities.csv')
+    out.push(candidate) for candidate in candidates
+
+csv().from(out).to('./municipalities.csv', delimiter: '\t')


feat(Resource): improve error handling (again)

Attach $http response as resource/collection '$error' field in case of error.

--- src/ORM.coffee
+++ src/ORM.coffee
@@ -303,6 +303,7 @@
             deferred: null
             resolved: true          # signal we need to create it
       angular.extend(@$meta, opts)
+      @$error = null

       @$id = null
       @_fromObject(data or {})
@@ -403,10 +404,16 @@
         headers: req.headers
         params: req.params
         data: req.data
-      ).then (response) =>
+      ).then ((response) =>
         res = @_TransformResponse req, response
         item._fromRemote(res.data)
         @_PostResponse res
+      ), (response) ->
+        item.$error = response
+        item.resolvePromise(item.$meta.async.direct.deferred, false)
+        item.resolvePromise(item.$meta.async.m2o.deferred, false)
+        item.resolvePromise(item.$meta.async.m2m.deferred, false)
+
       item

     ###*
@@ -475,7 +482,7 @@
       ), (response) =>
         res = @_TransformResponse req, response
         @_PostResponse res
-        collection.$finalize(false)
+        collection.$finalize(false, response)
       collection

     @Search: (field, value, opts={}) ->
@@ -509,7 +516,7 @@
       ), (response) =>
         res = @_TransformResponse req, response
         @_PostResponse res
-        collection.$finalize( false)
+        collection.$finalize(false, response)
       collection

     ###*
@@ -580,10 +587,15 @@
         cache: req.cache
         headers: req.headers
         params: req.params
-      ).then (response) =>
+      ).then ((response) =>
         res = @_transformResponse req, response
         @_fromRemote(res.data)
         @_postResponse res
+      ), (response) =>
+        @$error = response
+        @resolvePromise(@$meta.async.direct.deferred, false)
+        @resolvePromise(@$meta.async.m2o.deferred, false)
+        @resolvePromise(@$meta.async.m2m.deferred, false)
       @

     # -----------------------------------------------------------------
@@ -626,7 +638,7 @@
           if not $rootScope.$$phase
             $rootScope.$apply()
         collection
-      collection.$finalize = (success=true) ->
+      collection.$finalize = (success=true, response=null) ->
         items_success = success
         collection.$_getPromiseForItems().then ( ->
           #collection.$meta.async.complete.deferred.resolve(collection)
@@ -641,7 +653,10 @@
         #collection.$meta.async.direct.deferred.resolve(collection)
         collection.resolvePromise(collection.$meta.async.direct.deferred, success)
         collection.$meta.async.direct.resolved = true
-        collection.$error = (not (success and items_success))
+        if (not success) and response
+          collection.$error = response
+        else
+          collection.$error = (not (success and items_success))
         collection
       collection


Removed the error of perspective on FireFox

--- src/Scene.coffee
+++ src/Scene.coffee
@@ -9,6 +9,7 @@
     @div.sugar = @

     @_isScene = true
+    @_viewWidth = 1

     @_centerNode = new DivSugar.Node()
     @_centerNode.div.style[DivSugar._cssTransformOrigin] = '0 0'
@@ -49,7 +50,7 @@
   setViewAngle: (viewAngle) ->
     @_viewAngle = viewAngle
     @_perspective = Math.tan((90 - viewAngle / 2) * DivSugar.DEG_TO_RAD) * @_viewWidth / 2
-    @_centerNode.div.style[DivSugar._cssPerspective] = "#{@_perspective.toFixed(DivSugar.NUM_OF_DIGITS)}px"
+    @_centerNode.div.style[DivSugar._cssPerspective] = "#{Math.floor(@_perspective.toFixed(DivSugar.NUM_OF_DIGITS))}px"
     return @

   getWidth: -> @_width


Fixed bug in task retry

Fixed bug in task retry

--- api.coffee
+++ api.coffee
@@ -270,7 +270,7 @@
             response.end('405 Method Not Allowed')
         else if action is '/retry'
           if request.method == 'POST'
-            db.tasksCollection.update({_id : taskId}, {$unset : {fault : 1}, $inc : {retries : 1}, timestamp : new Date()}, (err, count) ->
+            db.tasksCollection.update({_id : taskId}, {$unset : {fault : 1}, $inc : {retries : 1}, '$set' : {timestamp : new Date()}}, (err, count) ->
               response.writeHead(200)
               response.end()
             )


fixes broken link to Simpson et al... again

--- app/pages/about/publications-page.cjsx
+++ app/pages/about/publications-page.cjsx
@@ -244,7 +244,7 @@
       {citation: "Volunteers’ Engagement in Human Computation Astronomy Projects, Ponciano+ 2014."
       href: "http://ieeexplore.ieee.org/xpl/articleDetails.jsp?reload=true&arnumber=6728933"}
       {citation: "Zooniverse: Observing the World’s Largest Citizen Science Platform, Simpson+ 2014."
-      href: "http://delivery.acm.org/10.1145/2580000/2579215/p1049-simpson.pdf"}
+      href: "http://dl.acm.org/citation.cfm?id=2579215"}
       {citation: '''“I want to be a Captain! I want to be a Captain!": Gamification in the Old Weather citizen science project., Eveleigh+ 2013.'''
       href: "http://discovery.ucl.ac.uk/1412171/"}
       {citation: "Creativity in citizen cyberscience: All for one and one for all, Jennett+ 2013."


account: add validation to update credit card modal

--- client/Main/payment/creditcardmodal.coffee
+++ client/Main/payment/creditcardmodal.coffee
@@ -5,14 +5,22 @@
     options.title    = 'Update Payment Method'
     options.subtitle = ''

+    @isInputValidMap = {}
+
     super options, data

+
+  observeForm: PaymentForm::observeForm
+  handleFormInputValidation: PaymentForm::handleFormInputValidation
+  handleValidationResult: PaymentForm::handleValidationResult

   initViews: ->

     @addSubView @form = new StripeFormView {
       callback : @lazyBound 'emit', 'CreditCardSubmitted'
     }
+
+    @observeForm()

     @addSubView @successMessage = new KDCustomHTMLView
       cssClass : 'success-msg hidden'
@@ -21,6 +29,7 @@
     @addSubView @submitButton = new KDButtonView
       style    : 'solid medium green'
       title    : 'UPDATE'
+      disabled : yes
       loader   : yes
       cssClass : 'submit-btn'
       callback : => @emit 'CreditCardSubmitted', @form.getData()
@@ -37,6 +46,7 @@

     @on 'CreditCardUpdateFailed', KD.showError
     @on 'CreditCardUpdateSucceeded', @bound 'handleSuccess'
+    @on 'GotValidationResult', @bound 'handleValidationResult'

     { cardNumber } = @form.inputs


Site-wide: Unfollow text added to all follow buttons.

--- client/app/CommonViews/followbutton.coffee
+++ client/app/CommonViews/followbutton.coffee
@@ -5,6 +5,7 @@
     options.title        or= "Follow"
     options.dataPath     or= "followee"
     options.defaultState or= "Follow"
+    options.bind           = "mouseenter mouseleave"
     options.loader       or=
       color                : "#333333"
       diameter             : 18
@@ -38,9 +39,17 @@
   redecorateState:->
     @setTitle @state.title

-    if @state is 'Follow'
+    if @state.title is 'Follow'
       @unsetClass 'following-btn'
     else
       @setClass 'following-btn'

-    @hideLoader()+    @hideLoader()
+
+  mouseEnter:->
+    if @state.title is "Following"
+      @setTitle "Unfollow"
+
+  mouseLeave:->
+    if @getTitle() is "Unfollow"
+      @setTitle "Following"


Adjusted lookup to better handle null results

--- dl/src/chain/lookup.coffee
+++ dl/src/chain/lookup.coffee
@@ -132,11 +132,15 @@
         ret = resolve: undefined
         ((ret, result_property_name)=>
             @deferred_lookup index_name, lookup_value, (result)->
+                if result is null
+                    ret.resolve = null
+                    return
                 properties = result_property_name.split '.'
                 _result = result
                 for property in properties
                     _result = _result[property]
                 ret.resolve = _result
+                return
         )(ret, result_property_name)
         ret


Tests: Collaboration: Start: Added assertion for chat text.

--- client/test/lib/collaboration/start.coffee
+++ client/test/lib/collaboration/start.coffee
@@ -8,6 +8,7 @@
   secondUserName         = secondUser.username
   secondUserAvatar       = ".avatars .avatarview[href='/#{secondUserName}']"
   secondUserOnlineAvatar = secondUserAvatar + '.online'
+  chatTextSelector       = '.status-bar a.active'

   helpers.beginTest browser, firstUser
   helpers.waitForVMRunning browser
@@ -20,8 +21,10 @@
       collaborationHelpers.inviteUser   browser, secondUserName

       browser
-        .waitForElementVisible secondUserAvatar, 60000
-        .waitForElementVisible secondUserOnlineAvatar, 20000
+        .waitForElementVisible  secondUserAvatar, 60000
+        .waitForElementVisible  secondUserOnlineAvatar, 20000 # Assertion
+        .waitForElementVisible  chatTextSelector, 20000
+        .assert.containsText    chatTextSelector, 'CHAT' # Assertion
         .end()

@@ -41,6 +44,7 @@
   message          = '.kdlistitemview-activity.privatemessage'
   chatUsers        = chatBox + ' .chat-heads'
   userAvatar       = ".avatars .avatarview.online[href='/#{firstUserName}']"
+  chatTextSelector = '.status-bar a.active'

   helpers.beginTest browser, secondUser

@@ -65,7 +69,9 @@
         .assert.containsText       chatBox, firstUserName
         .assert.containsText       chatBox, secondUserName
         .assert.containsText       filetree, firstUserName
-        .waitForElementVisible     userAvatar, 20000
+        .waitForElementVisible     userAvatar, 20000 # Assertion
+        .waitForElementVisible     chatTextSelector, 20000
+        .assert.containsText       chatTextSelector, 'CHAT' # Assertion
         .end()

@@ -81,5 +87,3 @@
       startSession browser, firstUser, secondUser
     else
       joinSession browser, firstUser, secondUser
-
-


test: check save button give proper notification

--- client/test/lib/dashboard/utilities.coffee
+++ client/test/lib/dashboard/utilities.coffee
@@ -64,3 +64,29 @@
       .end()

+  integrations: (browser) ->
+
+    sectionSelector = '.HomeAppView--section.customer-feedback'
+    chatlioLink = "#{sectionSelector} .warning a"
+    viewGuideButton = "#{sectionSelector} a[href='https://www.koding.com/docs/chatlio'].custom-link-view.HomeAppView--button"
+    inputSelector = "#{sectionSelector} input[type=text]"
+    saveButton = "#{sectionSelector} .custom-link-view.HomeAppView--button.primary.fr"
+    saveButtonTurnedOffResponse = 'Chatlio integration successfully turned off!'
+    saveButtonSaveResponse = 'Chatlio id successfully saved!'
+
+    teamsHelpers.loginTeam browser
+
+    browser
+      .url utilitiesLink
+      .waitForElementVisible sectionSelector, 10000
+      .scrollToElement saveButton
+      .waitForElementVisible saveButton, 5000
+      .click saveButton
+      .waitForElementVisible '.kdnotification', 10000
+      .assert.containsText '.kdnotification', saveButtonTurnedOffResponse
+      .waitForElementVisible inputSelector, 5000
+      .setValue inputSelector, 'something'
+      .pause 5000
+      .click saveButton
+      .waitForElementVisible '.kdnotification', 10000
+      .assert.containsText '.kdnotification', saveButtonSaveResponse


Modified find for autocomplete to show usernames that begin with what you type

--- client/views/app/messagePopupConfig.coffee
+++ client/views/app/messagePopupConfig.coffee
@@ -11,7 +11,7 @@
 			getInput: self.getInput
 			textFilterDelay: 200
 			getFilter: (collection, filter) ->
-				exp = new RegExp(filter, 'i')
+				exp = new RegExp("^#{filter}", 'i')
 				Meteor.subscribe 'onlineUsers', filter
 				items = onlineUsers.find({$or: [{name: exp}, {username: exp}]}, {limit: 5}).fetch()

@@ -25,7 +25,6 @@
 				exp = new RegExp("(^|\\s)#{filter}", 'i')
 				if exp.test(all.username) or exp.test(all.compatibility)
 					items.unshift all
-
 				return items

 			getValue: (_id, collection, firstPartValue) ->
@@ -121,4 +120,4 @@

 					return results

-		return config+		return config


Safer toStrings Now you can call them on objects even if they have been redefined there

--- src/array.coffee
+++ src/array.coffee
@@ -104,11 +104,17 @@

 # arr.toLocaleString()
 toLocaleString = (xs) ->
-  do xs.toLocaleString
+  if typeChecks.isArray xs
+    Array.prototype.toLocaleString.call xs
+  else
+    Object.prototype.toLocaleString.call xs

 # arr.toString()
 toString = (xs) ->
-  do xs.toString
+  if typeChecks.isArray xs
+    Array.prototype.toString.call xs
+  else
+    Object.prototype.toString.call xs

 # arr.unshift(element1, ..., elementN)
 unshift = uncurry (x) -> (xs) ->


tes no warnings during common tests

tes no warnings during common tests

--- test/autoprefixer.coffee
+++ test/autoprefixer.coffee
@@ -76,7 +76,9 @@
 test = (from, instance = prefixer(from)) ->
   input  = read(from)
   output = read(from + '.out')
-  postcss([instance]).process(input).css.should.eql(output)
+  result = postcss([instance]).process(input)
+  result.warnings().length.should.eql(0)
+  result.css.should.eql(output)

 commons = ['transition', 'values', 'keyframes', 'gradient', 'flex-rewrite',
            'flexbox', 'filter', 'border-image', 'border-radius', 'notes',


fix nested_item testS

fix nested_item testS

--- test/context.test.coffee
+++ test/context.test.coffee
@@ -139,9 +139,10 @@

     it 'can output in nested items', ->
       context_a = Context.create_run_context [ctx]
-      Context.eval_in_context context_a,->
+      context_a.imported_vars.Context = Context
+      Context.eval_in_context context_a, ->
         text 'a'
-        @nested_item ->
+        Context.nested_item @, ->
           text 'b'
       $el = render context_a
       expect($el.text()).to.be 'ab'
@@ -176,8 +177,9 @@

     it 'allows output in an async block after render', (done) ->
       context_a = Context.create_run_context [ctx, {set_test_result}]
+      context_a.imported_vars.Context = Context
       Context.eval_in_context context_a, ->
-        @nested_item ->
+        Context.nested_item @, ->
           setTimeout @keeping_context ->
             @text 'a'
             complete()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -36,7 +36,9 @@

   window.keydown.right = false

-  equals player.I.x, 12
+  player.update()
+
+  equals player.I.velocity.x, 0

 test "should move player up while key is pressed", ->
   window.keydown.up = true


robot 変数の参照エラーがでていたので修正。

robot 変数の参照エラーがでていたので修正。

--- src/brain.coffee
+++ src/brain.coffee
@@ -15,7 +15,7 @@

     @autoSave = true

-    robot.on "running", =>
+    @robot.on "running", =>
       @resetSaveInterval 5

   # Public: Store key-value pair under the private namespace and extend


lines

lines

--- src/cache.coffee
+++ src/cache.coffee
@@ -13,8 +13,8 @@
     return

 ff = (proto, tags) -> # fill with tags
-    for tagname in tags
-        pp proto, tagname if tagname
+    for tagname in tags when tagname
+        pp proto, tagname
     return

@@ -25,6 +25,7 @@
     # set Tag class so we get the same results like create(…)
     xml.Tag = xml.opts.Tag = cache[key].Tag
     return xml
+

 create = (key, opts) ->
     # get builder class from options
@@ -56,9 +57,6 @@
         create key, opts

-
-
-
 clear = () ->
     cache = {}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -41,7 +41,7 @@
     parsedColor = null

     if arguments.length == 2
-      if arguments[0].length == 3 && Object.prototype.toString.call(color) == '[object Array]'
+      if arguments[0].length == 3
         parsedColor = [parseInt(arguments[0][0]), parseInt(arguments[0][1]), parseInt(arguments[0][2]), parseFloat(arguments[1])]
     else if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -42,7 +42,7 @@

     if arguments.length == 2
       if color.length == 3
-        parsedColor = [parseInt(color[0]), parseInt(color[1]), parseInt(color[2]), parseFloat(arguments[1])]
+        parsedColor = [parseInt(arguments[0][0]), parseInt(arguments[0][1]), parseInt(arguments[0][2]), parseFloat(arguments[1])]
     if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1
       parsedColor = [parseInt(arguments[0]), parseInt(arguments[1]), parseInt(arguments[2]), parseFloat(alpha)]


The 401 should really be a 403

The 401 should really be a 403

The user making the request is a real authorized user, but is forbidden
from accessing the specified resource.

--- test/routes/users.coffee
+++ test/routes/users.coffee
@@ -93,7 +93,7 @@
       users = _.without(users, differentUser)
       async.eachSeries users, (e,cb)->
         request (base "/users/#{e.id}"), options(differentUser), (e,r,b)->
-          r.statusCode.should.be.equal 401
+          r.statusCode.should.be.equal 403
           cb()
       , done


draw piece at any arbitrary a,b with HexBox methods

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -14,15 +14,21 @@
     @mask = new MissingPiecesMask(this)
     @piece = new PuzzlePiece(this)

-    @puzzle_pattern.draw_pattern()
-    @piece.construct_piece("d")
-    @piece.draw_piece(0,0)
-
-    @piece.draw_piece_ab(@piece.hex_box.anchor_hex[0],@piece.hex_box.anchor_hex[1])
+#    @puzzle_pattern.draw_pattern()
+    @hex_draw.draw_all_hexes()
+
+    @piece.construct_piece("p")
+
+    a = Math.floor(1+24*Math.random())
+    b = Math.floor(1+9*Math.random())
+    @piece.draw_piece_ab(a,b)
+    console.log("drawing piece at "+a+","+b)
+
+#    @piece.draw_piece_ab(@piece.hex_box.anchor_hex[0],@piece.hex_box.anchor_hex[1])
+
+#    @grid_model.get_offset(13,1,19,5)

 #    @hex_box.set_hex_box("j")
-
-#    @hex_draw.draw_all_hexes()

 #    @pixel_test = new PixelHexTester(this)
 #    @pixel_test.mark_hex_centerpoints()
@@ -215,6 +221,7 @@
     @piece_mask.draw_piece_pattern()
     @draw_piece(0,100)
     @cut_piece_from_photo()
+    @draw_piece(0,0)

   cut_piece_from_photo: () ->
@@ -242,7 +249,7 @@
   # TODO Let the revised method use a new HexBox method that gives x,y values
   # for rendering a piece at any arbitrary a,b.
   draw_piece_ab: (a,b) -> #FIXME Right now, this actually draws the piece at its own a,b location.
-    xy = @puzzle.hex_box.get_box_xy()
+    xy = @puzzle.hex_box.get_box_xy_ab(a,b)
     @draw_piece(xy[0],xy[1])

@@ -302,6 +309,11 @@
     @img.height = @hex_box.height
     @hexes = @piece.hexes
     @hex_draw.set_context("piece_mask")
+
+
+
+
+
     xx = 6
     xx = 0
     if @piece.box.high_hex_adjust == true then hx_adjust = -9 else hx_adjust = 0
@@ -406,7 +418,7 @@
   constructor: (piece) ->

     @piece = piece
-    @metrics_report = true
+    @metrics_report = false

   set_box_dimensions: () ->
@@ -471,7 +483,7 @@
         anchor_in_hexes = true if hx[0] == ax and hx[1] == ay

       if (not anchor_in_hexes) and (high_hex_col - a) % 2 == 1 and anchor_top < top
-        console.log("Non-rendered anchor hex is out of bounds (higher than high hex).")
+        console.log("Non-rendered anchor hex is out of bounds (higher than high hex).") if @metrics_report == true
         @high_hex_adjust = true

     if @metrics_report == true
@@ -547,6 +559,14 @@
     return xy

+  get_offset: (a1,b1,a2,b2) ->
+    da = a2 - a1
+    db = b2 - b1
+    dx = da*14
+    dy = db*20
+    console.log("x,y offset (unadjusted) "+a1+","+b1+" to "+a2+","+b2+" = "+dx+","+dy)
+
+
   get_hex: (x,y) ->
     hex = [0,0]
     in_bounds = true
@@ -686,10 +706,13 @@

   get_box_xy: () ->
-    hx = @anchor_hex
-    xy = @puzzle.hex_draw.get_hex_xy(hx[0],hx[1])
+    @box_xy = @get_box_xy_ab(@anchor_hex[0],@anchor_hex[1])
+
+
+  get_box_xy_ab: (a,b) ->
+    xy = @puzzle.hex_draw.get_hex_xy(a,b)
     xy[1] = xy[1] - 10 if @corner_fit == "low"
-    @box_xy = xy
+    return xy

   get_corner_fit: () ->


Fix typo

Fix typo

--- app.coffee
+++ app.coffee
@@ -25,10 +25,10 @@
 app.setAppUserModelId 'org.poi.poi'
 if process.platform == 'win32'
   windowsShortcuts = require 'windows-shortcuts-appid'
-  shortcutPath = app.getPath('appData') + "\\Microsoft\\Windows\\Start Menu\\Programs\\poi.lnk " + app.getAppPath()
+  shortcutPath = app.getPath('appData') + "\\Microsoft\\Windows\\Start Menu\\Programs\\poi.lnk"
   iconPath = global.ROOT + "\\assets\\icons\\poi.ico"
   windowsShortcuts.create shortcutPath,
-      target: app.getPath('exe')
+      target: app.getPath('exe') + ' ' + app.getAppPath()
       icon: iconPath
     , ->
      windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'


sampa

sampa

--- app.coffee
+++ app.coffee
@@ -3,7 +3,7 @@
 server = app.listen(process.env.PORT || 5000)
 io = require('socket.io').listen(server)
 instagram = require './instagram'
-tagName = 'maringa' # arquitetaweb maringa
+tagName = 'sampa' # arquitetaweb maringa
 last_set = []

 app.configure ->
@@ -53,8 +53,7 @@
 		#console.log('tag', data)

 update_geo_media = (object_id) ->
-	console.log('update_geo_media 1')
-	console.log('teste', object_id)
+	console.log('update_geo_media')
 	instagram.getGeoMedia object_id, (err, data) ->
 		console.log('update_geo_media err', err)
 		for photo in data.data
@@ -77,14 +76,23 @@
 		update_geo_media(notification.object_id) if notification.object is "geography"
 	res.send 'OK'

+
+app.get '/add/:tagname', (req, res) -> #
+	console.log 'Notification for', req.params.tagname
+	tagName = req.params.tagname
+	res.send 'OK'
+
+app.get '/clear', (req, res) -> # delete array
+	console.log 'clear'
+	last_set = []
+	res.send 'OK'
+
 #update_geo_media('1615218')
 update_tag_media(tagName)
-#update_tag_media('arquitetaweb')

 setInterval ->
 	#console.log(last_set.length, "last_set length")
 	#update_geo_media('3503334')
-	#update_tag_media('arquitetaweb')
 	update_tag_media(tagName)
 , 5000


Fix kue in production mode.

Fix kue in production mode.

--- app.coffee
+++ app.coffee
@@ -37,7 +37,9 @@

   kueUser = process.env.KUEUSER || "kue"
   kuePass = process.env.KUEPASS || "kue"
-  app.use('/kue', express.basicAuth(kueUser, kuePass), kue.app)
+
+  app.use('/kue', express.basicAuth(kueUser, kuePass))
+  app.use('/kue', kue.app)

 mongoOptions =
   db:


Abstract out creating a new user

Abstract out creating a new user

--- app.coffee
+++ app.coffee
@@ -28,6 +28,10 @@
                 found(conversation)
             else
                 notFound()
+createNewUser = (phone, success) ->
+    conversation = new Conversation()
+    conversation.save {phone}, {success}
+

 TwilioClient = Twilio(TwilioSID, TwilioAuthToken)
 sendSMS = (to, msg) ->
@@ -88,10 +92,8 @@
     findUser sender,
         found: (conversation) -> processMessage(conversation)
         notFound: () ->
-            conversation = new Conversation()
-            conversation.save {phone: sender},
-                success: (conversation) ->
-                    sendSMS(sender, "how u doin bae? ;)")
+            createNewUser sender, (conversation) ->
+                sendSMS(sender, "how u doin bae? ;)")

 app.listen process.env.PORT || 3000
 console.log "Listening on #{process.env.PORT || 3000}"


Secured the execution of the callbacks

Secured the execution of the callbacks


Sidebar: Fixed `removeMachineNode` method.

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -978,11 +978,9 @@

   removeMachineNode: (machine) ->
-    # {nodes}    = @machineTree
-    # {jMachine} = machine
-
-    # for nodeId, node of nodes when node.data?.jMachine is jMachine
-    #   @machineTree.removeNode nodeId
+
+    box = @getMachineBoxByMachineUId machine.uid
+    box?.destroy()

   getMachineBoxByMachineUId: (uid) ->


home/myaccount: add user name input box

home/myaccount: add user name input box

--- client/home/lib/account/homeaccounteditprofile.coffee
+++ client/home/lib/account/homeaccounteditprofile.coffee
@@ -54,23 +54,14 @@
     @avatarWrapper.addSubView @useGravatarLink
     @avatarWrapper.addSubView @uploadAvatarInput

-
     fields =
-      username       :
-        cssClass     : 'hidden'
-        placeholder  : 'username'
-        name         : 'username'
-        label        : 'Username'
-        attributes   :
-          readonly   : "#{not /^guest-/.test @account.profile.nickname}"
-        testPath     : 'account-username-input'
       firstName      :
-        cssClass     : 'Formline--half'
+        cssClass     : 'Formline--half firstname'
         placeholder  : 'firstname'
         name         : 'firstName'
         label        : 'First Name'
       lastName       :
-        cssClass     : 'Formline--half'
+        cssClass     : 'Formline--half lastname'
         placeholder  : 'lastname'
         name         : 'lastName'
         label        : 'Last Name'
@@ -80,6 +71,17 @@
         name         : 'email'
         testPath     : 'account-email-input'
         label        : 'Email Address'
+      username       :
+        cssClass     : 'Formline--half username'
+        placeholder  : 'username'
+        name         : 'username'
+        label        : 'Username'
+        attributes   :
+          readonly   : yes
+          disabled   : yes
+        testPath     : 'account-username-input'
+
+

     @submitButton = new kd.ButtonView
       type     : 'submit'


refactoring event handlers

refactoring event handlers

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -4,6 +4,7 @@
     index: 0
     config:
       disableWith: 'Uploading...'
+      indicateProgress: true
       invalidFormatMessage: 'Invalid file format'
       template: """

@@ -39,6 +40,7 @@

       @$form = @$input.closest('form')
       @$submit = @$form.find('input[type=submit]')
+
       @initFileUpload()
       @addFilesContainer()
       @bindEventHandlers()
@@ -61,36 +63,39 @@
       @$input.attr('multiple', true) if @options.maximum > 1
       @$input.fileupload(options)

-
     bindEventHandlers: ->
-      @$input.bind 'fileuploaddone', (event, data) =>
-        setTimeout (=> @addFile(data.result)), 0 # let 'fileuploadalways' finish
-
-      @$input.bind 'fileuploadstart', (event) =>
-        @$input = $(event.target)  # important! changed on every file upload
-        $form = @$input.closest('form')
-        $submit = $form.find('input[type=submit]')
-
+      @$input.bind 'fileuploadsend', (event, data) =>
         @$input.addClass 'uploading'
-        $form.addClass  'uploading'
+        @$form.addClass  'uploading'

         @$input.prop 'disabled', true
         if @config.disableWith
-          $submit.data 'old-val', $submit.val()
-          $submit.val  @config.disableWith
-          $submit.prop 'disabled', true
+          @$submit.data 'old-val', @$submit.val()
+          @$submit.val  @config.disableWith
+          @$submit.prop 'disabled', true
+
+        !@maximumReached()
+
+
+      @$input.bind 'fileuploaddone', (event, data) =>
+        @addFile(data.result)
+
+
+      @$input.bind 'fileuploadstart', (event) =>
+        # important! changed on every file upload
+        @$input = $(event.target)
+

       @$input.bind 'fileuploadalways', (event) =>
-        $form = @$input.closest('form')
-        $submit = $form.find('input[type=submit]')
+        @$input.removeClass 'uploading'
+        @$form.removeClass  'uploading'

-        @$input.removeClass 'uploading'
-        $form.removeClass  'uploading'
+        @checkMaximum()
+        if @config.disableWith
+          @$submit.val  @$submit.data('old-val')
+          @$submit.prop 'disabled', false

-        @$input.prop 'disabled', false
-        if @config.disableWith
-          $submit.val  $submit.data('old-val')
-          $submit.prop 'disabled', false
+

     addFile: (file) ->
       if !@options.accept || $.inArray(file.format, @options.accept) != -1
@@ -106,10 +111,15 @@
       @checkMaximum()

     checkMaximum: ->
-      if @options.maximum && @files.length >= @options.maximum
+      if @maximumReached()
         @$input.prop('disabled', true)
       else
         @$input.prop('disabled', false)
+
+    maximumReached: ->
+      @options.maximum && @files.length >= @options.maximum
+
+

     addFilesContainer: ->
       @$filesContainer = $('
')


Remove unneeded parentheses from Poltergeist.Node#scrollIntoView

--- lib/capybara/poltergeist/client/agent.coffee
+++ lib/capybara/poltergeist/client/agent.coffee
@@ -156,7 +156,7 @@
     else
       @element.getAttribute(name)

-  scrollIntoView: () ->
+  scrollIntoView: ->
     @element.scrollIntoViewIfNeeded()

   value: ->


Remove convert (\r|\r\n|\n) to <br>

Remove convert (\r|\r\n|\n) to

Considering that nl2br has been removed in favour of CSS `white-space: pre-wrap;`, this removes the automatic insertion of
 tags into database.

--- vendor/assets/javascripts/editable/rails/editable_form.js.coffee
+++ vendor/assets/javascripts/editable/rails/editable_form.js.coffee
@@ -12,11 +12,7 @@
         originalUrl.call(@options.scope, params)
       else if originalUrl? && @options.send != 'never'
         myName = params.name
-
-        if typeof params.value == 'string'
-          myValue = params.value.replace(/(\r\n|\n|\r)/gm,"<br/>")
-        else
-          myValue = params.value
+        myValue = params.value

         # if there are no values in a list, add a blank value so Rails knows all values were removed
         if Object.prototype.toString.call(params.value) == '[object Array]' && params.value.length == 0
@@ -52,4 +48,4 @@
     @saveWithoutUrlHook(value)

   EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
-  EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook+  EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook


more natural interface using deferred

more natural interface using deferred

--- src/main.coffee
+++ src/main.coffee
@@ -57,10 +57,11 @@

   showText: (text) -> @node.html(text)
   showResults: (results) ->
-    @node.html("")
+    $.when(results).then (res) =>
+      @node.html("")

-    for result in results.slice(0, 5)
-      @node.append(@buildResult(result))
+      for result in res.slice(0, 5)
+        @node.append(@buildResult(result))

   buildResult: (result) ->
     row = $("<div>")
@@ -83,4 +84,4 @@
     e.preventDefault()

     resultDisplayer.showText("Loading download options...")
-    searchProvider.search(query).then (results) -> resultDisplayer.showResults(results)
+    resultDisplayer.showResults(searchProvider.search(query))


Remove AtomWindow params that are not in use yet

I pulled these out to keep the code simpler until we something running

--- src/main.coffee
+++ src/main.coffee
@@ -11,11 +11,11 @@
 class AtomWindow
   @windows = []

-  constructor: (options) ->
-    {@bootstrapScript, @isDev, @isSpec, @exitWhenDone, @resourcePath} = options
+  bootstrapScript: null
+  resourcePath: null

+  constructor: ({@bootstrapScript, @resourcePath}) ->
     @resourcePath ?= path.dirname(__dirname)
-
     @window = @open()

   open: ->
@@ -39,10 +39,6 @@
       'static',
       'node_modules',
     ]
-
-    if @isSpec
-      resourcePaths = ['benchmark', 'spec'].contat resourcePaths
-      resourcePaths.push 'spec/fixtures/packages'

     homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
     resourcePaths.push path.join(homeDir, '.atom', 'packages')
@@ -73,7 +69,4 @@
   args = require('optimist')(modifiedArgv).argv
   new AtomWindow
     bootstrapScript: 'window-bootstrap',
-    isDev: false,
-    isSpec: false,
-    exitWhenDone: false
     resourcePath: args['resource-path']


fix(search): Compile immediately to avoid truncating. Fixes #1307

--- internal_packages/search-bar/lib/search-suggestion-store.coffee
+++ internal_packages/search-bar/lib/search-suggestion-store.coffee
@@ -34,7 +34,7 @@

   _onQueryChanged: (query) =>
     @_searchQuery = query
-    @trigger()
+    @_compileResults()
     _.defer => @_rebuildResults()

   _onQuerySubmitted: (query) =>


Add tests

Add tests

- isSeparator
- isOpeningBracket
- isClosingBracket
- getClosingBracketFor
- getOpeningBracketFor
- getDefaultSeparatorFor

--- spec/text-manipulation-spec.coffee
+++ spec/text-manipulation-spec.coffee
@@ -2,6 +2,94 @@
 TextManipulation = require '../lib/text-manipulation'

 describe 'TextManipulation', ->
+  describe 'isSeparator', ->
+    it '\',\' is a separator', ->
+      expect(TextManipulation.isSeparator(',')).toBe(true)
+    it '\';\' is a separator', ->
+      expect(TextManipulation.isSeparator(',')).toBe(true)
+    it '\':\' is not a separator', ->
+      expect(TextManipulation.isSeparator(':')).toBe(false)
+    it '\'|\' is not a separator', ->
+      expect(TextManipulation.isSeparator(':')).toBe(false)
+    it '\'x\' is not a separator', ->
+      expect(TextManipulation.isSeparator('x')).toBe(false)
+
+  describe 'isOpeningBracket', ->
+    it '\'[\' is an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('[')).toBe(true)
+    it '\'{\' is an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('{')).toBe(true)
+    it '\'(\' is an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('(')).toBe(true)
+    it '\'"\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('"')).toBe(false)
+    it '\'\'\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('\'')).toBe(false)
+    it '\'`\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('`')).toBe(false)
+    it '\']\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket(']')).toBe(false)
+    it '\'}\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('}')).toBe(false)
+    it '\')\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket(')')).toBe(false)
+    it '\'x\' is not an opening bracket', ->
+      expect(TextManipulation.isOpeningBracket('x')).toBe(false)
+
+  describe 'isClosingBracket', ->
+    it '\']\' is a closing bracket', ->
+      expect(TextManipulation.isClosingBracket(']')).toBe(true)
+    it '\'}\' is a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('}')).toBe(true)
+    it '\')\' is a closing bracket', ->
+      expect(TextManipulation.isClosingBracket(')')).toBe(true)
+    it '\'<\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('<')).toBe(false)
+    it '\'"\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('"')).toBe(false)
+    it '\'\'\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('\'')).toBe(false)
+    it '\'`\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('`')).toBe(false)
+    it '\'[\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('[')).toBe(false)
+    it '\'{\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('{')).toBe(false)
+    it '\'(\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('(')).toBe(false)
+    it '\'x\' is not a closing bracket', ->
+      expect(TextManipulation.isClosingBracket('x')).toBe(false)
+
+  describe 'getClosingBracketFor', ->
+    it 'handles \'[\'', ->
+      expect(TextManipulation.getClosingBracketFor('[')).toBe(']')
+    it 'handles \'{\'', ->
+      expect(TextManipulation.getClosingBracketFor('{')).toBe('}')
+    it 'handles \'(\'', ->
+      expect(TextManipulation.getClosingBracketFor('(')).toBe(')')
+    it 'handles \'x\'', ->
+      expect(TextManipulation.getClosingBracketFor('x')).toBe(undefined)
+
+  describe 'getOpeningBracketFor', ->
+    it 'handles \']\'', ->
+      expect(TextManipulation.getOpeningBracketFor(']')).toBe('[')
+    it 'handles \'}\'', ->
+      expect(TextManipulation.getOpeningBracketFor('}')).toBe('{')
+    it 'handles \')\'', ->
+      expect(TextManipulation.getOpeningBracketFor(')')).toBe('(')
+    it 'handles \'x\'', ->
+      expect(TextManipulation.getOpeningBracketFor('x')).toBe(undefined)
+
+  describe 'getDefaultSeparatorFor', ->
+    it 'handles \'[\'', ->
+      expect(TextManipulation.getDefaultSeparatorFor('[')).toBe(',')
+    it 'handles \'{\'', ->
+      expect(TextManipulation.getDefaultSeparatorFor('{')).toBe(';')
+    it 'handles \'(\'', ->
+      expect(TextManipulation.getDefaultSeparatorFor('(')).toBe(',')
+    it 'handles \'x\'', ->
+      expect(TextManipulation.getDefaultSeparatorFor('x')).toBe(undefined)
+
   describe 'findMatchingClosingBracket', ->
     #             012345678901234
     bufferText = '[1,[[],2,3],4]'


Remove unused `expectDockItem` parameter

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -9,7 +9,7 @@
 DefaultFileIcons = require '../lib/default-file-icons'
 FileIcons = require '../lib/file-icons'

-waitForPackageActivation = (expectDockItem) ->
+waitForPackageActivation = ->
   waitsForPromise ->
     atom.packages.activatePackage('tree-view')
   waitsForPromise ->


Change text for tutorial slide 2

Change text for tutorial slide 2

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -30,7 +30,7 @@
       content: "Welcome to Wildlife Watch! In this project, we are asking you to identify animals found in camera trap photos from around Wisconsin. Your help allows us to better understand how animal populations across the state change through time."
     s2:
       title: "Identifying animals"
-      content: "You are presented with a set of photos on the left and a list of potential animals on the right. When you spot an animal within a photo, simply click the corresponding animal on the right. You will be asked to supply some followup info, such as the number of animals found in the images."
+      content: """You are presented with a set of photos on the left and a list of potential animals on the right. The photos often come as a sequence of 3, called a "capture". Check out other snapshots in the sequence using the buttons below the image, or play them like a flipbook by clicking the play button. When you spot an animal within a photo, simply click the corresponding animal on the right. You will be asked to supply some followup info, such as the number of animals found in the images."""
     s3:
       title: "Filtering by characteristics"
       content: "Occasionally it is difficult to determine which animal is in the photo. Just above the list of animals is a variety of methods to filter the animal list. Selecting different options reduces the number of choices, making your job easier."


re-word tagline

re-word tagline

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -12,7 +12,7 @@

   home:
     header:
-      title: 'Hunt for Mineral-Rich Asteroids!'
+      title: 'Hunt for Resource-Rich Asteroids!'
       content: '''
 Scientists are scanning our solar system for asteroids with the Catalina Sky Survey. They need your help to find asteroids for the exploration of their mineral properties!      '''
       start: 'Start Classifying'


Fix tests

Fix tests

--- app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee
+++ app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee
@@ -78,15 +78,13 @@

     _mockTgAppMetaService = () ->
         mocks.tgAppMetaService = {
-            setAll: sinon.stub()
+            setfn: sinon.stub()
         }

         provide.value "tgAppMetaService", mocks.tgAppMetaService

     _mockTranslate = () ->
-        mocks.translate = {
-            instant: sinon.stub()
-        }
+        mocks.translate = sinon.stub()

         provide.value "$translate", mocks.translate

@@ -113,18 +111,9 @@
         inject ($controller) ->
             controller = $controller

-    it "metada is set", (done) ->
-        mocks.translate.instant.withArgs("EPICS.PAGE_TITLE", {
-            projectName: "testing name"
-        }).returns("TITLE")
-        mocks.translate.instant.withArgs("EPICS.PAGE_DESCRIPTION", {
-            projectName: "testing name"
-            projectDescription: "testing description"
-        }).returns("DESCRIPTION")
-
+    it "metada is set", () ->
         ctrl = controller("EpicsDashboardCtrl")
-        expect(mocks.tgAppMetaService.setAll).have.been.calledWith("TITLE", "DESCRIPTION")
-        done()
+        expect(mocks.tgAppMetaService.setfn).have.been.called

     it "load data because epics panel is enabled and user has permissions", (done) ->
         ctrl = controller("EpicsDashboardCtrl")


linter pissed me of this time.

linter pissed me of this time.

--- workers/social/lib/social/models/computeproviders/helpers.coffee
+++ workers/social/lib/social/models/computeproviders/helpers.coffee
@@ -58,4 +58,5 @@
       callback err

-module.exports = { updateMachine, validateResizeByMachine, validateResizeByUserPlan }+module.exports = { updateMachine, validateResizeByMachine, validateResizeByUserPlan }
+


Update da.coffee (#5780)

Update da.coffee (#5780)

Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/da.coffee
+++ app/locale/da.coffee
@@ -1,19 +1,19 @@
 module.exports = nativeDescription: "dansk", englishDescription: "Danish", translation:

   new_home:
-#    title: "CodeCombat - Coding games to learn Python and JavaScript"
+    title: "CodeCombat - Programmér spil mens du lærer Python og JavaScript"
 #    meta_keywords: "CodeCombat, python, javascript, Coding Games"
-#    meta_description: "Learn typed code through a programming game. Learn Python, JavaScript, and HTML as you solve puzzles and learn to make your own coding games and websites."
+    meta_description: "Lær at skrive kode gennem et programmeringsspil. Lær Python, JavaScript og HTML mens du løser gåder og lær at lave dine egne spil og websider."
 #    meta_og_url: "https://codecombat.com"
 #    become_investor: "to become an investor in CodeCombat"
-#    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
-#    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
-#    built_for_teachers_subtitle1: "Computer Science"
-#    built_for_teachers_subblurb1: "Starting with our free Introduction to Computer Science course, students master core coding concepts such as while/for loops, functions, and algorithms."
-#    built_for_teachers_subtitle2: "Game Development"
-#    built_for_teachers_subblurb2: "Learners construct mazes and use basic input handling to code their own games that can be shared with friends and family."
-#    built_for_teachers_subtitle3: "Web Development"
-#    built_for_teachers_subblurb3: "Using HTML, CSS, and jQuery, learners flex their creative muscles to program their own webpages with a custom URL to share with their classmates."
+    built_for_teachers_title: "Et programmeringsspil henvendt til lærere"
+    built_for_teachers_blurb: "At undervise børn i at programmere, kan nogle gange føles overvældende. CodeCombat hjælper alle lærere med at undervise elever i programmering af enten JavaScript eller Python - to af de mest populære programmeringssprog. Med udgangspunkt i et omfattende pensum, som inkluderer seks datalogi-moduler og som understøtter læring gennem projektbaseret spiludvikling og webudvikling, vil børnene komme på en rejse gående fra grundlæggende syntaks mod rekursion!"
+    built_for_teachers_subtitle1: "Datalogi"
+    built_for_teachers_subblurb1: "I vores gratis introduktionskursus lærer eleverne at mestre basale programmeringsprincipper som while/for løkker, funktioner og algoritmer."
+    built_for_teachers_subtitle2: "Spiludvikling"
+    built_for_teachers_subblurb2: "Eleverne lærer at bygge labyrinter og anvender grundlæggende input til at programmere deres egne spil, som kan deles med venner og familie."
+    built_for_teachers_subtitle3: "Webudvikling"
+    built_for_teachers_subblurb3: "Eleverne bliver udfordret på den kreative sans ved at anvende HTML, CSS og jQuery til at opbygge deres egne websider med en brugerdefineret URL, som kan deles med deres klassekammerater."
 #    century_skills_title: "21st Century Skills"
 #    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
@@ -153,11 +153,11 @@
     run_class: "Alt hvad du behøver for at køre en datalogiklasse på din skole i dag - uden at kræve en datalogibaggrund."
     goto_classes: "Gå til mine Klasser" # {change}
     view_profile: "Vis Min Profil"
-#    view_progress: "View Progress"
+    view_progress: "Se Fremskridt"
 #    go_to_courses: "Go to My Courses"
     want_coco: "Ønsker du CodeCombat på din skole?"
 #    educator: "Educator"
-#    student: "Student"
+    student: "Elev"
 #    our_coding_programs: "Our Coding Programs"
 #    codecombat: "CodeCombat"
 #    ozaria: "Ozaria"
@@ -168,7 +168,7 @@

   nav:
 #    educators: "Educators"
-#    follow_us: "Follow Us"
+    follow_us: "Følg os"
 #    general: "General"
 #    map: "Map"
     play: "Spil" # The top nav bar entry where players choose which levels to play
@@ -189,7 +189,7 @@
     twitter_follow: "Følg"
 #    my_classrooms: "My Classes"
 #    my_courses: "My Courses"
-#    my_teachers: "My Teachers"
+    my_teachers: "Mine Lærere"
     careers: "Karrierer"
     facebook: "Facebook"
     twitter: "Twitter"
@@ -215,6 +215,7 @@
     close: "Luk"
     okay: "Okay"
 #    cancel: "Cancel"
+    cancel: "Annullér"

   not_found:
     page_not_found: "Siden blev ikke fundet"
@@ -335,7 +336,7 @@
   share_progress_modal:
     blurb: "Du gør godt fremskridt! Fortæl din forælder hvor meget du har lært med CodeCombat."
     email_invalid: "Ugyldig emailaddresse."
-    form_blurb: "Indtast din forælders email nedenfor, så viser vi dem det!"
+    form_blurb: "Indtast din forælders e-mail nedenfor, så viser vi dem det!"
     form_label: "Email"
     placeholder: "emailaddresse"
     title: "Fremragende arbejde, Lærling"
@@ -355,8 +356,8 @@

   signup:
 #    complete_subscription: "Complete Subscription"
-#    create_student_header: "Create Student Account"
-#    create_teacher_header: "Create Teacher Account"
+    create_student_header: "Opret elev konto"
+    create_teacher_header: "Opret lærer konto"
 #    create_individual_header: "Create Individual Account"
     email_announcements: "Modtag nyheder på email" # {change}
 #    sign_in_to_continue: "Sign in or create an account to continue"
@@ -373,17 +374,17 @@
     connected_facebook_header: "Du har registreret dig med  Facebook!"
     connected_facebook_p: "Afslut registreringen, så du kan logge ind med din Facebook-konto."
     hey_students: "Elever, indtast klassekoden udleveret af din lærer."
-#    birthday: "Birthday"
+    birthday: "Fødselsdag"
 #    parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
 #    classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
 #    checking: "Checking..."
 #    account_exists: "This email is already in use:"
 #    sign_in: "Sign in"
 #    email_good: "Email looks good!"
-#    name_taken: "Username already taken! Try {{suggestedName}}?"
+    name_taken: "Brugernavnet er allerede registreret! Prøv {{suggestedName}}?"
 #    name_available: "Username available!"
-#    name_is_email: "Username may not be an email"
-#    choose_type: "Choose your account type:"
+    name_is_email: "Brugernavnet må ikke være en e-mail"
+    choose_type: "Vælg din kontotype:"
 #    teacher_type_1: "Teach programming using CodeCombat!"
 #    teacher_type_2: "Set up your class"
 #    teacher_type_3: "Access Course Guides"