Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -9,4 +9,6 @@

   equals player.I.x, player.I.width / 2

+  play
+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -12,6 +12,6 @@
   player.I.x = 800
   player.update()

-  equals player.I.x, App
+  equals player.I.x, App.width

 module()


Add test for prepending a node module

Add test for prepending a node module

--- test/codecat.test.coffee
+++ test/codecat.test.coffee
@@ -33,6 +33,10 @@
 			'prepend2.js': 'var prepend2 = true;'
 			'append2.js': 'var append2 = true;'
 			'prepend3.js': 'var prepend3 = true;'
+			'withModule.js': '''
+				// @codecat-prepend "dummy-module"
+				var withModule = true;
+				'''

 		describe '#findConcats', ->

@@ -100,6 +104,20 @@
 				@subject.concatTo @destination, =>
 					done.check =>
 						expect(@fileContent).to.endWith(appended)
+
+		describe 'including a node module', ->
+			given 'source', -> './test/temp/withModule.js'
+
+			it 'concats the node module', (done) ->
+				@subject.concat (result) ->
+					done.check ->
+						expect(result).to.equal '''
+							var dummyModule = true;
+
+							// @codecat-prepend "dummy-module"
+							var withModule = true;
+							'''
+

 	describe 'with coffeescript', ->
 		given 'source', -> './test/temp/main.coffee'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -72,7 +72,7 @@

   player.update()

-  ok player.I.facing
+  ok player.I.facing.equal(Point(-1, -1))

 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))


Use sub-directory wildcards to match all files

--- test/karma.common.coffee
+++ test/karma.common.coffee
@@ -13,7 +13,7 @@

   preprocessors:
     'src/**/*.{coffee,cjsx}': ['webpack', 'sourcemap']
-    'test/*':  ['webpack', 'sourcemap']
+    'test/**/*':  ['webpack', 'sourcemap']

   webpack:
     devtool: 'eval-source-map'


Fix the unit test.

Fix the unit test.

--- test/mongoose/koa.coffee
+++ test/mongoose/koa.coffee
@@ -163,9 +163,9 @@
         await m(ctx, next)
         should.fail()
       catch e
+        e.message.should.be.equal 'required field is missing'
         e.meta.should.have.properties {
           responseCode: 422
-          message: 'required field is missing'
           path: 'kind'
         }


Remove console.log in test/routes/users.coffee

--- test/routes/users.coffee
+++ test/routes/users.coffee
@@ -11,7 +11,6 @@
     startApp -> UserTestHelper.addUsers done

   it 'Get all Users', (done)->
-    console.log base '/users'
     request (base '/users'), options, (e,r,b)->
       r.statusCode.should.be.equal 200
       b.should.have.property 'users'


wait connect: multiple test more tolerant to timing

--- test/wait/connect.coffee
+++ test/wait/connect.coffee
@@ -84,7 +84,7 @@
         ssh: ssh
         server1: server 12345
       .call ->
-        setTimeout @options.server1.listen, 100
+        setTimeout @options.server1.listen, 200
       .wait.connect
         servers: for i in [0...12]
           {host: 'localhost', port: 12345}


Fix: entity shows up in followings list

Fix: entity shows up in followings list

--- assets/javascripts/views/following.js.coffee
+++ assets/javascripts/views/following.js.coffee
@@ -35,7 +35,7 @@
       name: following.name() || following.get('entity')
       avatar: following.avatar()
       profileUrl: TentStatus.Helpers.entityProfileUrl following.get('entity')
-      has_name: following.hasName()
+      hasName: following.hasName()
       guest_authenticated: TentStatus.guest_authenticated || !TentStatus.config.domain_entity.assertEqual(entity)
     }


Guard against null guest or embedder

Guard against null guest or embedder

Uncaught exceptions would occur when these were null and
the target origin was '*'

--- atom/browser/lib/guest-window-manager.coffee
+++ atom/browser/lib/guest-window-manager.coffee
@@ -76,12 +76,12 @@
 ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) ->
   guestContents = BrowserWindow.fromId(guestId)?.webContents
   if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*'
-    guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin
+    guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin

 ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) ->
   embedder = v8Util.getHiddenValue event.sender, 'embedder'
   if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*'
-    embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin
+    embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin

 ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) ->
   BrowserWindow.fromId(guestId)?.webContents?[method] args...


Resetting the new player text input after a new player is created

--- client/client.coffee
+++ client/client.coffee
@@ -45,6 +45,7 @@
 			t_arr = tournaments.fetch()
 			id = Players.insert({name: document.getElementById("new-player").value, totalScore: Math.floor(t_arr.length * 104) })
 			Scores.insert({score: 104, tournamentId: tournament._id, playerId: id}) for tournament in t_arr
+			document.getElementById("new-player").value = ""
 	'keypress .score-input': (event) ->
 		if event.which == 13
 			id = event.target.id


heuristic for when alt-shift-up is between 2 tokens

--- client/keymap.coffee
+++ client/keymap.coffee
@@ -67,7 +67,12 @@
         [[l, 0], [l,  cm.getLine(l ).length]] # current line
         [[0, 0], [ll, cm.getLine(ll).length]] # whole document
       ]
-      if t = cm.getTokenAt cm.getCursor() then ranges.push [[l, t.start], [l, t.end]] # current token
+      u = cm.getCursor(); t0 = cm.getTokenAt u; t1 = cm.getTokenAt line: u.line, ch: u.ch + 1; t = t0 || t1
+      if t0 && t1 && !(t0.start == t1.start && t0.end == t1.end) # we must decide which token looks more important
+        [x, y] = [t0, t1].map (ti) -> (ti.type || '').replace /^.*\bapl-(\w*)$/, '$1' # x, y: left and right token type
+        I = var:5,glb:5,  quad:4,  str:3,  num:2,  kw:1,  par:-1,sqbr:-1,semi:-1,dfn:-1,  '':-2 # importance
+        t = if (I[x] || 0) > (I[y] || 0) then t0 else t1
+      t && ranges.push [[l, t.start], [l, t.end]] # current token
       ranges.sort (x, y) -> cmp(y[0], x[0]) || cmp(x[1], y[1]) # inside-out order: desc beginnings, then asc ends
       sel = sels[0]; s = [[sel.anchor.line, sel.anchor.ch], [sel.head.line, sel.head.ch]].sort cmp
       for r in ranges


Remove unused service dependency

Remove unused service dependency

--- app/assets/service/upload.coffee
+++ app/assets/service/upload.coffee
@@ -1,8 +1,8 @@
 'use strict'

 app.factory('uploadService', [
-  'routerService', 'messageService'
-  (router, messages) ->
+  'routerService',
+  (router) ->
     removedAsset: undefined

     # callbacks for ng-flow:


Move enumNames out of class

Move enumNames out of class

--- Enumeration.coffee
+++ Enumeration.coffee
@@ -1,6 +1,6 @@
+#Registered enums
+enumNames=[]
 class Enumeration
-  #Registered enums
-  @types:[]
   #Static function that creates an enum object value. Uniqueness guarantied by object reference.
   #This objects's unique own field is the Enumeration name. It's read only.
   #string value shall be uppercase
@@ -14,8 +14,8 @@

   constructor:(enumName,enumValues) ->
     #Check for uniqueness
-    if enumName in Enumeration.types then throw "#{enumName} already exists!"
-    else Enumeration.types.push enumName
+    if enumName in enumNames then throw "#{enumName} already exists!"
+    else enumNames.push enumName
     #Lambda to write enum values
     writeProperty = (property,key) => @[key or property]=Enumeration.value(property,enumName)
     if enumValues instanceof Array then writeProperty val for val in enumValues
@@ -31,3 +31,5 @@
     }
     #Guaranties properties to be 'final', non writable
     Object.freeze(this)
+
+#If you want to export the class, you'll need window.Enumeration=Enumeration or global.Enumeration=Enumeration


Remove filename debugging info from the subject viewer

--- app/classify/subject-viewer.cjsx
+++ app/classify/subject-viewer.cjsx
@@ -20,7 +20,7 @@
     image = @mediaSrcs['image/jpeg']

-
Page {@props.subject.metadata.page} {@props.subject.metadata.filename}

+
Page {@props.subject.metadata.page}

{@state.text}


Prevent double-logging

Prevent double-logging

--- app/coffee/ImageOptimiser.coffee
+++ app/coffee/ImageOptimiser.coffee
@@ -11,13 +11,11 @@
 			timeout: 30 * 1000
 			killSignal: "SIGKILL"
 		exec args, opts,(err, stdout, stderr)->
-			if err?
-				if err.signal == 'SIGKILL'
-					logger.warn {err: err, stderr: stderr, localPath: localPath}, "optimiser timeout reached"
-					err = null
-				else
-					logger.err err:err, stderr:stderr, localPath:localPath, "something went wrong converting compressPng"
+			if err? and err.signal == 'SIGKILL'
+				logger.warn {err: err, stderr: stderr, localPath: localPath}, "optimiser timeout reached"
+				err = null
+			else if err?
+				logger.err err:err, stderr:stderr, localPath:localPath, "something went wrong converting compressPng"
 			else
 				logger.log  localPath:localPath, "finished compressPng file"
 			callback(err)
-


Minor refactoring to make application.coffee code order more similar to Ozaria

--- app/core/application.coco.coffee
+++ app/core/application.coco.coffee
@@ -60,16 +60,6 @@
     store.commit('me/updateUser', me.attributes)
     store.commit('updateFeatures', features)

-    if me.showChinaRemindToast()
-      setInterval ( -> noty {
-        text: '你已经练习了一个小时了，建议休息一会儿哦'
-        layout: 'topRight'
-        type:'warning'
-        killer: false
-        timeout: 5000
-        }), 3600000  # one hour
-
-
     @store = store
     @api = api

@@ -94,6 +84,7 @@
     CocoModel.pollAchievements()
     unless me.get('anonymous')
       @checkForNewAchievement()
+    @remindPlayerToTakeBreaks()
     window.i18n = i18nextInstance = i18next.default.createInstance {
       lng: me.get('preferredLanguage', true)
       fallbackLng: locale.mapFallbackLanguages()
@@ -147,6 +138,15 @@
   setHocCampaign: (campaignSlug) -> storage.save('hoc-campaign', campaignSlug)
   getHocCampaign: -> storage.load('hoc-campaign')

+  remindPlayerToTakeBreaks: ->
+    return unless me.showChinaRemindToast()
+    setInterval ( -> noty {
+      text: '你已经练习了一个小时了，建议休息一会儿哦'
+      layout: 'topRight'
+      type:'warning'
+      killer: false
+      timeout: 5000
+      }), 3600000  # one hour
 }

 module.exports = Application


Fixed Facebook login bug.

Fixed Facebook login bug.

--- app/lib/services/facebook.coffee
+++ app/lib/services/facebook.coffee
@@ -17,7 +17,7 @@
       if response.status is 'connected'

         # They have logged in to the app.
-        Backbone.Mediator.publish 'facebook-logged-in', response: response
+        Backbone.Mediator.publish 'auth:logged-in-with-facebook', response: response

       else if response.status is 'not_authorized'
         #


update russian

update russian

--- src/i18n/ru.cson
+++ src/i18n/ru.cson
@@ -22,7 +22,7 @@
 CUSTOM_POSTS: "Пользовательские записи"
 CUSTOM_TAXO: "Пользовательские ярлыки"
 CUSTOM_TAXO_TITLE: "{{term}}: {{name}}"
-PUSH_NOTIFS: "Push notifications"
+PUSH_NOTIFS: "Настройки уведомлений"
 PUSH_NOTIF_TITLE: "Новый контент!"
 PUSH_NOTIF_TEXT: "Новая запись/страница: '{{postTitle}}' была только что добавлена в {{appTitle}}. Хочешь открыть?"
 BOOKMARKS: "Закладки"
@@ -62,7 +62,7 @@
 "about":
     "title": "Информация"
 "languages":
-    "en": "Ангийский"
+    "en": "Английский"
     "fr": "Французский"
     "zh": "Китайский"
     "es": "Испанский"


Rename the 'addPipe' method to 'pipe', and remove 'addPipes'

--- src/index.coffee
+++ src/index.coffee
@@ -52,18 +52,12 @@
     app.use compression()

   # The array of request handlers.
-  app.pipes = []
+  pipes = []

-  app.addPipe = (pipe) ->
+  app.pipe = (pipe) ->
     assertType pipe, Function
-    app.pipes.push pipe
-    return
-
-  app.addPipes = (pipes) ->
-    for pipe in pipes
-      assertType pipe, Function
-      app.pipes.push pipe
-    return
+    pipes.push pipe
+    return app

   # Context shared by all requests.
   app.context = {}
@@ -79,7 +73,6 @@
     context = app.createContext req, res
     setProto context, app.context

-    {length} = pipes = app.pipes
     index = -1

     measure = Function.prototype
@@ -98,7 +91,7 @@

     next = ->

-      if ++index is length
+      if ++index is pipes.length
         res.status 404
         res.setHeader "Content-Type", "application/json"
         res.send {error: "Nothing exists here. Sorry!"}


Only display respawn for dead Challengers.

--- src/index.coffee
+++ src/index.coffee
@@ -114,7 +114,7 @@
     lines = []
     for c in sorted
       line = "*#{c.displayName()}*: #{c.hp()}/#{c.maxHP()} HP #{c.exp()} EXP"
-      if c.nextRespawn()?
+      unless c.isAlive()
         line += " _will respawn #{c.nextRespawn().fromNow()}_"
       lines.push line


test mixins

test mixins

--- src/index.coffee
+++ src/index.coffee
@@ -43,6 +43,13 @@
   watch:
     count: (newValue, oldValue) ->
       console.log "count: #{oldValue} -> #{newValue}"
+  mixins: [
+    created: ->
+      console.log 'mixin 1 created'
+  ,
+    created: ->
+      console.log 'mixin 2 created'
+  ]

 app.$emit 'ping', '!'


Auto rotate image dimensions metadata

Auto rotate image dimensions metadata

--- src/index.coffee
+++ src/index.coffee
@@ -98,7 +98,7 @@
 # Get image metadata
 ##
 Image.prototype.getMetadata = (src, cb) ->
-  metadata src, exif: @upload.opts.returnExif, cb
+  metadata src, exif: @upload.opts.returnExif, autoOrient: true, cb

 ##
 # Get image destination


FIX Multi client control thanks to socket.io broadcast

--- src/index.coffee
+++ src/index.coffee
@@ -11,18 +11,20 @@

 		me.streamer 	= new (require('./streamer.js')).Streamer (state)->
 			if me.socket
-				me.socket.emit 'sys', JSON.stringify( {sys: {state:state, sample_count:me.streamer.getSampleCount()} } )
+				me.serv.io.sockets.emit 'sys', JSON.stringify( {sys: {state:state, sample_count:me.streamer.getSampleCount()} } )

 		me.classifier  = new (require('./classifier.js')).Classifier me.streamer.getSampleFile(), (code, data) ->
 			if me.socket
-				me.socket.emit 'data', data
+				me.serv.io.sockets.emit 'data', data
 		me.classifier.init()

 		me.serv.io.on 'connection', (socket) ->
 			me.socket = socket
 			setTimeout (->
-				me.socket.emit 'data', JSON.stringify(me.classifier.getConf())
+				me.serv.io.sockets.emit 'data', JSON.stringify(me.classifier.getConf())
 				), 5000
+
+#			me.serv.io.sockets.emit 'sys', 'test'

 			socket.on 'sys', (msg) ->
 				console.log 'sys event: ' + msg.toString()
@@ -42,7 +44,7 @@
 				if sys.url		 then me.streamer.url = me.stream.getStreamPath() + sys.url

 				if sys.streams? then me.stream.getStreams (code, data) ->
-					if !code then me.socket.emit 'sys', JSON.stringify {sys:{streams: data }}
+					if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{streams: data }}
 					else console.log "WARNING Error controller: " + data

 				if sys.sample && sys.classe then me.dataset.addCurrent sys.sample, sys.classe, me.streamer.getSampleFile(), (code, data) ->
@@ -56,19 +58,19 @@
 						else console.log 'Toggle of classifier ' + sys.classifier?.toggle + ' failed.' + data

 				if sys.classifier?.list? then me.classifier.getAvailableClassifiers (code,data) ->
-					if !code then me.socket.emit 'sys', JSON.stringify {sys:{classifier:{list: data }}}
+					if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{classifier:{list: data }}}
 					else console.log "WARNING Error controller: " + data

 				# TRAINING EVENTS
 				if sys.training?.list? then me.dataset.getTrainingSets (code,data) ->
-					if !code then me.socket.emit 'sys', JSON.stringify {sys:{training:{list: data }}}
+					if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{list: data }}}
 					else console.log "WARNING Error controller: " + data

 				if sys.training?.build then 	me.dataset.buildClassifier sys.training.build,  sys.training.filter_low, sys.training.filter_high,  sys.training.structure, sys.training.epoch, sys.training.learning_rate, (code,data) ->
 					if 			code == 0
 						socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'done',data:data}}}
 						me.classifier.getAvailableClassifiers (code,data) ->
-							if !code then me.socket.emit 'sys', JSON.stringify {sys:{classifier:{list: data }}}
+							if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{classifier:{list: data }}}
 							else console.log "WARNING Error controller: " + data
 					else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'running',data:data}}}
 					else socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'failed',out:data}}}
@@ -82,14 +84,14 @@

 				# DATASET EVENTS
 				if sys.dataset?.list? then me.dataset.getDatasets (code,data) ->
-					if !code then me.socket.emit 'sys', JSON.stringify {sys:{dataset:{list: data }}}
+					if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{list: data }}}
 					else console.log "WARNING Error controller: " + data

 				if sys.dataset?.build then 	me.dataset.buildDataset sys.dataset?.build, (code,data) ->
 					if 			code == 0
 						socket.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'done',data:data}}}
 						me.dataset.getTrainingSets (code,data) ->
-							if !code then me.socket.emit 'sys', JSON.stringify {sys:{training:{list: data }}}
+							if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{list: data }}}
 							else console.log "WARNING Error controller: " + data

 					else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'running',data:data}}}
@@ -108,7 +110,7 @@

 				# DATABASE RECORD EVENTS
 				if sys.records?.list?	then me.dataset.getDatabases (code, data) ->
-					if !code then me.socket.emit 'sys', JSON.stringify {sys:{records:{list: data }}}
+					if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{list: data }}}
 					else console.log "WARNING Error controller: " + data

 				if sys.records?.build
@@ -117,7 +119,7 @@
 						if 			code == 0
 							socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'done',data:data}}}
 							me.dataset.getDatasets (code,data) ->
-								if !code then me.socket.emit 'sys', JSON.stringify {sys:{dataset:{list: data }}}
+								if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{list: data }}}
 								else console.log "WARNING Error controller: " + data
 						else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'running',data:data}}}
 						else socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'failed',out:data}}}


okay, so broadcasting on student works, maybe also it will work on teacher...

--- src/index.coffee
+++ src/index.coffee
@@ -58,7 +58,8 @@
         io.of('/teacher').emit 'render', onlineData()
     socket.on 'edit', (data) ->
         socket.broadcast.emit 'edit', data
-        io.of('/teacher').emit 'edit', data
+        #io.of('/teacher').emit 'edit', data
+        socket.broadcast.to('/teacher').emit 'edit', data
     socket.on 'disconnect', (data) ->
         online = onlineData()
         nickPairsLessThis = _.filter online.idNickPairs, (o) -> o.id != socket.id


Escape executable filename of aapt.

Escape executable filename of aapt.

--- src/index.coffee
+++ src/index.coffee
@@ -1,5 +1,5 @@
 os = require 'os'
-exec = require('child_process').exec
+exec = require('child_process').execFile

 parseApk = (filename, cb) ->
     if os.type() == 'Darwin'
@@ -11,7 +11,7 @@
     else
         return cb(new Error('Unknown OS!'))

-    exec "#{__dirname}/../tools/#{platform}/aapt l -a #{filename}", (err, out) ->
+    exec "#{__dirname}/../tools/#{platform}/aapt", [ 'l', '-a', filename ], (err, out) ->
         return cb(err) if err
         parseOutput(out, cb)


Improve a bit of readability

Improve a bit of readability

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- src/items.coffee
+++ src/items.coffee
@@ -115,46 +115,46 @@
   #
   where: (params) ->
     (item) ->
-      for paramName, param of params
+      for property, matcher of params
         # Handle passives, active, and auras
-        if ["passives", "active", "auras"].indexOf(paramName) isnt -1
+        if ["passives", "active", "auras"].indexOf(property) isnt -1
           return false # TODO

         # Check for matching a function
-        if typeof param is "function"
-          return false unless param item[paramName]
+        if typeof matcher is "function"
+          return false unless matcher item[property]

         # Check for equality
-        else if typeof param isnt "object"
-          return false if item[paramName] isnt param
+        else if typeof matcher isnt "object"
+          return false if item[property] isnt matcher

         # It's a query!
         for handlerName, handler of lol.items._queryHandlers
-          if param[handlerName]
+          if matcher[handlerName]
             # Check if the property is of the right type
             if handler.propTypes
               prop = false
               for type in handler.propTypes
-                if typeof item[paramName] is type
+                if typeof item[property] is type
                   prop = true
                   break

               unless prop
-                throw new Error "Invalid type for property '#{item[paramName]}'! Available types are: #{handler.propTypes.join(", ")}"
+                throw new Error "Invalid type for property '#{item[property]}'! Available types are: #{handler.propTypes.join(", ")}"

             # Check if the handler is of the right type
             if handler.valTypes
               val = false
               for type in handler.valTypes
-                if typeof param[handlerName] is type
+                if typeof matcher[handlerName] is type
                   val = true
                   break

               unless val
-                throw new Error "Invalid type for filter value '#{param[handlerName]}'! Available types are: #{handler.valTypes.join(", ")}"
+                throw new Error "Invalid type for filter value '#{matcher[handlerName]}'! Available types are: #{handler.valTypes.join(", ")}"

             # Does not match filter unless the handler is triggered correctly
-            return false unless handler.handler item[paramName], param[handlerName]
+            return false unless handler.handler item[property], matcher[handlerName]

       return true


Remember list selection through navigations

JSDK-39

--- assets/javascript/sidebar.coffee
+++ assets/javascript/sidebar.coffee
@@ -1,7 +1,14 @@
 $ ->
+  OPEN_LIST_KEY = 'open_list'
+
   openList = (klass) ->
     $('.toggled').removeClass('visible')
     $(".toggled.#{klass}").addClass('visible')
+    sessionStorage.setItem(OPEN_LIST_KEY, klass)
+
+  # Reopen most recently opened list on page load
+  if klass = sessionStorage.getItem(OPEN_LIST_KEY)
+    openList(klass)

   window.openClassList = -> openList('class_list')
   window.openMethodList = -> openList('method_list')


bedtime

bedtime

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -22,5 +22,5 @@
       }
     }

-gulp.task 'test:watch', ['test'], ->
+gulp.task 'testwatch', ['test'], ->
   gulp.watch ['./src/*', './test/*'], ['test']


graph explorer: limit zoom range

graph explorer: limit zoom range

--- app/assets/javascripts/d3/topology.coffee
+++ app/assets/javascripts/d3/topology.coffee
@@ -66,7 +66,7 @@
       .attr('width', @width)
       .attr('height', @height)
       .attr("pointer-events", "all")
-      .call(d3.behavior.zoom().on("zoom", @rescale))
+      .call(d3.behavior.zoom().scaleExtent([0.9,15]).on("zoom", @rescale))
       .append('g')

     max_x = d3.max(@nodes, (d) -> d.x) + 50


ドロップしたときにポストする

ドロップしたときにポストする

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -11,5 +11,13 @@
   $('input.planed_time').on "change", ->
     alert "!"

-  $('.sortable').sortable();
+  $('.sortable').sortable update: (event, ui) ->
+    $.ajax (document.url + "/task/sort"),
+      type: "POST",
+      dataType: "script"
+      # data: {
+      #  "task-id": ui.item[0].attr("data-task-id")
+      #  "order": ui.item[0].rowIndex
+      #}
+
   $('.sortable').disableSelection();


JS fix as suggested by @Artofcode-

JS fix as suggested by @Artofcode-

Done on mobile, sorry if I fat-fingered it

--- app/assets/javascripts/vote_counts.coffee
+++ app/assets/javascripts/vote_counts.coffee
@@ -27,6 +27,16 @@
       $.each data, (user_id, current_score) ->
         element = $("#" + user_id + " .current-score")
         element.text current_score
+        delta = $("#" + user_id + " .delta")
+        user_index = $("tr").index($("#" + user_id))
+        if user_index == 0
+          next_score = parseInt($($("tr")[1]).find(".delta").text(), 10)
+          continue if isNaN(next_score)
+          delta.text 'Leading by ' + (parseInt(delta.text(), 10) - next_score)
+        else
+          prev_score = parseInt($($("tr")[user_index - 1]).find(".delta").text(), 10)
+          continue if isNaN(prev_score)
+          delta.text prev_score - parseInt(delta.text(), 10)
       rows = ($("table").find('tr').sort (a, b) ->
         current_score_a = $(a).find(".current-score").text()
         current_score_b = $(b).find(".current-score").text()


simplify

simplify

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -967,14 +967,14 @@
       part = (source, sourceIndex) -> (unsubAll) ->
         source.subscribe (e) ->
           if e.isEnd()
-            sources[sourceIndex].markEnded()
+            source.markEnded()
             if _.all(pats, cannotMatch)
               reply = Bacon.noMore
               sink end()
           else if e.isError()
             reply = sink e
           else
-            sources[sourceIndex].push e.value()
+            source.push e.value()
             for p in pats
                if match(p)
                  val = p.f(sources[i.index].consume() for i in p.ixs ...)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/State.coffee
+++ src/State.coffee
@@ -1,3 +1,6 @@
+StateMachine = (name, object, options, block) ->
+  return Machine(name, object, options, block)
+
 Callback = (options, machine, block) ->
   self =
     match: (from_state, to_state, event) ->


Add findMemberURIs

Add findMemberURIs

--- term.coffee
+++ term.coffee
@@ -351,6 +351,9 @@
     findTypeURIs: (subject) ->
         @NTtoURI @findTypesNT subject

+    findMemberURIs: (subject) ->
+        @NTtoURI @findMembersNT subject
+
     findTypesNT: (subject) ->
         # Get all the Classes of which we can RDFS-infer the subject is a member
         # ** @@ This will loop is there is a class subclass loop (Sublass loops are not illegal)


change ready to service manifest

change ready to service manifest

--- lib/generators/transponder/install/templates/manifest.js.coffee
+++ lib/generators/transponder/install/templates/manifest.js.coffee
@@ -1,4 +1,4 @@
-ready = ->
+service_manifest = ->
   # fill in your manifest here
   # example:
   # if you want to trigger a service on your entire app


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -48,51 +48,35 @@

     return hslToRgb(parsedColor)

-  hslToRgb = (hsl) ->
-    [hue, saturation, lightness, alpha] = (parseFloat(channel) for channel in hsl)
-
-    if saturation == 0
-      r = g = b = lightness
+  hslToRgb = (hsl) ->
+    [h, s, l, a] = (parseFloat(channel) for channel in hsl)
+
+    h = (h % 360) / 360
+    a ||= 1
+
+    r = g = b = null
+
+    hueToRgb = (p, q, t) ->
+      t += 1 if t < 0
+      t -= 1 if t > 1
+
+      return p + (q - p) * 6 * t if t < 1/6
+      return q if t < 1/2
+      return p + (q - p) * (2/3 - t) * 6 if t < 2/3
+      return p
+
+    if s == 0
+      r = g = b = l
     else
-      hue = (hue % 360) / 60
-      hue_floor = hue.floor()
-      hue_diff = hue - hue_floor
-
-      p = lightness * (1 - saturation)
-      q = lightness * (1 - saturation * hue_diff)
-      t = lightness * (1 - saturation * (1 - hue_diff))
-
-      switch hue_floor
-        when 0
-          r = lightness
-          g = t
-          b = p
-        when 1
-          r = q
-          g = lightness
-          b = p
-        when 2
-          r = p
-          g = lightness
-          b = t
-        when 3
-          r = p
-          g = q
-          b = lightness
-        when 4
-          r = t
-          g = p
-          b = lightness
-        else
-          r = lightness
-          g = p
-          b = q
-
-    red = (r * 255 + 0.5).floor()
-    green = (g * 255 + 0.5).floor()
-    blue = (b * 255 + 0.5).floor()
-
-    return [red, green, blue, alpha]
+      q = (if l < 0.5 then l * (1 + s) else l + s - l * s)
+      p = 2 * l - q
+      r = hueToRgb(p, q, h + 1/3)
+      g = hueToRgb(p, q, h)
+      b = hueToRgb(p, q, h - 1/3)
+
+      rgbMap = ((channel * 0xFF).round() for channel in [r, g, b])
+
+    return rgbMap.concat(a)

   normalizeKey = (key) ->
     key.toString().toLowerCase().split(' ').join('')
@@ -289,24 +273,29 @@

       {min, max} = [r, g, b].extremes()

-      delta = max - min
-      hue = 0
-      lightness = max
-      saturation = (if max == 0 then 0 else delta / max)
-
-      if delta != 0
-        if r == max
-          hue = (g - b) / delta
-        else if g == max
-          hue = 2 + (b - r) / delta
-        else
-          hue = 4 + (r - g) / delta
-
-      hue *= 60
-
-      hue += 360 if hue < 0
-
-      return [hue, saturation, lightness, @a]
+      hue = saturation = lightness = (max + min) / 2
+
+      if max == min
+        hue = saturation = 0
+      else
+        chroma = max - min
+
+        saturation =
+          if lightness > 0.5
+            chroma / (1 - lightness)
+          else
+            chroma / lightness
+
+        saturation /= 2
+
+        switch max
+          when r then hue = ((g - b) / chroma) + 0
+          when g then hue = ((b - r) / chroma) + 2
+          when b then hue = ((r - g) / chroma) + 4
+
+        hue *= 60
+
+      return [hue, saturation, lightness, @a]

     toString: ->
       "rgba(#{@r}, #{@g}, #{@b}, #{@a})"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -507,7 +507,7 @@
     magenta.lightness()
     # => 0.9

-    # modifies magenta in place
+    # modifies magenta in place to be lighter
     magenta.lightness(0.75)

     # to see what it looks like


fixing coffeescript issue with this

fixing coffeescript issue with this

--- src/giphy.coffee
+++ src/giphy.coffee
@@ -168,7 +168,7 @@
         if image and image.url
           image.url

-  getUriFromResultData: (data) ->
+  getUriFromResultData: (data) =>
     if data and data.images
       if @maxSize > 0
         @getUriFromResultDataWithMaxSize data.images, @maxSize, @allowLargerThanMaxSize


extend no longer culls empty string props

--- src/giphy.coffee
+++ src/giphy.coffee
@@ -32,8 +32,9 @@

 # utility method for extending an object definition
 extend = (object, properties) ->
-  for key, val of properties
-    object[key] = val if val
+  object = object or { }
+  for key, val of properties or { }
+    object[key] = val if val or val == ''
   object

 # utility method for merging two objects


Add highlighting for keywords missed by updater

These aren't listed in Vim's syntax file, so we have to adhoc them.

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -59,6 +59,7 @@
 			{include: "#variable"}
 			{include: "#supportType"}
 			{include: "#supportVariable"}
+			{include: "#extraVimFunc"}
 			{include: "#keywordLists"}
 		]

@@ -525,6 +526,16 @@
 		}]

+	# Extra functions not picked up by keyword parser
+	extraVimFunc:
+		name: "support.function.viml"
+		match: """(?x)\\b
+			(execute|Plugin|autocmd|[cinvo](?:un|nore)?map|(?:range)?go(?:to)?|(?:count)?(?:pop?|tag?|tn(?:ext)?|tp(?:revious)?|tr(?:ewind)?)
+			|(?:range)?(?:s(?:ubstitute)?|ret(?:ab)?|g(?:lobal)?)|unm(?:ap)?|map_l|mapc(?:lear)?|N?buffer|N?bnext|N?bNext|N?bprevious|N?bmod
+			|ab(?:breviate)?|norea(?:bbrev)?|[ic](?:un|nore)?ab|split_f|unmenu|rangefold|[ic](?:un|nore)?ab|[ic]abbrev|edit_f|next_f
+			|(?:range)?(?:w(?:rite)?|up(?:date)?)|sar)
+		\\b"""
+

 	# The following lists are auto-extracted from Vim's syntax file.
 	# Run `tools/update` to repopulate them.
@@ -604,14 +615,8 @@
 		match: "\\b(fu|function)\\b"

 	vimFuncName:
-		patterns: [{
-			name: "support.function.viml"
-			match: "\\b(abs|acos|add|and|append|argc|argidx|arglistid|argv|asin|assert_equal|assert_exception|assert_fails|assert_false|assert_inrange|assert_match|assert_notequal|assert_notmatch|assert_true|atan|atan2|browse|browsedir|bufexists|buflisted|bufloaded|bufname|bufnr|bufwinid|bufwinnr|byte2line|byteidx|byteidxcomp|call|ceil|ch_close|ch_close_in|ch_evalexpr|ch_evalraw|ch_getbufnr|ch_getjob|ch_info|ch_log|ch_logfile|ch_open|ch_read|ch_readraw|ch_sendexpr|ch_sendraw|ch_setoptions|ch_status|changenr|char2nr|cindent|clearmatches|col|complete|complete_add|complete_check|confirm|copy|cos|cosh|count|cscope_connection|cursor|deepcopy|delete|did_filetype|diff_filler|diff_hlID|empty|escape|eval|eventhandler|executable|execute|exepath|exists|exp|expand|extend|feedkeys|filereadable|filewritable|filter|finddir|findfile|float2nr|floor|fmod|fnameescape|fnamemodify|foldclosed|foldclosedend|foldlevel|foldtext|foldtextresult|foreground|funcref|function|garbagecollect|get|getbufinfo|getbufline|getbufvar|getchar|getcharmod|getcharsearch|getcmdline|getcmdpos|getcmdtype|getcmdwintype|getcompletion|getcurpos|getcwd|getfontname|getfperm|getfsize|getftime|getftype|getline|getloclist|getmatches|getpid|getpos|getqflist|getreg|getregtype|gettabinfo|gettabvar|gettabwinvar|getwininfo|getwinposx|getwinposy|getwinvar|glob|glob2regpat|globpath|has|has_key|haslocaldir|hasmapto|histadd|histdel|histget|histnr|hlexists|hlID|hostname|iconv|indent|index|input|inputdialog|inputlist|inputrestore|inputsave|inputsecret|insert|invert|isdirectory|islocked|isnan|items|job_getchannel|job_info|job_setoptions|job_start|job_status|job_stop|join|js_decode|js_encode|json_decode|json_encode|keys|len|libcall|libcallnr|line|line2byte|lispindent|localtime|log|log10|luaeval|map|maparg|mapcheck|match|matchadd|matchaddpos|matcharg|matchdelete|matchend|matchlist|matchstr|matchstrpos|max|min|mkdir|mode|mzeval|nextnonblank|nr2char|or|pathshorten|perleval|pow|prevnonblank|printf|pumvisible|py3eval|pyeval|range|readfile|reltime|reltimefloat|reltimestr|remote_expr|remote_foreground|remote_peek|remote_read|remote_send|remove|rename|repeat|resolve|reverse|round|screenattr|screenchar|screencol|screenrow|search|searchdecl|searchpair|searchpairpos|searchpos|server2client|serverlist|setbufvar|setcharsearch|setcmdpos|setfperm|setline|setloclist|setmatches|setpos|setqflist|setreg|settabvar|settabwinvar|setwinvar|sha256|shellescape|shiftwidth|simplify|sin|sinh|sort|soundfold|spellbadword|spellsuggest|split|sqrt|str2float|str2nr|strcharpart|strchars|strdisplaywidth|strftime|strgetchar|stridx|string|strlen|strpart|strridx|strtrans|strwidth|submatch|substitute|synconcealed|synID|synIDattr|synIDtrans|synstack|system|systemlist|tabpagebuflist|tabpagenr|tabpagewinnr|tagfiles|taglist|tan|tanh|tempname|test_alloc_fail|test_autochdir|test_disable_char_avail|test_garbagecollect_now|test_null_channel|test_null_dict|test_null_job|test_null_list|test_null_partial|test_null_string|test_settime|timer_info|timer_pause|timer_start|timer_stop|timer_stopall|tolower|toupper|tr|trunc|type|undofile|undotree|uniq|values|virtcol|visualmode|wildmenumode|win_findbuf|win_getid|win_gotoid|win_id2tabwin|win_id2win|winbufnr|wincol|winheight|winline|winnr|winrestcmd|winrestview|winsaveview|winwidth|wordcount|writefile|xor)\\b"
-		},{
-			# Extra functions not picked up by keyword parser
-			name: "support.function.viml"
-			match: "\\b(execute|Plugin|autocmd)\\b"
-		}]
+		name: "support.function.viml"
+		match: "\\b(abs|acos|add|and|append|argc|argidx|arglistid|argv|asin|assert_equal|assert_exception|assert_fails|assert_false|assert_inrange|assert_match|assert_notequal|assert_notmatch|assert_true|atan|atan2|browse|browsedir|bufexists|buflisted|bufloaded|bufname|bufnr|bufwinid|bufwinnr|byte2line|byteidx|byteidxcomp|call|ceil|ch_close|ch_close_in|ch_evalexpr|ch_evalraw|ch_getbufnr|ch_getjob|ch_info|ch_log|ch_logfile|ch_open|ch_read|ch_readraw|ch_sendexpr|ch_sendraw|ch_setoptions|ch_status|changenr|char2nr|cindent|clearmatches|col|complete|complete_add|complete_check|confirm|copy|cos|cosh|count|cscope_connection|cursor|deepcopy|delete|did_filetype|diff_filler|diff_hlID|empty|escape|eval|eventhandler|executable|execute|exepath|exists|exp|expand|extend|feedkeys|filereadable|filewritable|filter|finddir|findfile|float2nr|floor|fmod|fnameescape|fnamemodify|foldclosed|foldclosedend|foldlevel|foldtext|foldtextresult|foreground|funcref|function|garbagecollect|get|getbufinfo|getbufline|getbufvar|getchar|getcharmod|getcharsearch|getcmdline|getcmdpos|getcmdtype|getcmdwintype|getcompletion|getcurpos|getcwd|getfontname|getfperm|getfsize|getftime|getftype|getline|getloclist|getmatches|getpid|getpos|getqflist|getreg|getregtype|gettabinfo|gettabvar|gettabwinvar|getwininfo|getwinposx|getwinposy|getwinvar|glob|glob2regpat|globpath|has|has_key|haslocaldir|hasmapto|histadd|histdel|histget|histnr|hlexists|hlID|hostname|iconv|indent|index|input|inputdialog|inputlist|inputrestore|inputsave|inputsecret|insert|invert|isdirectory|islocked|isnan|items|job_getchannel|job_info|job_setoptions|job_start|job_status|job_stop|join|js_decode|js_encode|json_decode|json_encode|keys|len|libcall|libcallnr|line|line2byte|lispindent|localtime|log|log10|luaeval|map|maparg|mapcheck|match|matchadd|matchaddpos|matcharg|matchdelete|matchend|matchlist|matchstr|matchstrpos|max|min|mkdir|mode|mzeval|nextnonblank|nr2char|or|pathshorten|perleval|pow|prevnonblank|printf|pumvisible|py3eval|pyeval|range|readfile|reltime|reltimefloat|reltimestr|remote_expr|remote_foreground|remote_peek|remote_read|remote_send|remove|rename|repeat|resolve|reverse|round|screenattr|screenchar|screencol|screenrow|search|searchdecl|searchpair|searchpairpos|searchpos|server2client|serverlist|setbufvar|setcharsearch|setcmdpos|setfperm|setline|setloclist|setmatches|setpos|setqflist|setreg|settabvar|settabwinvar|setwinvar|sha256|shellescape|shiftwidth|simplify|sin|sinh|sort|soundfold|spellbadword|spellsuggest|split|sqrt|str2float|str2nr|strcharpart|strchars|strdisplaywidth|strftime|strgetchar|stridx|string|strlen|strpart|strridx|strtrans|strwidth|submatch|substitute|synconcealed|synID|synIDattr|synIDtrans|synstack|system|systemlist|tabpagebuflist|tabpagenr|tabpagewinnr|tagfiles|taglist|tan|tanh|tempname|test_alloc_fail|test_autochdir|test_disable_char_avail|test_garbagecollect_now|test_null_channel|test_null_dict|test_null_job|test_null_list|test_null_partial|test_null_string|test_settime|timer_info|timer_pause|timer_start|timer_stop|timer_stopall|tolower|toupper|tr|trunc|type|undofile|undotree|uniq|values|virtcol|visualmode|wildmenumode|win_findbuf|win_getid|win_gotoid|win_id2tabwin|win_id2win|winbufnr|wincol|winheight|winline|winnr|winrestcmd|winrestview|winsaveview|winwidth|wordcount|writefile|xor)\\b"

 	vimGroup:
 		name: "support.type.group.viml"


Add ANGULAR_APP_NAME to PUBLIC_GLOBALS

Add ANGULAR_APP_NAME to PUBLIC_GLOBALS

--- gulp/config.coffee
+++ gulp/config.coffee
@@ -113,6 +113,7 @@
     #
     # The filtered globals will be available under GulpConfig.PUBLIC_GLOBALS.
     @_PUBLIC_GLOBALS_KEYS = [
+      "ANGULAR_APP_NAME"
       "BUNDLE_NAME"
       "BUNDLE_VERSION"
       "CODE_VERSION"


Fix image URL now API is back up.

Fix image URL now API is back up.

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -73,7 +73,7 @@
       result = JSON.parse(body)['result']
       if result? and result['instanceUrl']? and result['instanceImageUrl']?
         instanceURL = result['instanceUrl']
-        img = "http://memegenerator.net" + result['instanceImageUrl']
+        img = result['instanceImageUrl']
         msg.http(instanceURL).get() (err, res, body) ->
           # Need to hit instanceURL so that image gets generated
           callback img


Need the auth portion.  Doh.

Need the auth portion.  Doh.

--- src/scripts/rimshot-counts.coffee
+++ src/scripts/rimshot-counts.coffee
@@ -9,6 +9,9 @@
 module.exports = (robot) ->
   info   = Url.parse process.env.REDISTOGO_URL || 'redis://localhost:6379'
   client = Redis.createClient(info.port, info.hostname)
+
+  if info.auth
+    client.auth info.auth.split(":")[1]

   robot.respond /(how many rimshots)+\?*/i, (msg) ->
     client.hget "counts", "rimshots", (err, reply) ->


Added nyt getter and callback methods to Individual controller

--- app/scripts/controllers/individual.coffee
+++ app/scripts/controllers/individual.coffee
@@ -6,7 +6,6 @@
     $scope.selected = {rep1: null, rep2: null, zip: null}
     $scope.reps_names_list = []
     $scope.selected_rep = {bioguide_id: "K000381"} #grab from URL
-

     #loading checks
     $scope.loaded_trandparencydata_id = false
@@ -54,6 +53,16 @@
         $scope.selected_rep.bio = data
         $scope.loaded_bio = true
       else console.log "Error: ", error
+
+    $scope.get_nyt = ()->
+      ApiGet.nyt 'members/M000303', $scope.callback_nyt, this
+
+    $scope.callback_nyt = (error, data)->
+      if not error
+        $scope.nyt_data = data
+      else console.log "Error: ", error
+
+    $scope.get_nyt()
   ])
   # .controller('ChartCtrl', ['$scope', '$http', 'ApiGet', ($scope, $http) ->
   #   $scope.source = {}


Remove confusing 'View Class' button from admin page (#548)


webapp/file listing: for admins, always assume project is running -- #3863

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1975,13 +1975,22 @@
         if pay? and pay <= webapp_client.server_time()
             return @render_course_payment_required()

-        public_view = @props.get_my_group(@props.project_id) == 'public'
-
-        if not public_view
+        my_group = @props.get_my_group(@props.project_id)
+
+        # regardless of consequences, for admins a project is always running
+        # see https://github.com/sagemathinc/cocalc/issues/3863
+        if my_group == 'admin'
+            project_state = immutable.Map('state': 'running')
+            project_is_running = true
+        # next, we check if this is a common user (not public)
+        else if my_group != 'public'
             project_state = @props.project_map?.getIn([@props.project_id, 'state'])
             project_is_running = project_state?.get('state') and project_state.get('state') in ['running', 'saving']
         else
             project_is_running = false
+
+        # enables/disables certain aspects if project is viewed publicly by a non-collaborator
+        public_view = my_group == 'public'

         {listing, error, file_map} = @props.displayed_listing


Remove clearing file search on ctrl+click

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -224,10 +224,12 @@

     handle_click: (e) ->
         if window.getSelection().toString() == @state.selection_at_last_mouse_down
+            foreground = misc.should_open_in_foreground(e)
             @props.actions.open_file
                 path       : @fullpath()
-                foreground : misc.should_open_in_foreground(e)
-            @props.actions.set_file_search('')
+                foreground : foreground
+            if foreground
+                @props.actions.set_file_search('')

     handle_download_click: (e) ->
         e.preventDefault()


make share action much more predictable to user (so doesn't feel totally broken to me)

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

     stop_sharing_click: ->
         @props.actions.disable_public_path(@props.checked_files.first())
-        @props.actions.set_file_action()

     render_share_warning: ->
         <Alert bsStyle='warning' style={wordWrap:'break-word'}>
@@ -1556,6 +1555,10 @@
                     </FormGroup>
                     {@render_share_warning() if parent_is_public}
                 </Col>
+                <Col sm={4} style={color:'#666'}>
+
Items

+                    {@render_selected_files_list()}
+                </Col>
                 {<Col sm={4} style={color:'#666'}>

Shared publicly

                     <CopyToClipBoard
@@ -1564,10 +1567,6 @@
                         hide_after    = {true}
                     />
                 </Col> if single_file_data.is_public}
-                <Col sm={4} style={color:'#666'}>
-
Items

-                    {@render_selected_files_list()}
-                </Col>
             </Row>
             <Row>
                 <Col sm={4}>


webapp/files: details around links + help for sharing files (fixing commit 8ff009a7ae9e393400f9c23b66ccc64f1a332ab3)

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

-WIKI_SHARE_HELP_URL = 'https://github.com/sagemathinc/cocalc/wiki/share'
+WIKI_SHARE_HELP_URL = 'https://doc.cocalc.com/share.html'

 ProjectFilesActionBox = rclass
     displayName : 'ProjectFiles-ProjectFilesActionBox'
@@ -1557,7 +1557,7 @@

     render_share_defn: ->

-            Use sharing to make a file or directory visible to the world.   (If you would instead like to privately collaborate and chat with people in this project, go the project Settings tab and "Add people to project".)
+            Use sharing to make a file or directory visible to the world.   (If you would instead like to privately collaborate and chat with people in this project, go the Project Settings tab and "Add people to project".)

     set_public_file_unlisting_to: (new_value) ->
@@ -1702,10 +1702,10 @@
             <FormGroup>
             {if @props.get_total_project_quotas(@props.project_id)?.network then <Radio name="sharing_options" value="public_listed" checked={state == "public_listed"} onChange={handler} inline>
                     <Icon name='eye'/><Space/>
-                    Public (listed) - This will appear on the public share server.
+                    Public (listed) - This will appear on the public share server.
               </Radio> else <Radio disabled={true} name="sharing_options" value="public_listed" checked={state == "public_listed"} inline>
                     <Icon name='eye'/><Space/>
-                    Public (listed) - This will appear on the share server. Public (listed) is only available for projects with network enabled.
+                    Public (listed) - This will appear on the share server. Public (listed) is only available for projects with network enabled.
                 </Radio>}

               <Radio name="sharing_options" value="public_unlisted" checked={state == "public_unlisted"} onChange={handler} inline>


Pass in the current layer and the state name to dynamic state properties.

--- framer/LayerStates.coffee
+++ framer/LayerStates.coffee
@@ -5,21 +5,21 @@
 LayerStatesIgnoredKeys = ["ignoreEvents"]

 class exports.LayerStates extends EventEmitter
-
+
 	constructor: (@layer) ->
-
+
 		@_states = {}
 		@_orderedStates = []
-
+
 		@animationOptions =
 			curve: "spring"
-
+
 		# Always add the default state as the current
 		@add "default", @layer.properties

 		@_currentState = "default"
 		@_previousStates = []
-
+
 	add: (stateName, properties) ->

 		# We also allow an object with states to be passed in
@@ -37,24 +37,24 @@
 		@_states[stateName] = properties

 	remove: (stateName) ->
-
+
 		if not @_states.hasOwnProperty stateName
 			return
-
+
 		delete @_states[stateName]
 		@_orderedStates = _.without @_orderedStates, stateName
-
+
 	switch: (stateName, animationOptions) ->
-
+
 		# Switches to a specific state. If animationOptions are
 		# given use those, otherwise the default options.
-
+
 		if stateName is @_currentState
 			return

 		if not @_states.hasOwnProperty stateName
 			throw Error "No such state: '#{stateName}'"
-
+
 		@emit "willSwitch", @_currentState, stateName, @

 		@_previousStates.push @_currentState
@@ -62,7 +62,7 @@

 		animationOptions ?= @animationOptions
 		animationOptions.properties = {}
-
+
 		animatingKeys = @animatingKeys()

 		for k, v of @_states[stateName]
@@ -75,15 +75,15 @@
 				continue

 			# Allow dynamic properties as functions
-			v = v() if _.isFunction(v)
-
+			v = v.call(@layer, @layer, stateName) if _.isFunction(v)
+
 			animationOptions.properties[k] = v
-
+
 		animation = @layer.animate animationOptions

 		animation.on "stop", =>
 			@emit "didSwitch", _.last @_previousStates, stateName, @
-
+
 	switchInstant: (stateName) ->
 		# Instantly switch to this new state
 		@switch stateName,
@@ -112,14 +112,13 @@
 	next:  ->
 		# TODO: maybe add animationOptions
 		states = Utils.arrayFromArguments arguments
-
+
 		if not states.length
 			states = @states()
-
+
 		@switch Utils.arrayNext(states, @_currentState)

 	last: (animationOptions) ->
 		# Return to last state
 		@switch _.last(@_previousStates), animationOptions
-


Location range changes should render, too

--- src/trix/controllers/editor_controller.coffee
+++ src/trix/controllers/editor_controller.coffee
@@ -88,6 +88,7 @@
   compositionDidRequestLocationRange: (locationRange) ->
     @requestedLocationRange = locationRange
     @editCountWhenLocationRangeRequested = @document.getEditCount()
+    @render() unless @handlingInput

   compositionDidRestoreSnapshot: ->
     @documentController.refreshViewCache()


Get error checking logic right way round

--- code/instance.coffee
+++ code/instance.coffee
@@ -114,7 +114,7 @@
     config = new cf.Config config_name
     hook = {config_name: config.name, file: hook_name}
     @cpdir config.hooks_dir, (exit_code) =>
-      if exit_code is 0
+      if exit_code is not 0
         process.stderr.write "Failed to copy files #{config.hooks_dir}\n"
         callback 'cpdir error'
       else


Patch child_process.fork to run .coffee files

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -6,12 +6,13 @@
 # If included on a webpage, it will automatically sniff out, compile, and
 # execute all scripts present in `text/coffeescript` tags.

-fs        = require 'fs'
-path      = require 'path'
-{Lexer}   = require './lexer'
-{parser}  = require './parser'
-helpers   = require './helpers'
-vm        = require 'vm'
+fs            = require 'fs'
+path          = require 'path'
+child_process = require 'child_process'
+{Lexer}       = require './lexer'
+{parser}      = require './parser'
+helpers       = require './helpers'
+vm            = require 'vm'

 # Load and run a CoffeeScript file for Node, stripping any `BOM`s.
 loadFile = (module, filename) ->
@@ -22,6 +23,15 @@
 if require.extensions
   for ext in ['.coffee', '.litcoffee', '.md', '.coffee.md']
     require.extensions[ext] = loadFile
+
+# Patch child_process.fork to properly run .coffee files
+do (fork = child_process.fork) ->
+  child_process.fork = (modulePath) ->
+    oldExecPath = process.execPath
+    process.execPath = 'coffee' if helpers.isCoffee modulePath
+    output = fork.apply this, arguments
+    process.execPath = oldExecPath
+    output

 # The current CoffeeScript version number.
 exports.VERSION = '1.5.0'


toolClasses -> tools

toolClasses -> tools

--- coffee/jquery.coffee
+++ coffee/jquery.coffee
@@ -11,8 +11,8 @@
   opts.sizeToContainer ?= true
   opts.backgroundShapes ?= []
   opts.watermarkImage ?= null
-  unless 'toolClasses' of opts
-    opts.toolClasses = [
+  unless 'tools' of opts
+    opts.tools = [
       'Pencil',
       'Eraser',
       'Line',
@@ -28,8 +28,7 @@
   unless $el.find('canvas').length
     $el.append('<canvas>')
   lc = new LC.LiterallyCanvas($el.find('canvas').get(0), opts)
-  # (new opts.toolClasses[0]()).select(lc)
-  LC.React.init(el, lc, opts.toolClasses, opts.imageURLPrefix)
+  LC.React.init(el, lc, opts.tools, opts.imageURLPrefix)

   if 'onInit' of opts
     opts.onInit(lc)


dont add if already exist

dont add if already exist

--- coffee/ticket.coffee
+++ coffee/ticket.coffee
@@ -162,7 +162,8 @@
       if not url? or not url? or not id? then return
       for t in tickets when equals(t, {url: url, id: id})
         for k, v of param then t[k] = v
-        if t.show isnt SHOW.NOT
+        found = selectableTickets.some (ele) -> equals t, ele
+        if t.show isnt SHOW.NOT and not found
           selectableTickets.push t
           selectableTickets.sortById()
         break


some azure debugging

some azure debugging

--- coffee_server.coffee
+++ coffee_server.coffee
@@ -59,6 +59,7 @@

 #========== APP CODE
 createApiResponse = (req) =>
+  console.log("request ip", req.ip, req.ips)
   JSON.stringify
     info: JSON.parse(my_api_info),
     result:


remove return in promise function

remove return in promise function

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -825,7 +825,10 @@
     # get back different results!
     if @expression
       answer = @expression.compileToFragments o, LEVEL_PAREN
-      unshiftAfterComments answer, @makeCode "#{@tab}return "
+      rt = "return "
+      if o.scope.promise
+        rt = ""
+      unshiftAfterComments answer, @makeCode "#{@tab}#{rt}"
       # Since the `return` got indented by `@tab`, preceding comments that are
       # multiline need to be indented.
       for fragment in answer
@@ -2922,6 +2925,7 @@

 exports.PromiseCode = class PromiseCode extends Code
   constructor: ({params, body, funcGlyph, paramStart}) ->
+    body = new PromiseBlock([body])
     super(
       params,
       Block.wrap([


fix remoteValidation scope

fix remoteValidation scope

--- _src/coffee/directives/remote-validation/RemoteValidation.coffee
+++ _src/coffee/directives/remote-validation/RemoteValidation.coffee
@@ -41,7 +41,7 @@

         timeoutPromise = $timeout(
           () ->
-            url = mapDataToURL(attrs.remoteValidation, attrs.remoteValidationMapData, scope.$parent)
+            url = mapDataToURL(attrs.remoteValidation, attrs.remoteValidationMapData, scope)
             dataType = attrs.remoteValidationDataType || "json"

             if (dataType == "jsonp")


add interpretation of signature

add interpretation of signature

--- src/renderer/components/doc-signatures-component.coffee
+++ src/renderer/components/doc-signatures-component.coffee
@@ -14,7 +14,9 @@
           elm.push {c.signatures[0].name}
           elm.push (
           c.signatures[0].parameters?.forEach (prm, i) ->
-            elm.push {prm.name}
+            elm.push {prm.name}
+            if prm.defaultValue?
+              elm.push {'?'}
             elm.push :
             elm.push {prm.type.name}
             if prm.type.typeArguments?


WIP conditional class for acceptance status of roles in metadata

--- src/scripts/modules/media/tabs/metadata/metadata.coffee
+++ src/scripts/modules/media/tabs/metadata/metadata.coffee
@@ -103,7 +103,9 @@
         if typeof item is 'string'
           return users[item]
         else
-          user = {id: item.id, text: @displayName(item.fullname, item.id)}
+          user = {id: item.id, text: @displayName(item.fullname, item.id), state: 'pending'}
+          if item.hasAccepted isnt null
+            user.state = if item.hasAccepted then 'accepted' else 'rejected'
           users["#{user.id}"] = user
           return user

@@ -114,7 +116,8 @@
         id: (item) -> item.id
         initSelection: (el, cb) -> cb(userRoles)
         multiple: true
-        formatResult: (item, $container, query) -> $('

').append(item.text)
+        formatSelection: (user) -> "
#{user.text}
"
+        formatResult: (item, $container, query) -> $("

").append(item.text)
         ajax:
           url: "//#{settings.cnxauthoring.host}:#{settings.cnxauthoring.port}/users/search"
           dataType: 'json'
@@ -133,7 +136,6 @@
                 return user
             }

-
     displayName: (fullname, id) ->
       if fullname isnt null
         return "#{fullname} (#{id})"


fixed spacing

fixed spacing

--- app/assets/javascripts/admin/models/dataset/dataset_model.coffee
+++ app/assets/javascripts/admin/models/dataset/dataset_model.coffee
@@ -23,7 +23,7 @@

     response.thumbnailURL = @createThumbnailURL(response.name, response.dataSource.dataLayers)

-    response.formattedCreated =moment(response.created).format("YYYY-MM-DD HH:mm")
+    response.formattedCreated = moment(response.created).format("YYYY-MM-DD HH:mm")

     return response


Disable remove log button for now

Disable remove log button for now

--- assets/scripts/app/views/repo/show.coffee
+++ assets/scripts/app/views/repo/show.coffee
@@ -283,8 +283,9 @@
     ).property('displayRemoveLog', 'hasPermission')

     displayRemoveLog: (->
-      (@get('isJobTab') || (@get('isBuildTab') && @get('build.jobs.length') == 1)) &&
-        @get('build.jobs').objectAt(0).get?('canRemoveLog')
+      #(@get('isJobTab') || (@get('isBuildTab') && @get('build.jobs.length') == 1)) &&
+      #  @get('build.jobs').objectAt(0).get?('canRemoveLog')
+      false
     ).property('isJobTab', 'isBuildTab', 'build.jobs.length', 'job.canRemoveLog')

     canCancelBuild: (->


Split directive into functions

Split directive into functions

--- app/assets/javascripts/darkswarm/directives/map_search.js.coffee
+++ app/assets/javascripts/darkswarm/directives/map_search.js.coffee
@@ -1,18 +1,27 @@
-Darkswarm.directive 'mapSearch', ($timeout)->
+Darkswarm.directive 'mapSearch', ($timeout) ->
   # Install a basic search field in a map
   restrict: 'E'
   require: '^googleMap'
   replace: true
   template: '<input id="pac-input" placeholder="' + t('location_placeholder') + '"></input>'
-  link: (scope, elem, attrs, ctrl)->
+  scope: {}
+  link: (scope, elem, attrs, ctrl) ->
     $timeout =>
       map = ctrl.getMap()

-      # Use OSM tiles server
-      map.mapTypes.set 'OSM', new (google.maps.ImageMapType)(
+      # Does this *really* belong here? It's not about search.
+      scope.useOsmTiles map
+
+      searchBox = scope.createSearchBox map
+      scope.respondToSearch map, searchBox
+      scope.biasResults map, searchBox
+
+
+    scope.useOsmTiles = (map) ->
+      map.mapTypes.set 'OSM', new google.maps.ImageMapType
         getTileUrl: (coord, zoom) ->
           # "Wrap" x (logitude) at 180th meridian properly
-          # NB: Don't touch coord.x because coord param is by reference, and changing its x property breakes something in Google's lib
+          # NB: Don't touch coord.x because coord param is by reference, and changing its x property breaks something in Google's lib
           tilesPerGlobe = 1 << zoom
           x = coord.x % tilesPerGlobe
           if x < 0
@@ -21,19 +30,22 @@
           'http://tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png'
         tileSize: new (google.maps.Size)(256, 256)
         name: 'OpenStreetMap'
-        maxZoom: 18)
+        maxZoom: 18

-      input = (document.getElementById("pac-input"))
+    scope.createSearchBox = (map) ->
+      input = document.getElementById("pac-input")
       map.controls[google.maps.ControlPosition.TOP_LEFT].push input
-      searchBox = new google.maps.places.SearchBox((input))
+      return new google.maps.places.SearchBox(input)

+    scope.respondToSearch = (map, searchBox) ->
       google.maps.event.addListener searchBox, "places_changed", ->
         places = searchBox.getPlaces()
         for place in places when place.geometry.viewport?
           map.fitBounds place.geometry.viewport

-      # Bias the SearchBox results towards places that are within the bounds of the
-      # current map's viewport.
+    # Bias the SearchBox results towards places that are within the bounds of the
+    # current map's viewport.
+    scope.biasResults = (map, searchBox) ->
       google.maps.event.addListener map, "bounds_changed", ->
         bounds = map.getBounds()
         searchBox.setBounds bounds


'bundle' script lint bugs fixed

'bundle' script lint bugs fixed

--- scripts/bundle.coffee
+++ scripts/bundle.coffee
@@ -27,17 +27,22 @@
             return console.error err?.stack or err

         try
-            template = fs.readFileSync "./scripts/bundle/#{opts.platform}.coffee.mustache", 'utf-8'
+            tmplSrc = "./scripts/bundle/#{opts.platform}.coffee.mustache"
+            template = fs.readFileSync tmplSrc, 'utf-8'
             template = coffee.compile template, bare: true
         try
-            template ||= fs.readFileSync "./scripts/bundle/#{opts.platform}.js.mustache", 'utf-8'
-        template ||= fs.readFileSync "./scripts/bundle/standard.js.mustache", 'utf-8'
+            tmplSrc = "./scripts/bundle/#{opts.platform}.js.mustache"
+            template ||= fs.readFileSync tmplSrc, 'utf-8'
+        tmplSrc = "./scripts/bundle/standard.js.mustache"
+        template ||= fs.readFileSync tmplSrc, 'utf-8'

         mode = if opts.release then 'release' else 'develop'

         template = Mustache.render template, neftCode: bundle

-        extrasText = if opts.extras then "#{Object.keys(opts.extras).sort().join('-')}-" else ""
+        extrasText = ''
+        if opts.extras
+            extrasText = "#{Object.keys(opts.extras).sort().join('-')}-"
         name = "#{opts.platform}-#{extrasText}#{mode}"

         fs.writeFileSync "./cli/bundle/neft-#{name}.js", template
@@ -46,23 +51,23 @@
         callback()

 TYPES = [
-    { platform: 'node' },
-    { platform: 'browser' },
-    { platform: 'browser', extras: {game: true} },
-    { platform: 'qt' },
-    { platform: 'android' },
-    { platform: 'ios' },
+    {platform: 'node'},
+    {platform: 'browser'},
+    {platform: 'browser', extras: {game: true}},
+    {platform: 'qt'},
+    {platform: 'android'},
+    {platform: 'ios'},
 ]

 stack = new utils.async.Stack

 for type in TYPES
     if utils.has(process.argv, "--#{type.platform}")
-        opts = { release: false }
+        opts = {release: false}
         utils.merge opts, type
         stack.add createBundle, null, [opts]
     if utils.has(process.argv, "--#{type.platform}-release")
-        opts = { release: true }
+        opts = {release: true}
         utils.merge opts, type
         stack.add createBundle, null, [opts]


Send error

Send error

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -5,7 +5,7 @@
 constants = require './constants.coffee'

 dataHelper = new DataHelper
-helpText = "Use the `help` command to learn about using the bot, or check out the full README here: https://github.com/RuCray/destiny_weekly_update"
+helpText = "Check out the full README here: https://github.com/RuCray/destiny_weekly_update"

 module.exports = (robot) ->
   # executes when any text is directed at the bot
@@ -40,12 +40,14 @@
       data['activityKey'] = activityKey

     getPublicWeeklyActivity(res, data.activityKey).then (activityDetails) ->
-
       payload =
         message: res.message
         attachments: [dataHelper.parseActivityDetails(activityDetails)]

       robot.emit 'slack-attachment', payload
+
+    .fail (err) ->
+      sendError(robot, res, err)

     # # interprets input based on length
     # # if 3 elements, assume: gamertag, network, bucket
@@ -85,11 +87,11 @@
     #         robot.emit 'slack-attachment', payload

-  robot.respond /help/i, (res) ->
-    sendHelp(robot, res)
-
-  robot.respond /!help/i, (res) ->
-    sendHelp(robot, res)
+  # robot.respond /help/i, (res) ->
+  #   sendHelp(robot, res)
+
+  # robot.respond /!help/i, (res) ->
+  #   sendHelp(robot, res)

 sendHelp = (robot, res) ->
@@ -135,7 +137,6 @@
     attachments: attachment

   robot.emit 'slack-attachment', payload
-

 checkNetwork = (network) ->
   xbox = ['xbox', 'xb1', 'xbox1', 'xboxone', 'xbox360', 'xb360', 'xbone', 'xb']


Refactor

Refactor

--- api/apps/articles/model/save.coffee
+++ api/apps/articles/model/save.coffee
@@ -9,15 +9,15 @@
 xss = require 'xss'
 cheerio = require 'cheerio'
 url = require 'url'
+Q = require 'bluebird-q'
 request = require 'superagent'
 requestBluebird = require 'superagent-bluebird-promise'
+Backbone = require 'backbone'
+{ ArtworkHelpers } = require 'artsy-backbone-mixins'
 debug = require('debug') 'api'
 schema = require './schema'
 Article = require './index'
 { ObjectId } = require 'mongojs'
-Backbone = require 'backbone'
-Artwork = require '../../../../client/models/artwork.coffee'
-Q = require 'bluebird-q'
 cloneDeep = require 'lodash.clonedeep'
 { ARTSY_URL, SAILTHRU_KEY, SAILTHRU_SECRET,
 EMBEDLY_KEY, FORCE_URL, ARTSY_EDITORIAL_ID, SECURE_IMAGES_URL } = process.env
@@ -92,68 +92,35 @@
     cb(null, article)

 @generateArtworks = (input, article, accessToken, cb) ->
+  article.sections = input.sections
   return cb(null, article) unless _.some input.sections, type: 'artworks'
   # Try to fetch and denormalize the artworks from Gravity asynchonously
   artworkIds = _.pluck (_.where input.sections, type: 'artworks' ), 'ids'
-  dfds = []
-  Q.allfor artworkId in _.flatten artworkIds
-    dfds.push(requestBluebird.get("#{ARTSY_URL}/api/v1/artwork/#{artworkId}").set('X-Xapp-Token': accessToken).promise()
-    )
-  Q.all(dfds).then (responses) =>
-    # console.log responses
-    # console.log _.pluck responses 'value'
-    # # FOR ARTWORKS THAT DON'T EXIST, THE ENTIRE CALLBACK COMES BACK WITH AN ERROR
-    # fetchedArtworks = _.map results, (result) -> result.body
-    # # Push fetched artworks to the section if they are available
-    # newSections = []
-    # for section in input.sections
-    #   if section.type is 'artworks'
-    #     newSection = _.extend _.clone(section), artworks: []
-    #     console.log "newSection cloned and with extended artworks"
-    #     console.log newSection
-    #     for artworkId in section.ids
-    #       artwork = _.findWhere fetchedArtworks, _id: artworkId
-    #       if artwork
-    #         console.log "Found an artwork"
-    #         newSection.artworks.push denormalizedArtworkData artwork
-    #       else
-    #         newSection.ids = _.without section.ids, artworkId
-    #         console.log "newSection.ids"
-    #         console.log newSection.ids
-    #     # Section shouldn't be added if there are no artworks
-    #     newSection = {} if newSection.artworks.length is 0
-    #   else
-    #     newSection = section
-    #   newSections.push newSection unless _.isEmpty newSection
-
-    # console.log newSections
-    # Finally return callback with updated article
-    # article.sections = newSections
-    article.sections = input.sections
+  Q.allSettled( for artworkId in _.flatten artworkIds
+    requestBluebird
+      .get("#{ARTSY_URL}/api/v1/artwork/#{artworkId}")
+      .set('X-Xapp-Token': accessToken)
+  ).done (responses) =>
+    fetchedArtworks = _.map responses, (res) ->
+      res.value?.body
+    newSections = []
+    for section in cloneDeep input.sections
+      if section.type is 'artworks'
+        section.artworks = []
+        for artworkId in section.ids
+          artwork = _.findWhere fetchedArtworks, _id: artworkId
+          if artwork
+            section.artworks.push denormalizedArtworkData artwork
+          else
+            section.ids = _.without section.ids, artworkId
+        # Section shouldn't be added if there are no artworks
+        section = {} if section.artworks.length is 0
+      newSections.push section unless _.isEmpty section
+    article.sections = newSections
     cb(null, article)

-  # async.parallel callbacks, (err, results) =>
-  #   fetchedArtworks = results.map (result) -> result.body
-  #   # Push fetched artworks to the section if they are available
-  #   newSections = _.clone input.sections
-  #   for section in newSections when section.type is 'artworks'
-  #     if section.type is 'artworks'
-  #       section.artworks = []
-  #       for artworkId in section.ids
-  #         artwork = _.findWhere fetchedArtworks, _id: artworkId
-  #         if artwork
-  #           section.artworks.push denormalizedArtworkData artwork
-  #         else
-  #           section.ids = _.without section.ids, artworkId
-  #   # Do not include sections that have no valid artworks
-  #   newSections = _.filter newSections, (section) ->
-  #     section.type isnt 'artworks' or section.ids?.length > 0
-  #   # Finally return callback with updated article
-  #   article.sections = newSections
-  #   cb(null, article)
-
-
 denormalizedArtworkData = (artwork) ->
+  Artwork = Backbone.Model.extend ArtworkHelpers
   artwork = new Artwork artwork
   denormalizedArtwork =
     id: artwork.get('_id')
@@ -169,7 +136,7 @@
       slug: artwork.get('artist').id

 getPartnerName = (artwork) ->
-  if artwork.get('collection_institution')?.length > 0
+  if artwork.get('collecting_institution')?.length > 0
     artwork.get('collecting_institution')
   else if artwork.get('partner')
     artwork.get('partner').name
@@ -196,7 +163,8 @@
   User.findOrInsert authorId, accessToken, (err, author) ->
     return callback err if err
     publishing = (input.published and not article.published) || (input.scheduled_publish_at and not article.published)
-    article = _.extend article, input, updated_at: new Date
+    article = _.extend article, _.omit(input, 'sections'), updated_at: new Date
+    article.sections = [] unless input.sections?.length
     article.author = User.denormalizedForArticle author if author
     cb null, article, author, publishing


proper stub data

proper stub data

--- api/apps/articles/test/model.coffee
+++ api/apps/articles/test/model.coffee
@@ -348,12 +348,12 @@
     it 'saves an article to a gravity post', (done) ->
       article = _.extend fixtures().articles, gravity_id: null
       article.sections[0] = { type: 'text', body: '' }
-      article.sections[3].ids = ['foo', 'bar']
+      article.sections[3].ids = ['4f5f64c23b555230ac00047b', '4f5f64c23b555230ac000472']
       Article.syncToPost article, 'foo-token', (err, post) ->
         post.title.should.equal article.title
         post.body.should.equal 'Just before the lines start forming...

10. Lisson Gallery

Mia Bergeron merges the personal and universal...Check out this video art:'
         post.published.should.be.ok
-        _.pluck(post.artworks, 'id').join('').should.equal 'foobar'
+        _.pluck(post.artworks, 'id').join('').should.equal '4f5f64c23b555230ac00047b4f5f64c23b555230ac000472'
         _.pluck(post.content_links, 'url').join('').should.equal 'http://gemini.herokuapp.com/123/miaart-banner.jpghttp://youtu.be/yYjLrJRuMnY'
         done()


fix spelling

fix spelling

--- browser/coffee/controller.coffee
+++ browser/coffee/controller.coffee
@@ -30,7 +30,7 @@
     connection.onmessage = ({data}) -> press data
     connection.onclose = -> setTimeout ->
         newDelay = delay * 1.5
-        console.log "server not availabe, trying again in #{newDelay} milliseconds"
+        console.log "server not available, trying again in #{newDelay} milliseconds"
         open newDelay
     , delay


home/stacks: show old user's user name with stack name

--- client/home/lib/stacks/components/stacktemplateitem/index.coffee
+++ client/home/lib/stacks/components/stacktemplateitem/index.coffee
@@ -17,7 +17,7 @@

   render: ->

-    { template, onOpen } = @props
+    { template, stack, onOpen } = @props

     editorUrl = "/Stack-Editor/#{template.get '_id'}"

@@ -26,7 +26,7 @@
         href={editorUrl}
         className='HomeAppViewListItem-label'
         onClick={onOpen}>
-        {_.unescape template.get 'title'}
+        { makeTitle { template, stack } }
       </a>
       <div className='HomeAppViewListItem-description'>
         Last updated <TimeAgo from={template.getIn ['meta', 'modifiedAt']} />
@@ -35,3 +35,13 @@
         {@renderButton()}
       </div>
     </div>
+
+makeTitle = ({ template, stack }) ->
+
+  title = _.unescape template.get 'title'
+
+  return title  unless stack
+  return title  unless oldOwner = stack.getIn(['config', 'oldOwner'])
+
+  return "#{title} (@#{oldOwner})"
+


ContentSearch: Fixed encoding issue.

ContentSearch: Fixed encoding issue.

--- client/ide/views/contentsearch/idecontentsearchresultview.coffee
+++ client/ide/views/contentsearch/idecontentsearchresultview.coffee
@@ -27,7 +27,8 @@
           if line.occurence
             flags    = if isCaseSensitive then 'g' else 'gi'
             regExp   = new RegExp searchText, flags
-            replaced = line.line.replace regExp, (match) -> """
#{match}
"""
+            encoded  = Encoder.htmlEncode line.line
+            replaced = encoded.replace regExp, (match) -> """
#{match}
"""
           else
             replaced = "#{Encoder.htmlEncode line.line}"


Ignore port information from host header.

--- src/index.coffee
+++ src/index.coffee
@@ -44,7 +44,8 @@
   async.waterfall [
     # Determine host from header
     (done) ->
-      host = req.headers.host
+      hostHeader = req.headers.host
+      host = hostHeader.split(':', 1)[0]
       for suffixRegexp in SUFFIXES
         if suffixRegexp.test(host)
           host = host.replace(suffixRegexp, "")


simplication

simplication

--- src/index.coffee
+++ src/index.coffee
@@ -26,16 +26,9 @@
     return @props[name] = val if Utils.isFunction(val)
     @props[name]?(val)

-  bindDom: (node, data) =>
-    return if @_dataFor(node)
-    ko.applyBindings(data, node)
+  bindDom: (node, data) => ko.applyBindings(data, node)

   unbindDom: (node) -> ko.cleanNode(node)
-
-  _dataFor: (node) =>
-    return true if Object.keys(node).some((test)-> test.indexOf('__ko__') is 0)
-    return false if not node.parentNode or node.parentNode?.nodeName in Utils.excludeTags
-    @_dataFor(node.parentNode)

   ###
   # knockout explicit memory safe computed for synchronizing values


Remove callback on timeout. Load script asynchronously.

--- src/jsonp.coffee
+++ src/jsonp.coffee
@@ -47,13 +47,14 @@
       window.setTimeout( ->
                            if !returned
                              returned = true
+                             JsonP.removeCallback.call(elem, callbackName)
                              options.error({typename: 'problem', subtype: 500, messages: [{typename: "timeout", item: "The server took longer than #{options.timeout} ms to reply"}]})
         , options.timeout)

     elem = document.createElement("script")
     elem.setAttribute("type","text/javascript")
-    # onreadystatechange is for IE, onload for everyone else
-    elem.onload = elem.onreadystatechange =
-      () -> JsonP.removeCallback.call(elem, callbackName)
+    elem.setAttribute("async", "true")
+    # onreadystatechange is for IE, onload/onerror for everyone else
+    elem.onload = elem.onreadystatechange = -> JsonP.removeCallback.call(elem, callbackName)
     elem.setAttribute("src", url)
     document.getElementsByTagName("head")[0].appendChild(elem)


update label

update label

--- lib/edit-in-new-tab.coffee
+++ lib/edit-in-new-tab.coffee
@@ -20,7 +20,7 @@
       type: "string",
       default: "",
       enum: [
-        { value: "", description: "(none)"}
+        { value: "", description: "(current)"}
         { value: 'up', description: 'Split Up'}
         { value: 'down', description: 'Split Down'}
         { value: 'left', description: 'Split Left'}


Bugfix

Bugfix

--- app/assets/javascripts/heros.coffee
+++ app/assets/javascripts/heros.coffee
@@ -11,7 +11,8 @@

     if action == 'plus'
       val += 1
-    else
+    else if action == 'minus'
+      val -= 1

     html_part.html( val.toString() )


OX-137 console output that illustrates the problem, no solution yet

--- app/assets/javascripts/input.coffee
+++ app/assets/javascripts/input.coffee
@@ -50,7 +50,7 @@

     KeyboardJS.bind.key(
       key
-      (evt) =>
+      (evt, keys, key2) =>
         # When first pressed, insert the callback into
         # keyCallbackMap and start the buttonLoop.
         # Then, ignore any other events fired from the operating
@@ -58,6 +58,7 @@
         # When control key is pressed, everything is ignored, because
         # if there is any browser action attached to this (as with Ctrl + S)
         # KeyboardJS does not receive the up event.
+        console.log key2 + " down"
         unless @keyCallbackMap[key]?
           if not evt.ctrlKey
             @keyPressedCount++
@@ -65,7 +66,12 @@
             @buttonLoop() if @keyPressedCount == 1

         return
-      =>
+      (evt, keys, key2) =>
+        activeKeys = KeyboardJS.activeKeys()
+        console.log key2 + " up"
+        #for key of @keyCallbackMap
+        #  if activeKeys.indexOf(key) < 0
+        #    delete @keyCallbackMap[key]
         if @keyCallbackMap[key]?
           @keyPressedCount--
           delete @keyCallbackMap[key]


Intro page: fix load scenario button

Intro page: fix load scenario button

The preset scenario id wasn't updated after the mouse hover

--- app/assets/javascripts/intro.coffee
+++ app/assets/javascripts/intro.coffee
@@ -18,10 +18,13 @@
     $("#existing_scenario li").removeClass('active')
     $(this).addClass('active')
     $("#load_existing_scenario  #description  #text").html($(this).data 'description')
+    # update the hidden input, too
+    scenario_id = $(this).closest('li').data('scenario-id')
+    $("#load_existing_scenario input#id").val scenario_id
     $("#load_existing_scenario").show()

   $("#existing_scenario li").click (e) ->
     e.preventDefault()
     scenario_id = $(e.target).closest('li').data('scenario-id')
     $("#load_existing_scenario input#id").val scenario_id
-    $("#load_existing_scenario form").submit()+    $("#load_existing_scenario form").submit()


Added tags to search documents

Added tags to search documents

--- app/assets/javascripts/posts.coffee
+++ app/assets/javascripts/posts.coffee
@@ -79,6 +79,7 @@
   index = elasticlunr ->
     @addField 'title'
     @addField 'body'
+    @addField 'tags'
     @setRef 'id'
     return

@@ -86,25 +87,36 @@
   'id': 1
   'title': 'A16'
   'body': 'ICT Computer room.'
+  'tags' : 'IT ICT'
   'annotation': 'Annotation.A.A16'
   doc2 =
   'id': 2
   'title': 'IT Support'
   'body': 'ICT Support for all ICT support related queries.'
+  'tags' : 'IT support'
   'annotation': 'Annotation.A.IT.Support'
   doc3 =
   'id': 3
   'title': 'A15'
   'body': 'Sociology classroom'
+  'tag': 'sociology'
   'annotation': 'Annotation.A.A15'
+  doc4 =
+  'id': 4
+  'title': 'A14'
+  'body': 'Religious Studies classroom'
+  'tags': 'RE RS religion religious'
+  'annotation': 'Annotation.A.A14'

   index.addDoc doc1
   index.addDoc doc2
   index.addDoc doc3
+  index.addDoc doc4

 search = (text) ->
   console.log("search for: " + text)
   results = index.search text, fields:
+    tags: boost: 3
     title: boost: 2
     body: boost: 1
   console.log(results)
@@ -118,6 +130,7 @@
   for result in results
     output += '' + result.doc.title + '' + '
'
     output += result.doc.body + '
'
+    output += 'tags: ' + result.doc.tags + '
'
     output += result.doc.annotation + '
'
     output += '
'


relax condition on readyState in thyme player

--- app/assets/javascripts/thyme.coffee
+++ app/assets/javascripts/thyme.coffee
@@ -342,7 +342,7 @@
   # after video metadata have been loaded, display chapters and metadat in the
   # interactive area
   $(video).on 'loadedmetadata', ->
-    if chaptersElement.readyState == 2
+    if chaptersElement.readyState >= 1
       displayChapters()
       displayMetadata()
     else


Make it work

Make it work

--- app/assets/javascripts/todos.coffee
+++ app/assets/javascripts/todos.coffee
@@ -9,17 +9,15 @@
     defaults:
       title: ''
       completed: false
-    url: 'todos'
+    rootUrl: 'todos'
     toggle: ->
-      @save completed: !@get('completed'), { patch: true }
-      return
+      @save completed: !@get('completed')
   )
   #--------------
   # Collections
   #--------------
   app.TodoList = Backbone.Collection.extend(
     model: app.Todo
-    # localStorage: new Store('backbone-todo')
     url: 'todos'
     completed: ->
       @filter (todo) ->
@@ -32,10 +30,8 @@
   #--------------
   # Views
   #--------------
-  # renders individual todo items list (li)
   app.TodoView = Backbone.View.extend(
     tagName: 'li'
-    # template: _.template($('#item-template').html())
     template: _.template('

         <input class="toggle" type="checkbox" <%= completed ? "checked" : "" %>>
         <%- title %>
@@ -46,12 +42,9 @@
       @$el.html @template(@model.toJSON())
       @input = @$('.edit')
       this
-      # enable chained calls
     initialize: ->
       @model.on 'change', @render, this
       @model.on 'destroy', @remove, this
-      # remove: Convenience Backbone's function for removing the view from the DOM.
-      return
     events:
       'dblclick label': 'edit'
       'keypress .edit': 'updateOnEnter'
@@ -61,63 +54,46 @@
     edit: ->
       @$el.addClass 'editing'
       @input.focus()
-      return
     close: ->
       value = @input.val().trim()
       if value
-        @model.save title: value, { patch: true }
+        @model.save title: value
       @$el.removeClass 'editing'
-      return
     updateOnEnter: (e) ->
       if e.which == 13
         @close()
-      return
     toggleCompleted: ->
       @model.toggle()
-      return
     destroy: ->
       @model.destroy()
-      return
   )
   # renders the full list of todo items calling TodoView for each one.
   app.AppView = Backbone.View.extend(
     el: '#todoapp'
     initialize: ->
       @input = @$('#new-todo')
-      app.todoList.on 'add', @addAll, this
+      app.todoList.on 'add', @addOne, this
       app.todoList.on 'reset', @addAll, this
-      app.todoList.fetch(reset: true)
-      # console.log @$el
-      # Loads list from local storage
-      return
+      app.todoList.fetch()
     events:
       'keypress #new-todo': 'createTodoOnEnter'
     createTodoOnEnter: (e) ->
-      if e.which != 13 or !@input.val().trim()
-        # ENTER_KEY = 13
-        return
+      return if e.which != 13 or !@input.val().trim() # ENTER_KEY = 13
       app.todoList.create @newAttributes()
       @input.val ''
-      # clean input box
-      return
     addOne: (todo) ->
       view = new (app.TodoView)(model: todo)
       $('#todo-list').append view.render().el
-      return
     addAll: ->
       @$('#todo-list').html ''
-      app.todoList.each @addOne, this
-      # clean the todo list
-      # filter todo item list
-      # switch window.filter
-      #   when 'pending'
-      #     _.each app.todoList.remaining(), @addOne
-      #   when 'completed'
-      #     _.each app.todoList.completed(), @addOne
-      #   else
-      #     app.todoList.each @addOne, this
-      #     break
-      return
+      switch window.filter
+        when 'pending'
+          _.each app.todoList.remaining(), @addOne
+        when 'completed'
+          _.each app.todoList.completed(), @addOne
+        else
+          app.todoList.each @addOne, this
+          break
     newAttributes: ->
       {
         title: @input.val().trim()
@@ -127,19 +103,18 @@
   #--------------
   # Routers
   #--------------
-  # app.Router = Backbone.Router.extend(
-  #   routes: '*filter': 'setFilter'
-  #   setFilter: (params) ->
-  #     console.log 'app.router.params = ' + params
-  #     # window.filter = params.trim() or ''
-  #     app.todoList.trigger 'reset'
-  #     return
-  # )
+  app.Router = Backbone.Router.extend(
+    routes: '*filter': 'setFilter'
+    setFilter: (params) ->
+      console.log 'app.router.params = ' + params
+      window.filter =  if params? then params.trim() else ''
+      app.todoList.trigger 'reset'
+  )
   #--------------
   # Initializers
   #--------------
-  #app.router = new (app.Router)
-  #Backbone.history.start()
+  app.router = new (app.Router)
+  Backbone.history.start()
   app.appView = new (app.AppView)

 $(document).ready(ready)


script/master.coffee: only respond when bot is mentioned

--- scripts/master.coffee
+++ scripts/master.coffee
@@ -12,19 +12,19 @@
 Sugar = require 'sugar'

 module.exports = (robot) ->
-  robot.hear /freeze ?(.*)/i, (msg) ->
+  robot.respond /freeze ?(.*)/i, (msg) ->
     Project.find msg.match[1], msg, (project) ->
       project.freeze msg.message.user
-      msg.reply project.shortDescription()
+      msg.send project.shortDescription()

-  robot.hear /thaw ?(.*)/i, (msg) ->
+  robot.respond /thaw ?(.*)/i, (msg) ->
     Project.find msg.match[1], msg, (project) ->
       project.thaw msg.message.user
-      msg.reply project.shortDescription()
+      msg.send project.shortDescription()

-  robot.hear /(?:frozen\?|master|merge\?|thawed\?) ?(.*)/i, (msg) ->
+  robot.respond /(?:frozen\?|master|merge\?|thawed\?) ?(.*)/i, (msg) ->
     Project.find msg.match[1], msg, (project) ->
-      msg.reply project.fullDescription()
+      msg.send project.fullDescription()

 class Project
   @PROJECTS:


Unfragile spec failures due to help dialog

The help dialog currently sets an instance variable when init-ed, and
it's kind of being used as a global singleton.  This means that if
it's initialized, used, and then something else wipes the dom, the
next time it gets engaged, it thinks it's already been initialized
and then tries to call methods that the DOM isn't ready for.

Lesson:  you can't count on some global semaphore to be a proxy
for DOM state

Change-Id: Ic5f0216fe9341ada85c30545a894a1a500f09b52
Reviewed-on: https://gerrit.instructure.com/52341
Tested-by: Jenkins
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
QA-Review: August Thornton <9adc7a1161ddf32ff608de792a7e50179545f026@instructure.com>
Product-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>

--- app/coffeescripts/helpDialog.coffee
+++ app/coffeescripts/helpDialog.coffee
@@ -76,7 +76,7 @@
       }, {
         step: =>
           #reposition vertically to reflect current height
-          @initDialog() unless @dialogInited
+          @initDialog() unless @dialogInited and @$dialog?.hasClass("ui-dialog-content")
           @$dialog?.dialog('option', 'position', 'center')
         duration: 100
         complete: ->
@@ -95,7 +95,7 @@
       @$dialog.dialog 'option', 'title', newTitle

     open: ->
-      helpDialog.initDialog() unless helpDialog.dialogInited
+      helpDialog.initDialog() unless helpDialog.dialogInited and helpDialog.$dialog?.hasClass("ui-dialog-content")
       helpDialog.$dialog.dialog('open')
       helpDialog.initTeacherFeedback()


fix y3y case

fix y3y case

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -217,11 +217,10 @@
     @vimState.toggleClassList('with-count', true)

   buildCountString: ->
-    normalCount = String(@count['normal'])
-    if @count['operator-pending']?
-      normalCount + ' * ' + String(@count['operator-pending'])
-    else
-      normalCount
+    [@count['normal'], @count['operator-pending']]
+      .filter (count) -> count?
+      .map (count) -> String(count)
+      .join(' x ')

   resetCount: ->
     @count = {}


:art: refactoring

:art: refactoring

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -1,14 +1,15 @@
 # Refactoring status: 100%
 _ = require 'underscore-plus'
+
 {CurrentSelection} = require './motion'
 {Select} = require './operator'
 {debug} = require './utils'
 settings = require './settings'

-module.exports =
 class OperationStack
   constructor: (@vimState) ->
     @stack = []
+    {@editor} = @vimState

   push: (op) ->
     if @isEmpty() and settings.get('debug')
@@ -18,24 +19,18 @@

     # Use implicit Select operator as operator.
     if @vimState.isMode('visual') and _.isFunction(op.select)
-      debug "push IMPLICIT Operator.Select"
-      @stack.push(new Select(@vimState))
+      @pushToStack new Select(@vimState), message: "push IMPLICIT Operator.Select"

-    debug "pushing <#{op.getKind()}>"
-    @stack.push op
+    @pushToStack op, message: "push <#{op.getKind()}>"

     # Operate on implicit CurrentSelection TextObject.
     if @vimState.isMode('visual') and op.isOperator()
-      debug "push IMPLICIT Motion.CurrentSelection"
-      @stack.push(new CurrentSelection(@vimState))
+      @pushToStack new CurrentSelection(@vimState),
+        message: "push IMPLICIT Motion.CurrentSelection"

-    @withLock =>
-      @process()
-
-  withLock: (fn) ->
     try
       @processing = true
-      fn()
+      @process()
     finally
       @processing = false

@@ -58,7 +53,17 @@
         else
           throw error

-    unless @peekTop().isComplete()
+    if @peekTop().isComplete()
+      @inspect()
+      debug '-> @pop()'
+      op = @pop()
+      debug " -> <#{op.getKind()}>.execute()"
+      op.execute()
+
+      @vimState.recordOperation(op) if op.isRecordable()
+      @finish()
+      debug "#=== Finish at #{new Date().toISOString()}\n"
+    else
       if @vimState.isMode('normal') and @peekTop().isOperator?()
         @inspect()
         debug '-> @process(): activating: operator-pending-mode'
@@ -66,44 +71,35 @@
       else
         debug "-> @process(): return: not <#{@peekTop().getKind()}>.isComplete()"
         @inspect()
-      return
-
-    @inspect()
-    debug '-> @pop()'
-    op = @pop()
-    debug " -> <#{op.getKind()}>.execute()"
-    op.execute()
-
-    @vimState.recordOperation(op) if op.isRecordable()
-    @finish()
-    debug "#=== Finish at #{new Date().toISOString()}\n"

   cancel: ->
     debug "Cancelled stack size: #{@stack.length}"
-    for op in @pop()
-      debug  op.getKind()
+    debug(op.getKind()) for op in @pop()
     unless @vimState.isMode('visual') or @vimState.isMode('insert')
       @vimState.activate('reset')
     @finish()
     debug "#=== Canceled at #{new Date().toISOString()}\n"

   finish: ->
-    {editor} = @vimState
     if @vimState.isMode('normal')
-      if editor.getLastSelection().isEmpty()
+      if @editor.getLastSelection().isEmpty()
         @dontPutCursorsAtEndOfLine()
     if @vimState.isMode('visual', 'blockwise')
       @vimState.showCursors()
     @vimState.reset()

   dontPutCursorsAtEndOfLine: ->
-    for c in @vimState.editor.getCursors() when c.isAtEndOfLine() and not c.isAtBeginningOfLine()
+    for c in @editor.getCursors() when c.isAtEndOfLine() and not c.isAtBeginningOfLine()
       {goalColumn} = c
       c.moveLeft()
       c.goalColumn = goalColumn

   peekTop: ->
     _.last @stack
+
+  pushToStack: (operation, {message}={}) ->
+    debug message if message?
+    @stack.push(operation)

   pop: ->
     @stack.pop()
@@ -119,3 +115,5 @@

   inspect: ->
     @vimState.developer?.inspectOperationStack()
+
+module.exports = OperationStack


remove comment

remove comment

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -116,7 +116,6 @@

   reset: ->
     @stack = []
-    # @operation = null
     @processing = false
     @subscriptions?.dispose()
     @subscriptions = new CompositeDisposable


remove global debug toolbar

remove global debug toolbar

--- lib/package/toolbar.coffee
+++ lib/package/toolbar.coffee
@@ -61,29 +61,5 @@
       tooltip: 'Stop Julia'
       iconset: 'ion'

-    @bar.addSpacer()
-
-    # Debugging
-
-    @bar.addButton
-      icon: 'arrow-down'
-      callback: 'julia-debug:step-to-next-line'
-      tooltip: 'Debug: Step to Next Line'
-
-    @bar.addButton
-      icon: 'link-external'
-      callback: 'julia-debug:finish-function'
-      tooltip: 'Debug: Finish Function'
-
-    @bar.addButton
-      icon: 'move-right'
-      callback: 'julia-debug:step-into-function'
-      tooltip: 'Debug: Step into Function'
-
-    @bar.addButton
-      icon: 'chevron-right'
-      callback: 'julia-debug:step-to-next-expression'
-      tooltip: 'Debug: Step to Next Expression'
-
   deactivate: ->
     @bar?.removeItems()


restructured, extracted out AutoCompleteSearchview

--- core/app/backbone/views/autocompleted_add_to_channel_view.coffee
+++ core/app/backbone/views/autocompleted_add_to_channel_view.coffee
@@ -1,16 +1,26 @@
 #= require ./generic/text_input_view
-searchCollection = (type)->
-  model = new Backbone.Model text: ''
-  collection = new type()
-  model.on 'change', -> collection.searchFor model.get('text')

-  [model, collection]
+class AutocompleteSearchView extends Backbone.Marionette.Layout
+  searchCollection: (type)->
+    model = new Backbone.Model text: ''
+    collection = new type()
+    model.on 'change', -> collection.searchFor model.get('text')

-class window.AutoCompletedAddToChannelView extends Backbone.Marionette.Layout
+    [model, collection]
+
+  bindTextViewToSteppableViewAndSelf: (text_view, steppable_view)->
+    @bindTo text_view, 'down', -> steppable_view.moveSelectionDown()
+    @bindTo text_view, 'up',    -> steppable_view.moveSelectionUp()
+    @bindTo text_view, 'return', @addCurrent, this
+
+  addCurrent: ->
+    console.error "the function to add current selection was not implemented"
+
+class window.AutoCompletedAddToChannelView extends AutocompleteSearchView
   tagName: "div"
   className: "add-to-channel"
   events:
-    "click div.auto_complete": "addCurrentlySelectedChannel"
+    "click div.auto_complete": "addCurrent"

   regions:
     'added_channels': 'div.added_channels_container'
@@ -22,9 +32,8 @@
   initialize: ->
     @_added_channels_view = new AutoCompletedAddedChannelsView
       collection: @collection
-      mainView: this

-    [@model, @search_collection] = searchCollection(TopicSearchResults)
+    [@model, @search_collection] = @searchCollection(TopicSearchResults)

     @_text_input_view = new TextInputView model: @model

@@ -32,21 +41,17 @@
       'slug_title', @search_collection, @collection)

     @_auto_completes_view = new AutoCompletesView
-      mainView: this
-      alreadyAdded: @collection
       model: @model
       collection: @filtered_search_collection

-    @_text_input_view.on 'return', => @addCurrentlySelectedChannel()
-    @_text_input_view.on 'down', => @_auto_completes_view.moveSelectionDown()
-    @_text_input_view.on 'up', => @_auto_completes_view.moveSelectionUp()
+    @bindTextViewToSteppableViewAndSelf(@_text_input_view, @_auto_completes_view)

   onRender: ->
     @added_channels.show @_added_channels_view
     @auto_completes.show @_auto_completes_view
     @input.show @_text_input_view

-  addCurrentlySelectedChannel: ->
+  addCurrent: ->
     @disable()
     afterAdd = =>
       @model.set text:''


Working on flippable tile highlight bounds marks.

--- app/lib/surface/Mark.coffee
+++ app/lib/surface/Mark.coffee
@@ -72,11 +72,14 @@
     color = "rgba(#{colors[0]}, #{colors[1]}, #{colors[2]}, 0.5)"
     [w, h] = [@sprite.thang.width * Camera.PPM, @sprite.thang.height * Camera.PPM * @camera.y2x]

-    if @sprite.thang.drawsBoundsStyle is 'border-text'
-      shape = new createjs.Shape()
+    if @sprite.thang.drawsBoundsStyle in ['border-text', 'corner-text']
+      @drawsBoundsBorderShape = shape = new createjs.Shape()
       shape.graphics.setStrokeStyle 5
       shape.graphics.beginStroke color
-      shape.graphics.beginFill color.replace('0.5', '0.25')
+      if @sprite.thang.drawsBoundsStyle is 'border-text'
+        shape.graphics.beginFill color.replace('0.5', '0.25')
+      else
+        shape.graphics.beginFill color
       if @sprite.thang.shape in ['ellipsoid', 'disc']
         shape.drawEllipse 0, 0, w, h
       else
@@ -101,7 +104,7 @@
     else
       console.warn @sprite.thang.id, 'didn\'t know how to draw bounds style:', @sprite.thang.drawsBoundsStyle

-    if w > 0 and h > 0
+    if w > 0 and h > 0 and @sprite.thang.drawsBoundsStyle is 'border-text'
       @mark.cache -w / 2, -h / 2, w, h, 2
     @lastWidth = @sprite.thang.width
     @lastHeight = @sprite.thang.height
@@ -226,13 +229,12 @@
       @highlightTween = createjs.Tween.get(@mark).to({}, @highlightDelay).call =>
         @mark.visible = true
         @highlightDelay = @highlightTween = null
+    @updateAlpha @alpha if @name in ['shadow', 'bounds']
     true

   updatePosition: (pos) ->
     if @sprite?.thang and @name in ['shadow', 'debug', 'target', 'selection', 'repair']
       pos = @camera.worldToSurface x: @sprite.thang.pos.x, y: @sprite.thang.pos.y
-      if @name is 'shadow'
-        @updateAlpha @alpha
     else
       pos ?= @sprite?.imageObject
     @mark.x = pos.x
@@ -248,7 +250,9 @@
     if @name is 'shadow'
       worldZ = @sprite.thang.pos.z - @sprite.thang.depth / 2 + @sprite.getBobOffset()
       @mark.alpha = @alpha * 0.451 / Math.sqrt(worldZ / 2 + 1)
-    else if @name isnt 'bounds'
+    else if @name is 'bounds'
+      @drawsBoundsBorderShape?.alpha = Math.floor @sprite.thang.alpha  # Stop drawing bounds as soon as alpha is reduced at all
+    else
       @mark.alpha = @alpha

   updateRotation: ->


Added noty error messages when unable to save CocoModels.

--- app/models/CocoModel.coffee
+++ app/models/CocoModel.coffee
@@ -37,7 +37,7 @@
     @loading = false
     @markToRevert()
     @loadFromBackup()
-
+
   getNormalizedURL: -> "#{@urlRoot}/#{@id}"

   set: ->
@@ -69,11 +69,17 @@
     @set 'editPath', document.location.pathname
     options ?= {}
     success = options.success
-    options.success = (resp) =>
+    error = options.error
+    options.success = (model, res) =>
       @trigger "save:success", @
-      success(@, resp) if success
+      success(@, res) if success
       @markToRevert()
       @clearBackup()
+    options.error = (model, res) =>
+      error(@, res) if error
+      errorMessage = "Error saving #{@get('name') ? @type()}"
+      console.error errorMessage, res.responseJSON
+      noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
     @trigger "save", @
     return super attrs, options

@@ -164,7 +170,7 @@
   getDelta: ->
     differ = deltasLib.makeJSONDiffer()
     differ.diff @_revertAttributes, @attributes
-
+
   getDeltaWith: (otherModel) ->
     differ = deltasLib.makeJSONDiffer()
     differ.diff @attributes, otherModel.attributes