More nodemon logging

More nodemon logging

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -50,6 +50,9 @@
                 return
               ), 1000
               return
+            nodemon.on 'log', (event) ->
+              console.log event.colour
+              return
     watch:
       options:
         livereload: true


Minor updates in Gruntfile

Minor updates in Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -9,7 +9,11 @@
       rest:
         options:
           livereload: true
-        files: ['main/**/*.py', 'main/**/*.js', 'main/**/*.html']
+        files: [
+            'main/**/*.py'
+            'main/**/*.js'
+            'main/**/*.html'
+          ]

     grunt.loadNpmTasks('grunt-contrib-watch')
     grunt.registerTask('default', ['watch'])


Finally got multiple source and destinations directories working for 'coffee' task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -7,11 +7,10 @@
     # Coffeescript compilation
     coffee:
       compile:
-        expand: true
-        cwd: 'src/'
-        src: ['**/*.coffee']
-        dest: 'lib/'
-        ext: '.js'
+        files: [
+          {expand: true, cwd: 'src', src: '*.coffee', dest: 'lib', ext: '.js'}
+          {expand: true, cwd: 'demo/src', src: '*.coffee', dest: 'demo/lib', ext: '.js'}
+        ]
       options:
         bare: true


Output the file being run

Output the file being run

--- config/karma/run.coffee
+++ config/karma/run.coffee
@@ -19,6 +19,7 @@
     for test in tests
       do (test) -> queue.defer (callback) ->
         gutil.log "RUNNING TESTS: #{test.name}"
+        gutil.log "#{JSON.stringify test.files}"
         # karma.start _.defaults({client: {mocha: {grep: '@no_cache'}}, files: test.files}, BASE_CONFIG), (return_value) -> callback(new Error "Tests failed: #{return_value}" if return_value)
         karma.start _.defaults({client: {mocha: {grep: ''}}, files: test.files}, BASE_CONFIG), (return_value) -> callback(new Error "Tests failed: #{return_value}" if return_value)


config: turn on logging to external

config: turn on logging to external

--- config/main.prod.coffee
+++ config/main.prod.coffee
@@ -140,7 +140,7 @@
   KONFIG.client.runtimeOptions =
     kites             : require './kites.coffee'           # browser passes this version information to kontrol , so it connects to correct version of the kite.
     algolia           : algolia
-    logToExternal     : no                                 # rollbar , mixpanel etc.
+    logToExternal     : yes                                # rollbar , mixpanel etc.
     suppressLogs      : no
     logToInternal     : no                                 # log worker
     authExchange      : "auth"


use var options

use var options

--- BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
@@ -3,13 +3,10 @@
     'click #none': 'clickAdd'

   initialize: (options) ->
-    @elements = options.elements
-    @displayedElements = options.displayedElements
-    @order = [ 0, 'asc' ]
-    @order = options.order if options.order != undefined
-    @title = options.title
-    @entityType = options.entityType
-    @addUrl = appRouter.generateUrl('addEntity', entityType: @entityType)
+    @options = options
+    @options.order = [ 0, 'asc' ]
+    @options.order = options.order if options.order != undefined
+    @options.addUrl = appRouter.generateUrl('addEntity', entityType: @options.entityType)
     key = 'click a.ajax-add-' + @cid
     @events[key] = 'clickAdd'
     _.bindAll this, "render"
@@ -22,29 +19,30 @@

   render: ->
     $(@el).html @renderTemplate('tableviewCollectionView',
-      displayedElements: @displayedElements
-      links: @elements.get('links')
+      displayedElements: @options.displayedElements
+      links: @options.elements.get('links')
       cid: @cid
     )
-    $('.js-widget-title', @$el).text @title
-    for element of @elements.get(@elements.get('collection_name'))
-      @addElementToView (@elements.get(@elements.get('collection_name'))[element])
+    $('.js-widget-title', @$el).text @options.title
+    for element of @options.elements.get(@options.elements.get('collection_name'))
+      @addElementToView (@options.elements.get(@options.elements.get('collection_name'))[element])
     $('#tableviewCollectionTable').dataTable(
       searching: false
       ordering: true
-      order: [@order]
+      order: [@options.order]
       lengthChange: false
     )
     return

   addElementToView: (elementData) ->
+    options = @options
     elementModel = new TableviewModel
     elementModel.set elementData
     view = new TableviewView(
       element: elementModel
-      displayedElements: @displayedElements
-      title: @title
-      entityType: @entityType
+      displayedElements: options.displayedElements
+      title: options.title
+      entityType: options.entityType
       el : this.$el.find('tbody')
     )
     return
@@ -53,18 +51,16 @@
     event.preventDefault()
     displayLoader('div[role="container"]')
     Backbone.history.navigate(@addUrl)
-    title = @title
-    entityType = @entityType
-    element = @elements
+    options = @options
     $.ajax
-      url: @elements.get('links')._self_add
+      url: options.elements.get('links')._self_add
       method: 'GET'
       success: (response) ->
         view = new FullPageFormView(
           html: response
-          title: title
-          entityType: entityType
-          element: element
+          title: options.title
+          entityType: options.entityType
+          element: options.element
           triggers: [
             {
               event: "focusout input.generate-id-source"


Starts work on saving artwork issue

Starts work on saving artwork issue

--- api/apps/articles/model/save.coffee
+++ api/apps/articles/model/save.coffee
@@ -127,10 +127,12 @@
   if input.sections?.length > 0
     article.sections = input.sections
   return cb null, article unless input.sections
+  console.log 'i have sections'
   emptyArtworks = _.filter input.sections, (section) ->
     section.type is 'artworks' and section.artworks.length is 0
   if emptyArtworks.length is 0 and inputOrder is articleOrder
     return cb(null, article)
+  console.log 'here gonna fetch'
   # Try to fetch and denormalize the artworks from Gravity asynchonously
   artworkIds = _.pluck (_.where input.sections, type: 'artworks' ), 'ids'
   Q.allSettled( for artworkId in _.flatten artworkIds
@@ -139,12 +141,16 @@
       .set('X-Xapp-Token': artsyXapp)
   ).done (responses) =>
     fetchedArtworks = _.map responses, (res) ->
+      console.log res
       res.value?.body
+    console.log 'fetchedArtworks'
+    console.log fetchedArtworks
     newSections = []
     for section in cloneDeep input.sections
       if section.type is 'artworks'
         section.artworks = []
         for artworkId in section.ids
+          console.log artworkId
           artwork = _.findWhere fetchedArtworks, _id: artworkId
           if artwork
             section.artworks.push denormalizedArtworkData artwork


string_extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -35,8 +35,7 @@
 @name constantize
 @methodOf String#

-@returns The class or constant named in this string.
-@type Object
+@returns {Object} The class or constant named in this string.
 ###
 String::constantize = ->
   if this.match /[A-Z][A-Za-z0-9]*/


blob dump -- add a little more documentation and repeat_until_done option

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -2601,10 +2601,16 @@
     # Backup limit blobs that previously haven't been dumped to blobs, and put them in
     # a tarball in the given path.  The tarball's name is the time when the backup starts.
     # The tarball is compressed using gzip compression.
+    #    db.backup_blobs_to_tarball(limit:10000,path:'/backup/tmp/blobs',repeat_until_done:true, cb:done())
+    # I have not written code to restore from these tarballs.  Assuming the database has been restore,
+    # so there is an entry in the blobs table for each blob, it would suffice to upload the tarballs,
+    # then copy their contents straight into the BLOB_GCLOUD_BUCKET gcloud bucket, and that’s it.
+    # If we didn't have the blobs table in the db, make dummy entries from the blob names in the tarballs.
     backup_blobs_to_tarball: (opts) =>
         opts = defaults opts,
             limit : 10000     # number of blobs to backup
             path  : required  # path where [timestamp].tar file is placed
+            repeat_until_done : false  # if true, keeps re-call'ing this function until no more results to backup
             cb    : undefined # cb(err, '[timestamp].tar')
         dbg     = @dbg("backup_blobs_to_tarball(limit=#{opts.limit},path='#{opts.path}')")
         join    = require('path').join
@@ -2659,8 +2665,15 @@
                 dbg("backup succeeded completely -- mark all blobs as backed up")
                 @table('blobs').getAll((x.id for x in v)...).update(backup:true).run(cb)
         ], (err) =>
-            dbg("done -- #{err}")
-            opts.cb?(err, if not err then tarball)
+            if err
+                dbg("ERROR: #{err}")
+                opts.cb?(err)
+            else
+                dbg("done")
+                if opts.repeat_until_done and to_remove.length > 0
+                    @backup_blobs_to_tarball(opts)
+                else
+                    opts.cb?(undefined, tarball)
         )

     # Copied all blobs that will never expire to a google cloud storage bucket.


Removed legacy DASHBOARD_TOKEN requirement.

--- prviewer.coffee
+++ prviewer.coffee
@@ -195,9 +195,7 @@
     res.redirect "https://#{req.headers["host"]}#{req.url}"
     return

-  if req.param('token') == requireEnv 'DASHBOARD_TOKEN'
-    return next()
-  else if req.isAuthenticated()
+  if req.isAuthenticated()
     return next()
   else
     res.redirect '/auth/github'


tweak a param to make clients behavea  bit more nicely (and hopefully not mess speed up too much -- may change back).

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -23,7 +23,7 @@

 # Maximum number of outstanding concurrent messages (that have responses)
 # to send at once to the backend.
-MAX_CONCURRENT = 100
+MAX_CONCURRENT = 25

 {EventEmitter} = require('events')

@@ -207,7 +207,7 @@
         # (node) warning: possible EventEmitter memory leak detected. 301 listeners added. Use emitter.setMaxListeners() to increase limit.
         @setMaxListeners(3000)  # every open file/table/sync db listens for connect event, which adds up.

-        @_emit_mesg_info = underscore.throttle(@_emit_mesg_info, 1500)
+        @_emit_mesg_info = underscore.throttle(@_emit_mesg_info, 750)

         @emit("connecting")
         @_call             =
@@ -689,6 +689,7 @@
     _update_calls: =>
         while @_call.queue.length > 0 and @_call.count < MAX_CONCURRENT
             @_process_next_call()
+        #console.log("_update_calls: ", @_call)

     _emit_mesg_info: =>
         info = misc.copy_without(@_call, ['queue'])


Refactoring gulpfile

Refactoring gulpfile


Proper async

Proper async

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -56,22 +56,26 @@
     stream
       .pipe(header(HEADER, {file_name: file_name}))
       .pipe(gulp.dest(library.destination))
-      .on 'end', -> callback?()
+      .on 'end', callback

-  helper(cachedBuild(library), library.modules.file_name, callback)
-  helper(cachedStackBuild(library), library.stack_file_name) if library.stack_file_name
+  queue = new Queue(1)
+  queue.defer (callback) -> helper(cachedBuild(library), library.modules.file_name, callback)
+  queue.defer (callback) -> helper(cachedStackBuild(library), library.stack_file_name, callback) if library.stack_file_name
+  queue.await (err) -> callback?(err)

 minifyLibrary = (library, callback) ->
-  helper = (stream, file_name) ->
+  helper = (stream, file_name, callback) ->
     stream
       .pipe(uglify())
       .pipe(rename({suffix: '.min'}))
       .pipe(header(HEADER, {file_name: file_name}))
       .pipe(gulp.dest(library.destination))
-      .on 'end', -> callback?()
+      .on 'end', callback

-  helper(cachedBuild(library), library.modules.file_name)
-  helper(cachedStackBuild(library), library.stack_file_name) if library.stack_file_name
+  queue = new Queue(1)
+  queue.defer (callback) -> helper(cachedBuild(library), library.modules.file_name, callback)
+  queue.defer (callback) -> helper(cachedStackBuild(library), library.stack_file_name, callback) if library.stack_file_name
+  queue.await (err) -> callback?(err)

 gulp.task 'build', -> LIBRARIES.map buildLibrary
 gulp.task 'watch', ['build'], -> LIBRARIES.map (library) -> gulp.watch library.paths, -> buildLibrary(library)


Sidebar: Remove navigationitem.coffee from includes.

--- client/Main/includes.coffee
+++ client/Main/includes.coffee
@@ -185,7 +185,6 @@

   "navigation/navigationlink.coffee",
   "navigation/navigationworkspaceitem.coffee",
-  "navigation/navigationitem.coffee",
   "navigation/navigationmachineitem.coffee",

   "domains/managedomainsview.coffee",


client: export createTestMachine to global on dev envs.

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/index.coffee
+++ client/app/lib/index.coffee
@@ -41,6 +41,7 @@
   if globals.config.environment in ['dev', 'default', 'sandbox']
     global._kd      = kd
     global._remote  = remote
+    global._createTestMachine = require 'app/util/createTestMachine'

   if globals.currentGroup?
     globals.config.entryPoint.slug = globals.currentGroup.slug


Add capture

Add capture

--- client/data/cardData.coffee
+++ client/data/cardData.coffee
@@ -1,4 +1,12 @@
 module.exports = [
+#   id: 'capture'
+#   title: 'Capture'
+#   img: 'svg'
+#   description: 'Capture is an iOS app that allows you to generate websites from hand-drawn mocks.'
+#   url:
+#     demo: 'http://treehackswinter2015.challengepost.com/submissions/33360-capture'
+#     github: 'https://github.com/grant/capture'
+# ,
   id: 'snappo'
   title: 'Snappo'
   img: 'png'


FSItem: use getKite which supports both old and new kites

--- client/ide/fs/fsitem.coffee
+++ client/ide/fs/fsitem.coffee
@@ -8,8 +8,7 @@

   @create:({ path, type, vmName, treeController }, callback)->

-    kite = KD.getSingleton('vmController').getKiteByVmName vmName
-
+    kite = @getKite()
     kite.vmOn().then ->

       plainPath = FSHelper.plainPath path


Remove workspace snapshot method

Remove workspace snapshot method

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -688,6 +688,11 @@
     @localStorageController.setValue name, value

+  removeWorkspaceSnapshot: ->
+
+    @localStorageController.unsetKey @getWorkspaceSnapshotName()
+
+
   getWorkspaceSnapshotName: ->

     return "wss.#{@mountedMachine.uid}.#{@workspaceData.slug}"


simplify linking from visualization

simplify linking from visualization

--- client/plugins/radar.coffee
+++ client/plugins/radar.coffee
@@ -139,9 +139,8 @@
           .style("stroke", ruleColor)
           .style("cursor", 'pointer')
           .attr("transform", "rotate(180)")
-          .on("click", (d) ->
-            $(div).parents('.page').nextAll().remove()
-            wiki.doInternalLink(keys[d])
+          .on("click", (d,i) ->
+            wiki.doInternalLink keys[i], $(div).parents('.page')
           )

         fill = d3.scale.category10()


Fix incorrectly nested clientPermissions user schema property type

--- app/schemas/models/user.ozar.coffee
+++ app/schemas/models/user.ozar.coffee
@@ -97,7 +97,7 @@
       type: 'object'
       additionalProperties: false
       properties:
-        client: {type: c.objectId({description: 'APIClient with permissions on this user'})}
+        client: c.objectId({description: 'APIClient with permissions on this user'})
         access: {type: 'string', 'enum': ['read', 'grant', 'write', 'owner']}  # 'grant' permissions allow APIClients to grant licenses to a user
     format: 'hidden'


mpegts: Fix bug in read_pes_packet()

mpegts: Fix bug in read_pes_packet()

--- mpegts.coffee
+++ mpegts.coffee
@@ -653,16 +653,16 @@
           info.field_id = bits.read_bits 2
           info.intra_slice_refresh = bits.read_bit()
           info.frequency_truncation = bits.read_bits 2
-        else if trick_mode_control is TRICK_MODE_CONTROL_SLOW_MOTION
+        else if info.trick_mode_control is TRICK_MODE_CONTROL_SLOW_MOTION
           info.rep_cntrl = bits.read_bits 5
-        else if trick_mode_control is TRICK_MODE_CONTROL_FREEZE_FRAME
+        else if info.trick_mode_control is TRICK_MODE_CONTROL_FREEZE_FRAME
           info.field_id = bits.read_bits 2
           reserved = bits.read_bits 3
-        else if trick_mode_control is TRICK_MODE_CONTROL_FAST_REVERSE
+        else if info.trick_mode_control is TRICK_MODE_CONTROL_FAST_REVERSE
           info.field_id = bits.read_bits 2
           info.intra_slice_refresh = bits.read_bit()
           info.frequency_truncation = bits.read_bits 2
-        else if trick_mode_control is TRICK_MODE_CONTROL_SLOW_REVERSE
+        else if info.trick_mode_control is TRICK_MODE_CONTROL_SLOW_REVERSE
           info.rep_cntrl = bits.read_bits 5
         else
           reserved = bits.read_bits 5


tooltip/tag for edited content in embeds, now uses embed caching for the view as well, multiple embeds can be displayed, hover on links in status update body will show the embed for that. link shortening in body with tooltips

--- core/utils.coffee
+++ core/utils.coffee
@@ -172,14 +172,18 @@
   expandUrls: (text) ->
     return null unless text
     text.replace /([A-Za-z]+:\/\/)?[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&#\+\?\/.=]+/g, (url) ->
-
-      visibleUrl = url
+      originalUrl = url
+      visibleUrl = url.replace(/(ht|f)tp(s)?\:\/\//,"").replace(/\/.*/,"")

       if not /[A-Za-z]+:\/\//.test url
         # url has no protocol
         url = '//'+url

-      "#{visibleUrl}"
+      "#{visibleUrl}"
+      # new KDView
+      #   partial :   "#{visibleUrl}"
+      #   tooltip :
+      #     title : url

   putShowMore: (text, l = 500)->
     shortenedText = __utils.shortenText text,


utils: make throttle and debounce() backwards compatibile

--- core/utils.coffee
+++ core/utils.coffee
@@ -488,6 +488,10 @@
   ###

   throttle : (wait, func)->
+    # for backwards compatibility
+    if (typeof func) is "number"
+      [wait, func] = [func, wait]
+
     context = args = timeout = throttling = more = null
     whenDone = KD.utils.debounce wait, -> more = throttling = false
     ->
@@ -506,6 +510,9 @@
       throttling = yes

   debounce : (wait, func)->
+    if (typeof func) is "number"
+      [wait, func] = [func, wait]
+
     timeout   = null
     ->
       context = this


Formatting / whitespace

Formatting / whitespace

--- examples/listnr-examples.coffee
+++ examples/listnr-examples.coffee
@@ -18,6 +18,7 @@

 listnr = new Listnr()

+
 setContext = (ctx) ->
   listnr.activate(ctx)
   document
@@ -30,15 +31,18 @@
     .getElementById('help')
     .innerHTML = JSON.stringify(listnr.help(), null, 2)

+
 matchingHandler = (combo) ->
   document
     .getElementById('action')
     .innerHTML = "Has mapping for '#{combo}'"

+
 defaultHandler = (combo) ->
   document
     .getElementById('action')
     .innerHTML = "No mapping for '#{combo}'"
+

 listnr
   .map('a', 'Mapping for "a"', matchingHandler)
@@ -50,4 +54,5 @@
   .map('d', 'Switch to default context', -> setContext('default'))
   .default(defaultHandler)

+
 setContext('default')


util.test: throwsExceptionWithString parameter validation

--- coffee/src/util/test.coffee
+++ coffee/src/util/test.coffee
@@ -23,7 +23,13 @@
 # Returns false otherwise.
 test.throwsExceptionWithString = (str, fn, args) ->
   success = false
-  args ?= []
+
+  if args?
+    unless args instanceof Array
+      args = [ args ]
+  else
+    args = []
+
   try fn args... catch error
     success = error.message.search(str) >= 0


storagePath - Always is function

storagePath - Always is function

--- files.coffee
+++ files.coffee
@@ -590,14 +590,14 @@
         headers['Accept-Ranges'] = 'bytes'
         return headers

-      if @public and (not storagePath or not _.isString(storagePath))
+      if @public and not storagePath
         throw new Meteor.Error 500, "[FilesCollection.#{@collectionName}] \"storagePath\" must be set on \"public\" collections! Note: \"storagePath\" must be equal on be inside of your web/proxy-server (absolute) root."

       console.info('[FilesCollection.storagePath] Set to:', @storagePath({})) if @debug

       fs.mkdirs @storagePath({}), {mode: @parentDirPermissions}, (error) ->
         if error
-          throw new Meteor.Error 401, "[FilesCollection.#{self.collectionName}] Path \"#{self.storagePath}\" is not writable!", error
+          throw new Meteor.Error 401, "[FilesCollection.#{self.collectionName}] Path \"#{self.storagePath({})}\" is not writable!", error
         return

       check @strict, Boolean


Minor code enhancements

Minor code enhancements

--- files.coffee
+++ files.coffee
@@ -803,7 +803,8 @@
     opts.path  = "#{@storagePath}/#{FSName}#{extensionWithDot}"

     request.get(url).on('error', (error)-> bound ->
-      throw new Meteor.Error 500, "Error on [load(#{url})]:" + JSON.stringify error
+      callback and callback error
+      console.error "[FilesCollection] [load] [request.get(#{url})]:", error if self.debug
     ).on('response', (response) -> bound ->

       console.info "[FilesCollection] [load] Received: #{url}" if self.debug
@@ -821,7 +822,7 @@
       self.collection.insert _.clone(result), (error) ->
         if error
           callback and callback error
-          console.warn "[FilesCollection] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
+          console.error "[FilesCollection] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
         else
           callback and callback null, result
           console.info "[FilesCollection] [load] [insert] #{fileName} -> #{self.collectionName}" if self.debug
@@ -1384,31 +1385,29 @@
   @memberOf FilesCollection
   @name remove
   @param {String|Object} search - `_id` of the file or `Object` like, {prop:'val'}
-  @param {Function} cb - Callback with one `error` argument
+  @param {Function} callback - Callback with one `error` argument
   @summary Remove file(s) on cursor or find and remove file(s) if search is set
   @returns {FilesCollection} Instance
   ###
-  remove: (search, cb) ->
+  remove: (search, callback) ->
     console.info "[FilesCollection] [remove(#{JSON.stringify(search)})]" if @debug
     check search, Match.Optional Match.OneOf Object, String
-    check cb, Match.Optional Function
+    check callback, Match.Optional Function

     @srch search
     if Meteor.isClient
       if @allowClientCode
-        Meteor.call @methodNames.MeteorFileUnlink, search, (if cb then cb else NOOP)
+        Meteor.call @methodNames.MeteorFileUnlink, search, (callback or NOOP)
       else
-        if cb
-          cb new Meteor.Error 401, '[FilesCollection] [remove] Run code from client is not allowed!'
-        else
-          throw new Meteor.Error 401, '[FilesCollection] [remove] Run code from client is not allowed!'
+        callback and callback new Meteor.Error 401, '[FilesCollection] [remove] Run code from client is not allowed!'
+        console.warn '[FilesCollection] [remove] Run code from client is not allowed!' if @debug

     if Meteor.isServer
       files = @collection.find @search
       if files.count() > 0
         self = @
         files.forEach (file) -> self.unlink file
-      @collection.remove @search, cb
+      @collection.remove @search, (callback or NOOP)
     return @

   ###


Show "deploy" in "Started by" so we don't think there's no reason

--- js/inner.coffee
+++ js/inner.coffee
@@ -126,6 +126,12 @@
             "#{@user()} on CircleCI.com"
           else
             "CircleCI.com"
+        else
+          if @job_name() == "deploy"
+            "deploy"
+          else
+            "unknown"
+

   description: (include_project) =>
     return unless @build_num?


Fixing typos

Fixing typos

--- static/js/maproulette.coffee
+++ static/js/maproulette.coffee
@@ -1,4 +1,4 @@
-11###
+###
 # This file contains all the MapRoulette client/javascript code
 ###
 root = exports ? this
@@ -270,7 +270,7 @@
   # location (if supplied)
   ###
   if not currentChallenge? or currentChallenge.slug != challenge
-    updateChallengechallenge)
+    updateChallengechallenge()
   $.getJSON "c/#{challenge}/task", (data) ->
     currentTask = data
     currentTask.startTime = new Date.getTime()


██ ██████ ███ :black_large_square:

██ ██████ ███ :black_large_square:

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -100,6 +100,9 @@

   # Emails 'R' Secret
   text = text.replace(/([^@\s]+@)[^@\s]+\.[^@\s]+/, "$1[redacted]")
+
+  # Let users redact shit too
+  text = text.replace(/\[^\]]\]/, "[redacted]")

   # Twitter length, then truncate with `...`
   limit = 140


Return an avatar look up dictionary directly

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -155,9 +155,10 @@
   buildPanel lines[4..5], (err, panel) -> finishPanel(err, 2, panel)

 # Load avatars for `names` and invoke `cb` as:
-# `cb(err, [{{name: n, img: GD-image, err: null}},... ])`
-# The `img` member will be a GD image loader from disk
-# that can be used to represent the `name`.
+# `cb(err, {"Nickname": avatarImg, ...})`
+# mapping each name to an avatar image that
+# can be used to represent it.
+#
 # `err` is only set on failure
 loadAvatars = (names, cb) ->
   # Prepare requirements
@@ -180,8 +181,13 @@
         return fail(err) if err
         nameObj.img = img unless err

-        # Are we done?
-        cb(null, names) if names.every((o) -> o.img)
+        # Are we done? Then build up a dictionary
+        # and tell the caller.
+        if names.every((o) -> o.img)
+          avatars = {}
+          for obj in names
+            avatars[obj.name] = obj.img
+          cb(null, avatars)

 # Build a single panel out of (UP TO) two lines of dialog
 # cb invoked as `cb(err, image)`. `err` is only set on failure
@@ -201,13 +207,8 @@
   names = (l[0] for l in lines).
     filter((v, i, a) -> a.indexOf(v) == i) # De-dupe

-  loadAvatars names, (err, namesList) ->
+  loadAvatars names, (err, avatars) ->
     return cb(err, null) if err
-
-    # Dictionary of name -> img
-    avatars = {}
-    for obj in namesList
-      avatars[obj.name] = obj.img

     if names.length == 1
       # The only person speaking is centered in the frame


Send graph icon and description

Send graph icon and description

--- components/SendGraph.coffee
+++ components/SendGraph.coffee
@@ -13,6 +13,8 @@
     id: graphId
     name: graph.name
     library: graph.properties.project
+    icon: graph.properties.icon or ''
+    description: graph.properties.description or ''
   for node in graph.nodes
     runtime.sendGraph 'addnode',
       id: node.id


JCredential: fetchData: make shadowing sensitive fields optional for backend calls

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -337,7 +337,7 @@
     return "*#{Array(r c).join '*'}#{c[(r c)..]}"

-  fetchData: (callback) ->
+  fetchData: (callback, shadowSensitiveData = yes) ->

     sensitiveKeys = PROVIDERS[@provider]?.sensitiveKeys or []

@@ -349,9 +349,10 @@
       rel.fetchTarget (err, data) ->
         return callback err  if err

-        meta = data?.data?.meta or {}
-        sensitiveKeys.forEach (key) ->
-          meta[key] = shadowed meta[key]
+        if shadowSensitiveData
+          meta = data?.data?.meta or {}
+          sensitiveKeys.forEach (key) ->
+            meta[key] = shadowed meta[key]

         callback null, data


changed sync to allow arbitrary user defined headers


build better

build better


model_postgres: writeItem should also INSERT existing updated value

that prevented sync


Visualizer hovering works when zoomed

Visualizer hovering works when zoomed

--- src/visualizer/Grid.coffee
+++ src/visualizer/Grid.coffee
@@ -99,8 +99,8 @@

 # throttle this
 mouseMove = (e) ->
-  @mouseState.x = e.layerX
-  @mouseState.y = e.layerY
+  @mouseState.x = e.offsetX ? e.layerX
+  @mouseState.y = e.offsetY ? e.layerY
   newRegion = @_getRegion @mouseState.x, @mouseState.y
   if newRegion != @currentRegion
     @currentRegion = newRegion


Small tidy up.

Small tidy up.

--- js/flight.coffee
+++ js/flight.coffee
@@ -26,17 +26,14 @@
         @speed = options.speed

     @currentPanel = document.getElementsByClassName('visible')[0]
-
     if @currentPanel is undefined then throw new Error "Current panel not set"

     @fitHeightToContent()
     @detectUA()

-    if @os.android and @os.version <= "2.1"
-      @transitionAnimation = false
-
-    if @hideUrlBar is true
-      @hideUrlBar()
+    if @os.android and @os.version <= "2.1" then @transitionAnimation = false
+
+    if @hideUrlBar is true then @hideUrlBar()

   # Goes to page with or without transtion
   goToPage: (options) =>


add comments

add comments

--- lib/assets/javascripts/nali/model.js.coffee
+++ lib/assets/javascripts/nali/model.js.coffee
@@ -22,29 +22,36 @@
     @

   notice: ( params ) ->
+    # добавляет уведомление в очередь на выполнение, запускает выполнение очереди
     @noticesWait.push params
     @runNotices()
     @

   runNotices: ->
+    # запускает выполнение уведомлений на существующих моделях
     for item, index in @noticesWait[ 0.. ]
       if model = @extensions[ item.model ].find item.id
         model[ item.notice ] item.params
         @noticesWait.splice @noticesWait.indexOf( item ), 1
     @
+
+  # работа с моделями
+
+  accessing: ->
+    # устанавливает геттеры доступа к атрибутам и связям
+    @access @attributes
+    @setRelations()
+    @

   force: ( params = {} ) ->
+    # создает новую модель с заданными атрибутами
     attributes = @default_attributes()
     attributes[ name ] = value for name, value of params
     attributes[ name ] = @normalizeValue value for name, value of attributes
     @clone( attributes: attributes ).accessing()
-
-  accessing: ->
-    @access @attributes
-    @setRelations()
-    @
-
+
   save: ( success, failure ) ->
+    # отправляет на сервер запрос на сохранение модели, вызывает success в случае успеха и failure при неудаче
     if @isValid()
       @query "#{ @sysname.lowercase() }s.save", @attributes,
         ( { attributes, created, updated } ) =>
@@ -54,6 +61,7 @@
     @

   sync: ( { sysname, attributes, created, updated, destroyed } ) ->
+    # синхронизирует пришедшую с сервера модель с локальной, либо создает новую
     if model = @extensions[ sysname ].find attributes.id
       if destroyed then model.remove()
       else model.update attributes, updated, created
@@ -65,12 +73,11 @@
     @

   select: ( filters, success, failure ) ->
+    # отправляет на сервер запрос на выборку моделей по фильтру, вызывает success в случае успеха и failure при неудаче
     @query @sysname.lowercase() + 's.select', filters, success, failure if Object.keys( filters ).length
-
-  destroy: ( success, failure ) ->
-    @query @sysname.lowercase() + 's.destroy', @attributes, success, failure
-
+
   write: ->
+    # добавляет модель во временную таблицу, генерирует событие create
     unless @ in @table
       @table.push @
       @table.index[ @id ] = @
@@ -80,6 +87,7 @@
     @

   remove: ->
+    # удаляет модель из временной таблицы, генерирует событие destroy
     if @ in @table
       delete @table.index[ @id ]
       @table.splice @table.indexOf( @ ), 1
@@ -89,12 +97,15 @@
     @

   build: ( attributes ) ->
+    # создает модель, не сохраняя её на сервере
     @force attributes

   create: ( attributes, success, failure ) ->
+    # создает модель, и сохраняет её на сервере, вызывает success в случае успеха и failure при неудаче
     @build( attributes ).save success, failure

   update: ( attributes, updated = 0, created = 0 ) ->
+    # обновляет атрибуты модели, проверяя их валидность, генерирует событие update
     if not updated or updated > @updated
       @created = created if created
       changed = []
@@ -106,6 +117,7 @@
     @

   update_attribute: ( name, value ) ->
+    # обновляет один атрибут модели, проверяя его валидность, генерирует событие update.{ propertyName }
     value = @normalizeValue value
     if @attributes[ name ] isnt value and @isValidAttributeValue( name, value )
       @attributes[ name ] = value
@@ -113,11 +125,20 @@
       @trigger "update.#{ name }", @
       true
     else false
-
+
+  destroy: ( success, failure ) ->
+    # отправляет на сервер запрос на удаление модели, вызывает success в случае успеха и failure при неудаче
+    @query @sysname.lowercase() + 's.destroy', @attributes, success, failure
+
+  # поиск моделей
+
   find: ( id ) ->
+    # находит модель по её id используя индекс
     @table.index[ id ]

   where: ( filters ) ->
+    # находит все модели соответствующие фильтру, также отправляет запрос с фильтром на сервер,
+    # возвращает коллекцию моделей, модели найденные на сервере также попадут в эту коллекцию
     collection = @Collection.clone model: @, filters: filters
     collection.add model for model in @table when model.isCorrect filters
     if @forced and not collection.length


description text update

description text update

--- types.coffee
+++ types.coffee
@@ -1,4 +1,4 @@
-# type.js - A tiny Javascript type-check library, written in Coffeescript.
+# types.js - A tiny Javascript type-check library, written in Coffeescript.
 #
 # Copyright (c) 2014 Dennis Raymondo van der Sluis
 #


Added notification always on build failed

--- cli/tasks/build/server/build.coffee
+++ cli/tasks/build/server/build.coffee
@@ -35,10 +35,10 @@
         stack.add saveBundle, null, args

     # notify
-    if options.notify
-        callback = do (callback) -> (err) ->
+    callback = do (callback) -> (err) ->
+        if options.notify or err?
             notify err
-            callback err
+        callback err

     # run
     stack.runAll (err) ->


only use crossorigin.me for http and https

--- index.coffee
+++ index.coffee
@@ -12,7 +12,9 @@
 savechooser = document.getElementById 'savechooser'

 if location.search? and location.search.substr(1)
-  window.url = settings.urlPrefix + location.search.substr(1)
+  window.url = location.search.substr(1)
+  if window.url.startsWith 'http'
+    window.url = settings.urlPrefix + window.url
   [..., window.filename] = location.search.substr(1).split('/')

 if window.url and window.filename


travis

travis

--- index.coffee
+++ index.coffee
@@ -20,4 +20,3 @@
     s.options = options || {}
     s.options.minPrefixLength ?= 3
     s
-


Enable calling SyncReleases if a remote Release URL is present

--- index.coffee
+++ index.coffee
@@ -31,7 +31,7 @@
     loadingGif = config.loadingGif ? path.resolve(__dirname, '..', 'resources', 'install-spinner.gif')
     loadingGif = path.resolve(loadingGif)

-    {certificateFile, certificatePassword} = config
+    {certificateFile, certificatePassword, remoteReleases} = config

     appResourcesDirectory = path.join(appDirectory, 'resources', 'app')
     if grunt.file.isDir(appResourcesDirectory)
@@ -67,35 +67,46 @@
       '-NoDefaultExcludes'
     ]

+    syncReleases = (cb) -> cb()
+
+    if remoteReleases?
+      syncReleases = (cb) ->
+        cmd = path.resolve(__dirname, '..', 'vendor', 'SyncReleases.exe')
+        args = ['-u', remoteReleases, '-r', outputDirectory]
+        exec {cmd, args}, cb
+
     exec {cmd, args}, (error) ->
       return done(error) if error?

       nupkgPath = path.join(nugetOutput, "#{metadata.name}.#{metadata.version}.nupkg")

-      cmd = path.resolve(__dirname, '..', 'vendor', 'Update.com')
-      args = [
-        '--releasify'
-        nupkgPath
-        '--releaseDir'
-        outputDirectory
-        '--loadingGif'
-        loadingGif
-      ]
-
-      if certificateFile? and certificatePassword?
-        args.push '--signWithParams'
-        args.push "/a /f \"#{path.resolve(certificateFile)}\" /p \"#{certificatePassword}\""
-
-      if config.setupIcon
-        setupIconPath = path.resolve(config.setupIcon)
-        args.push '--setupIcon'
-        args.push setupIconPath
-
-      exec {cmd, args}, (error) ->
+      syncReleases (error) ->
         return done(error) if error?

-        if metadata.productName
-          setupPath = path.join(outputDirectory, "#{metadata.productName}Setup.exe")
-          fs.renameSync(path.join(outputDirectory, 'Setup.exe'), setupPath)
+        cmd = path.resolve(__dirname, '..', 'vendor', 'Update.com')
+        args = [
+          '--releasify'
+          nupkgPath
+          '--releaseDir'
+          outputDirectory
+          '--loadingGif'
+          loadingGif
+        ]

-        done()
+        if certificateFile? and certificatePassword?
+          args.push '--signWithParams'
+          args.push "/a /f \"#{path.resolve(certificateFile)}\" /p \"#{certificatePassword}\""
+
+        if config.setupIcon
+          setupIconPath = path.resolve(config.setupIcon)
+          args.push '--setupIcon'
+          args.push setupIconPath
+
+        exec {cmd, args}, (error) ->
+          return done(error) if error?
+
+          if metadata.productName
+            setupPath = path.join(outputDirectory, "#{metadata.productName}Setup.exe")
+            fs.renameSync(path.join(outputDirectory, 'Setup.exe'), setupPath)
+
+          done()


List now is observable

List now is observable

--- index.coffee
+++ index.coffee
@@ -83,6 +83,12 @@
 			if item instanceof Dict
 				@isConnected = true
 				item.onChange @getSignalChangeListener(), @
+			else if item instanceof List
+				@isConnected = true
+				handler = @getSignalChangeListener()
+				item.onChange handler, @
+				item.onInsert handler, @
+				item.onPop handler, @
 			else if handler = item[@handlerName]
 				@isConnected = true
 				handler @getSignalChangeListener(), @
@@ -91,10 +97,15 @@
 	disconnect: ->
 		{item} = @
 		if item and @isConnected
+			handler = @getSignalChangeListener()
 			if item instanceof Dict
-				item.onChange.disconnect @getSignalChangeListener(), @
+				item.onChange.disconnect handler, @
+			else if item instanceof List
+				item.onChange.disconnect handler, @
+				item.onInsert.disconnect handler, @
+				item.onPop.disconnect handler, @
 			else
-				item[@handlerName].disconnect @getSignalChangeListener(), @
+				item[@handlerName].disconnect handler, @
 		@isConnected = false
 		return


implemented "+m", "+f", "+p" buttons

implemented "+m", "+f", "+p" buttons

--- index.coffee
+++ index.coffee
@@ -144,6 +144,27 @@
   $('#b-prev').click -> search true; false
   $('#search').keydown (e) -> if e.which == 13 then search(); false

+  $('#b-refac-m').click ->
+    l = cme.getCursor().line
+    s = cme.getLine l
+    if !/^\s*$/.test s
+      cme.replaceRange "∇ #{s}\n\n∇", {line: l, ch: 0}, {line: l, ch: s.length}
+      cme.setCursor line: l + 1, ch: 0
+
+  $('#b-refac-f').click ->
+    l = cme.getCursor().line
+    s = cme.getLine l
+    if !/^\s*$/.test s
+      cme.replaceRange ":field public #{s}", {line: l, ch: 0}, {line: l, ch: s.length}
+      cme.setCursor line: l + 1, ch: 0
+
+  $('#b-refac-p').click ->
+    l = cme.getCursor().line
+    s = cme.getLine l
+    if !/^\s*$/.test s
+      cme.replaceRange ":Property #{s}\n\n∇r←get\nr←0\n∇\n\n∇set args\n∇\n:EndProperty", {line: l, ch: 0}, {line: l, ch: s.length}
+      cme.setCursor line: l + 1, ch: 0
+
   search = (backwards) ->
     if q = $('#search').val()
       v = cme.getValue()


[enh] runSync method

[enh] runSync method

--- index.coffee
+++ index.coffee
@@ -103,13 +103,14 @@
           log.error err

-watchLocalChanges = (devicename) ->
+runSync = (devicename, args) ->
     # Get config
     remoteConfig = config.config.remotes[devicename]

     watcher = chokidar.watch remoteConfig.path,
         ignored: /[\/\\]\./
         persistent: true
+        ignoreInitial: not args.catchup?

     watcher
     .on 'add', (path) ->
@@ -118,7 +119,7 @@
     .on 'addDir', (path) ->
         if path isnt remoteConfig.path
             log.info "Directory added: #{path}"
-            putDirectory devicename, path, { recursive: true }, () ->
+            putDirectory devicename, path, { recursive: false }, () ->
     .on 'change', (path) ->
         log.info "File changed: #{path}"
         putFile devicename, path, () ->
@@ -133,15 +134,61 @@

     url = urlParser.parse remoteConfig.url
     url.auth = devicename + ':' + remoteConfig.devicePassword
-    replication = db.sync(config.dbPath, urlParser.format(url) + 'cozy', options)
+    needTreeRebuild = false
+    replication = db.sync(urlParser.format(url) + 'cozy', options)
+      .on 'change', (info) ->
+          if info.direction is 'pull'
+              needTreeRebuild = true
+          console.log info
+      .on 'uptodate', (info) ->
+          log.info 'Replication is complete, applying changes on the filesystem...'
+          if needTreeRebuild
+              if args.binary?
+                  fetchBinaries devicename, {}, () ->
+                      needTreeRebuild = false
+              else
+                  buildFsTree devicename, {}, () ->
+                      needTreeRebuild = false
+      .on 'error', (err) ->
+          log.error err
+
+
+watchLocalChanges = (devicename) ->
+    # Get config
+    remoteConfig = config.config.remotes[devicename]
+
+    watcher = chokidar.watch remoteConfig.path,
+        ignored: /[\/\\]\./
+        persistent: true
+        ignoreInitial: true
+
+    watcher
+    .on 'add', (path) ->
+        log.info "File added: #{path}"
+        putFile devicename, path, () ->
+    .on 'addDir', (path) ->
+        if path isnt remoteConfig.path
+            log.info "Directory added: #{path}"
+            putDirectory devicename, path, { recursive: false }, () ->
+    .on 'change', (path) ->
+        log.info "File changed: #{path}"
+        putFile devicename, path, () ->
+    .on 'error', (err) ->
+        log.error 'An error occured when watching changes'
+        console.log err
+
+    options =
+        filter: (doc) ->
+            doc.docType is 'Folder' or doc.docType is 'File'
+        live: true
+
+    url = urlParser.parse remoteConfig.url
+    url.auth = devicename + ':' + remoteConfig.devicePassword
+    replication = db.replicate.to(urlParser.format(url) + 'cozy', options)
       .on 'change', (info) ->
           console.log info
       .on 'uptodate', (info) ->
-          log.info 'Replication is complete, applying changes on the filesystem...'
-          if args.binary?
-              fetchBinaries devicename, {}, () ->
-          else
-              buildFsTree devicename, {}, () ->
+          log.info 'Replication is complete'
       .on 'error', (err) ->
           log.error err

@@ -429,8 +476,8 @@
                         body = JSON.parse body
                         binaryDoc =
                             docType: 'Binary'
-                            path: relativePath
-                        log.info "Updating binary doc: #{relativePath}"
+                            path: absolutePath
+                        log.info "Updating binary doc: #{absolutePath}"
                         db.put binaryDoc, body.id, body.rev, (err, res) ->
                             return putFileDoc existingFileId, existingFileRev, body.id, body.rev, callback

@@ -529,6 +576,13 @@
     .action watchRemoteChanges

 program
+    .command('sync <devicename>')
+    .description('Watch changes on the remote DB')
+    .option('-b, --binary', 'automatically fetch binaries')
+    .option('-c, --catchup', 'catchup local changes')
+    .action runSync
+
+program
     .command('display-config')
     .description('Display device configuration and exit')
     .action displayConfig


Groups: Registration completed message reformatted

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -342,9 +342,9 @@
         $.cookie 'clientId', replacementToken
         KD.getSingleton('mainController').accountChanged account

-        title = ''
-        title += unless err then 'Good to go, Enjoy!' \
-                else 'Quota exceeded and could not join to the group. Please contact with group admin'
+        titleText = unless err then 'Good to go, Enjoy!' \
+                    else 'Quota exceeded and could not join to the group. Please contact with group admin'
+        title = '#{titleText}'

         new KDNotificationView
           cssClass  : "login"


Fixed another bug related to aliases cache clearing.

--- acs.coffee
+++ acs.coffee
@@ -75,10 +75,8 @@
       updates["#{i[0]}._name"] = i[1]

   if actions.parameterNames?
-    newAliases = [] # possible new aliased parameters
+    newAliases = {} # possible new aliased parameters
     for p in actions.parameterNames
-      newAliases.push(p[0]) if parameters.getAlias(p[0])?
-
       if OBJECT_REGEX.test(p[0])
         path = p[0]
         if INSTANCE_REGEX.test(p[0])
@@ -90,6 +88,9 @@
       updates["#{path}_writable"] = p[1] if p[1]?
       updates["#{path}_timestamp"] = now

+      # Add parameter (sans any dot suffix) if has an alias
+      newAliases[pp] = a if (a = parameters.getAlias(pp = path.slice(0, -1)))?
+
   if actions.customCommands?
     for p in actions.customCommands
       commandName = p[0]
@@ -108,8 +109,8 @@
           throw err if err
           return if not res?
           aliases = JSON.parse(res)
-          for a in newAliases
-            if not aliases[a]?
+          for p, a of newAliases
+            if not aliases[a]? or p not in aliases[a]
               db.redisClient.del('aliases', (err, res) ->
                 throw err if err
               )


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -10,4 +10,4 @@
     Object.extend(I.clampData, data)

   clampToBounds: (bounds) ->
-    bounds ||=+    bounds ||=


colloboration: add modal informating user of experimental nature

--- client/Main/main.coffee
+++ client/Main/main.coffee
@@ -9,6 +9,27 @@
   firstLoad        = yes

   mainController.tempStorage = {}
+
+  if /edge.koding.com/.test window.location.href
+    name = KD.utils.getFullnameFromAccount KD.whoami()
+    modal = new KDModalView
+      title            : "Hi #{name},"
+      width            : 600
+      overlay          : yes
+      cssClass         : "new-kdmodal"
+      content          : """
+

+
Thanks for trying out bleeding 'edge' of Koding features. Most of the new features here are experimental and hence prone to breaking.

+
+
If you run into problems, we'd love to hear what we wrong and how to reproduce the error. Please send an email to edge@koding.com. Note even though this environment is experimental, your vms are the same production vms as on koding.com.

+
+
By continuing you acknowledge the experimental nature of this environment.

+

+"""
+      buttons          :
+        "I Agree"      :
+          style        : "modal-clean-green"
+          callback     : -> modal.destroy()

   ###
   # CONNECTIVITY EVENTS


Use bust only if debug

Use bust only if debug

--- static/scripts/main.coffee
+++ static/scripts/main.coffee
@@ -1,4 +1,4 @@
-require.config
+config =
     baseUrl: '/static/scripts/'
     shim:
         jquery:
@@ -49,7 +49,11 @@
         underscoreString: '../underscore.string/dist/underscore.string.min'
         favico: '../favico.js/favico-0.3.0.min'
         chai: '../chai/chai'
-    urlArgs: "bust=" + (new Date()).getTime()
+
+if window.debug
+    config.urlArgs = "bust=" + (new Date()).getTime()
+
+require.config config

 if not window.testSuite
     require ['angular', 'application'], ->


STL/core making the Lovely API work without a document

--- stl/core/src/lovely.coffee
+++ stl/core/src/lovely.coffee
@@ -21,7 +21,6 @@
   options  = if isObject(args[0])   then args.shift() else {}
   modules  = if isArray(args[0])    then args.shift() else []
   callback = if isFunction(args[0]) then args.shift() else ->
-  header   = document.getElementsByTagName('head')[0]
   deadline = new Date() # the hang up time

   # setting up the options
@@ -38,6 +37,7 @@

   unless check_modules_load() # checking maybe they are already loaded
     modules_load_listeners.push(check_modules_load)
+    header = document.getElementsByTagName('head')[0]

     # inserting the actual scripts on the page
     for module, i in modules
@@ -141,11 +141,12 @@
 # @return {String} location
 #
 find_host_url = ->
-  scripts = document.getElementsByTagName('script')
-  re = /(.*?(^|\/))(lovely.io(:\d{4})?\/core\.js)/
+  if global.document
+    scripts = document.getElementsByTagName('script')
+    re = /(.*?(^|\/))(lovely.io(:\d{4})?\/core\.js)/

-  for script in scripts
-    if match = (script.getAttribute('src') || '').match(re)
-      return match[0].replace(/core\.js$/, '')
+    for script in scripts
+      if match = (script.getAttribute('src') || '').match(re)
+        return match[0].replace(/core\.js$/, '')

   Lovely.hostUrl


Fixed route issues pulled in from PulsenGame

--- src/code/routes.cjsx
+++ src/code/routes.cjsx
@@ -8,36 +8,12 @@
 	component: Master
 	onEnter: ({ location }, replace) =>
 		if location.pathname == '/'
-			replace '/game'
+			replace '/randomizer'
 	childRoutes: [

 		# Redirects
-		path: 'about'
-		onEnter: redirectRoute.bind null, '/game'
-	,
-		path: 'credits'
-		onEnter: redirectRoute.bind null, '/dev-team'
-	,
-		path: 'dancemats'
-		onEnter: redirectRoute.bind null, '/dance-pads'
-	,
-		path: 'dlc'
-		onEnter: redirectRoute.bind null, '/dlc/pulsen-souleye'
-	,
-		path: 'faqs'
-		onEnter: redirectRoute.bind null, '/faq'
-	,
-		path: 'manual'
-		onEnter: redirectRoute.bind null, '/faq'
-	,
-		path: 'media'
-		onEnter: redirectRoute.bind null, '/game'
-	,
-		path: 'news'
-		onEnter: redirectRoute.bind null, '/game'
-	,
-		path: 'press'
-		onEnter: redirectRoute.bind null, '/press-kit'
+		path: 'randomizer'
+		onEnter: redirectRoute.bind null, '/randomizer/smash-up'
 	,
 		path: '**/'
 		onEnter: ({ location }, replace) =>
@@ -45,54 +21,34 @@
 	,

 		# Routes
-		path: 'game'
+		path: 'instructions'
 		getComponent: (location, cb) =>
 			require.ensure [], (require) =>
-				cb null, require './views/game'
+				cb null, require './views/instructions'
 	,
-		path: 'dlc'
+		path: 'instructions/:link'
+		getComponent: (location, cb) =>
+			require.ensure [], (require) =>
+				cb null, require './views/instruction'
+	,
+		path: 'score-keeper'
+		getComponent: (location, cb) =>
+			require.ensure [], (require) =>
+				cb null, require './views/score-keeper'
+	,
+		path: 'randomizer'
 		childRoutes: [
-			path: 'pulsen-souleye'
+			path: 'smash-up'
 			getComponent: (location, cb) =>
 				require.ensure [], (require) =>
-					cb null, require './views/pulsen-souleye'
-		,
-			path: 'pulsen-community-hopscotch-mix'
-			getComponent: (location, cb) =>
-				require.ensure [], (require) =>
-					cb null, require './views/pulsen-community-hopscotch-mix'
+					cb null, require './views/randomizer-smash-up'
 		]
 	,
-		path: 'dance-pads'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/dance-pads'
-	,
-		path: 'faq'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/faq'
-	,
-		path: 'dev-team'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/dev-team'
-	,
-		path: 'contact'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/contact'
-	,
-		path: 'press-kit'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/press-kit'
-	,
-		path: 'video-policy'
-		getComponent: (location, cb) =>
-			require.ensure [], (require) =>
-				cb null, require './views/video-policy'
-	,
+	# 	path: 'contact'
+	# 	getComponent: (location, cb) =>
+	#		require.ensure [], (require) =>
+	# 			cb null, require './views/contact'
+	# ,
 		path: '*'
 		getComponent: (location, cb) =>
 			require.ensure [], (require) =>


Comment typo.

Comment typo.

--- src/collision.coffee
+++ src/collision.coffee
@@ -93,7 +93,7 @@
     # @param {collision} the collision to resolve.
     resolveCollision : (collision) ->

-        # Calcule the seperating velocity of the elements.
+        # Calculate the seperating velocity of the elements.
         seperatingVelocity = collision.getSeperatingVelocity()

         return if 0 > seperatingVelocity


list full folder path contents

list full folder path contents

--- app/assets/javascripts/osc_jobs.js.coffee
+++ app/assets/javascripts/osc_jobs.js.coffee
@@ -18,14 +18,17 @@
       error: (jqXHR, textStatus, errorThrown) ->
         console.log jqXHR
       success: (data, textStatus, jqXHR) ->
+        # TODO remove the console.log, this is for devving
+        console.log data
+
         update_job_details_panel(data)
         update_stop_button(data)
         update_template_button(data.script_path, data.batch_host)
-        # TODO remove the console.log, this is for devving
-        console.log data
+        list_folder_contents(data)
   else
     update_job_details_panel()
     update_template_button()
+    list_folder_contents()

 @show_job_panel = (show) ->
   if show?
@@ -90,6 +93,16 @@

 # Return the directory path of a file path
 abs_path = (filepath) ->
-  f = filepath.split('/')
-  f.pop()
-  f.join('/')
+  if filepath?
+    f = filepath.split('/')
+    f.pop()
+    f.join('/')
+
+@list_folder_contents = (data) ->
+  if data?
+    list = "
"
+    list += "
#{content.content}
" for content in data.folder_contents
+    list += "
"
+    $("#jobDetailsStagedDirContents").html(list)
+  else
+    $("#jobDetailsStagedDirContents").html("")


update IDs

update IDs

--- app/assets/javascripts/payments.js.coffee
+++ app/assets/javascripts/payments.js.coffee
@@ -8,19 +8,10 @@

   # strip non-numeric characters from the credit card field
-  $('#card_number').change ->
+  $('#payment_card_number').change ->
     value = $(this).val().replace(/[^\d\.]/g, '')
     $(this).val(value)

   # populate the card_expiry_date field from the expiry month and year
-  $('#payment_form').submit ->
-    $('#card_expiry_date').val($('#payment_card_expiry_date_2i').val() + '-' + $('#payment_card_expiry_date_1i').val())
-
-  # highlight missing fields if we fail client-side validation
-  $('#payment_form .btn-primary').click ->
-    # TODO: this is relying on HTML 5 currently...
-    # ... and it's not working very well. Several required show up as valid even when blank
-    if $('#payment_form')[0].checkValidity() == false
-      $('#payment_form input, #payment_form select').each ->
-        if $(this)[0].checkValidity() == false
-          $(this).closest('.form-group').addClass('has-error')
+  $('#new_payment').submit ->
+    $('#payment_card_expiry_date').val($('#payment_card_expiry_dummy_2i').val() + '-' + $('#payment_card_expiry_dummy_1i').val())


do not show alert if ajax fails worklog

do not show alert if ajax fails worklog

--- app/assets/javascripts/worklogs.js.coffee
+++ app/assets/javascripts/worklogs.js.coffee
@@ -55,5 +55,3 @@
       type: 'PUT'
       dataType: 'json'
       data: _this.elems.form().serialize()
-      error: ->
-        alert "There has been an error saving your worklog."


Just drop IE < 9 support

Just drop IE < 9 support

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -97,16 +97,9 @@
     unless FactlinkJailRoot.can_haz.client_buttons
       @$el.addClass 'fl-button-hide-default'

-  _isDisplayBlock: (el) ->
-    # For some reason jQuery in FF sometimes returns "undefined"
-    # Seems to be this one: https://stackoverflow.com/questions/14466604/jquery-returning-undefined-css-display
-    displayValue = $(el).css('display') || window.getComputedStyle(el).display
-
-    displayValue == 'block'
-
   _textContainer: ($el) ->
     for el in $el.parents()
-      return el if @_isDisplayBlock(el)
+      return el if window.getComputedStyle(el).display == 'block'
     console.error 'FactlinkJailRoot: No text container found for ', $el

   _updatePosition: ->


update popupContent peacemarker

update popupContent peacemarker

--- client/src/mymap.coffee
+++ client/src/mymap.coffee
@@ -109,10 +109,33 @@
         position = marker.getLatLng()
         popupContent = L.popup()
             .setContent(
-                'beau gosse!' + '
' +
-                '[Lat; Lng](zoom) = [' +
-                position.lat.toFixed(4) + '; ' + position.lng.toFixed(4) + ']
-                (' + markerzoom + ')')
+                '<form class="placemarker-form">' +
+                '

+                <input type="text" name="bookmark-name" ' +
+                'placeholder="Bookmark name"
+                class="placemarker-form-input" value="">' +
+                '<input type="hidden"
+                class="placemarker-form-id" value="id">' +
+                '<button type="submit" class="placemarker-form-submit">' +
+                '+</button>
' +
+
+                '

+                [Lat; Lng](zoom) = [' +
+                position.lat.toFixed(4) + '; ' +
+                position.lng.toFixed(4) + '](' +
+                markerzoom + ')
</form>' +
+
+                '<form class="placemarker-form">' +
+                '

+                <input type="text" name="past-position" ' +
+                'placeholder="[Lat; Lng](zoom)"
+                class="placemarker-form-input" value="">' +
+                '<input type="hidden"
+                class="placemarker-form-id" value="goto">' +
+                '<button type="submit" class="placemarker-form-submit">' +
+                '@</button>
</form>'
+                )
+
         marker.setLatLng new L.LatLng(position.lat, position.lng),
             draggable: 'true'
         marker.bindPopup(popupContent).openPopup()


Remove non-client side test

Remove non-client side test

--- client/test/src/section_view.coffee
+++ client/test/src/section_view.coffee
@@ -186,4 +186,3 @@
 )

 test("Can view report containing this section")
-test("Can navigate to Dashboard, Reports, Indicators and Bookmarks")


moar repos added

moar repos added

--- app.coffee
+++ app.coffee
@@ -97,6 +97,10 @@
 app.locals.repos = [
   attachGithubRepo "Pretty-Binary-Clock"
   attachGithubRepo "BitcoinMonitor"
+  attachGithubRepo "Tabs-Butcher"
+  attachGithubRepo "Weather-Happy"
+  attachGithubRepo "Dvorak-Programmer"
+  attachGithubRepo "Chrome-Bitcoin-Monitor"
 ]

 # prefix relative picture urls and support youtube links


Reformat code

Reformat code

--- app.coffee
+++ app.coffee
@@ -16,15 +16,10 @@
 ###

 panic = -> throw new Error "Cowardly refusing to keep the process alive as root"
-if process.getuid?() is 0 or process.getgid?() is 0
-	panic()
-
-exec = (require 'child_process').exec
-
+panic() if process.getuid?() is 0 or process.getgid?() is 0
+
 serverVersion = (require './package.json').version
-exec 'git describe --always', (err, stdout, stderr) ->
-	return if err?
-	serverVersion = stdout.trim()
+(require 'child_process').exec 'git describe --always', (err, stdout, stderr) -> serverVersion = stdout.trim() unless err?

 async = require 'async'
 basicAuth = require 'basic-auth'


Use callback

Use callback

--- app.coffee
+++ app.coffee
@@ -26,8 +26,8 @@
 if process.platform == 'win32'
   windowsShortcuts = require 'windows-shortcuts-appid'
   shortcutPath = app.getPath('appData') + "\\Microsoft\\Windows\\Start Menu\\Programs\\poi.lnk"
-  windowsShortcuts.create shortcutPath, app.getPath('exe')
-  windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'
+  windowsShortcuts.create shortcutPath, app.getPath('exe'), () ->
+    windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'

 if process.env.DEBUG?
   global.SERVER_HOSTNAME = '127.0.0.1:17027'


Test url

Test url

--- app.coffee
+++ app.coffee
@@ -29,7 +29,7 @@
 app.use express.errorHandler()  if "development" is app.get("env")
 app.get "/javascripts/*", serveAsset
 app.get "/stylesheets/*", serveAsset
-app.get "/", routes.index
+app.get "/test", routes.index

 http.createServer(app).listen app.get("port"), ->
   console.log "Express server listening on port " + app.get("port")


Make sure the led is off after blinking

Make sure the led is off after blinking

--- app.coffee
+++ app.coffee
@@ -64,6 +64,7 @@
 	interval = setInterval(toggleLed, settings.BLINK_STEP)
 	setTimeout(->
 		clearInterval(interval)
+		fs.writeFileSync(settings.LED_FILE, 0)
 		res.send(200)
 	, 5000)
 )


calculate debug for browserify based on process.argv

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -18,7 +18,6 @@
   # when there's an error, give it to the callback
   bundle.once 'error', (error) -> cb error

-# TODO: how to know if it's production or dev? change value of debug...
 # TODO: inputPath may include directories we need to strip for basedir
 processFile = (step) ->

@@ -38,9 +37,18 @@
   # basedir is fullInputPath with inputPath replaced with '.npm/package'
   basedir = step.fullInputPath.slice(0, -(step.inputPath.length)) + '.npm/package'

+  # debug is true unless we are doing a bundle or build, excpet with --debug set
+  debug = true
+  # check args used
+  for key in process.argv
+    # if 'meteor bundle file' or 'meteor build file'
+    if key is 'bundle' or key is 'build'
+      debug = '--debug' in process.argv
+      break;
+
   # create a browserify instance passing our readable stream as input,
   # and options with debug set to true for a dev build, and the basedir
-  browserify = Browserify [readable], debug:true, basedir:basedir
+  browserify = Browserify [readable], debug:debug, basedir:basedir

   # have browserify process the file and include all required modules.
   # we receive a readable stream as the result


Give docker a second after stopping a container before attempting to remove it. This may avoid errors during removal.

--- src/container.coffee
+++ src/container.coffee
@@ -76,6 +76,7 @@

   destroy: (callback) ->
     @_log.info(retries: @destroyCount, 'destroying')
+    @destroyCount++

     cb = callback
     callback = (err) ->
@@ -91,7 +92,10 @@

     timeout = setTimeout(timeoutCallback, 5000)

-    async.series [@stop, @remove], (err) =>
+    pause = ->
+      setTimeout(callback, 1000)
+
+    async.series [@stop, pause, @remove], (err) =>
       return if timedOut
       clearTimeout(timeout)
       unless err
@@ -155,7 +159,7 @@
     @_container.start options, (err) =>
       if (err)
         @_log.error(err, 'could not start')
-        return callback(err, container)
+        return callback(err, @)
       @_log.info('started')

       @info (err, info) =>