Added support for editing and creating new recipes.

--- src/main.coffee
+++ src/main.coffee
@@ -3,9 +3,10 @@

 class Workspace extends Backbone.Router
 	routes: {
-		'':		"show_root"
-		"new":	"make_new",
-		":r":	"recipe"
+		'':			"show_root"
+		"new":		"make_new",
+		":r":		"recipe"
+		":r/edit":	"edit_recipe"
 	}

 	show_root: =>
@@ -18,8 +19,15 @@
 		recipeview.render()
 		$(".main").html(recipeview.$el)

+	edit_recipe: (r) =>
+		recipeview = new RecipeView(recipes.get(r))
+		recipeview.edit()
+		$(".main").html(recipeview.$el)
+
 	make_new: ->
-		console.log "newnew"
+		recipeview = new RecipeView()
+		recipeview.edit()
+		$(".main").html(recipeview.$el)

 window.app = new Workspace()


Query proxy information from Electron and set it as env var

Request should pick this up and honour it as per
https://github.com/request/request#controlling-proxy-behaviour-using-environment-variables

--- src/main.coffee
+++ src/main.coffee
@@ -10,6 +10,13 @@
 tmp.setGracefulCleanup()

 app = require 'app'
+
+app.resolveProxy 'https://google.com', (proxyURL) ->
+  process.env.HTTPS_PROXY ?= proxyURL
+
+app.resolveProxy 'http://google.com', (proxyURL) ->
+  process.env.HTTP_PROXY ?= proxyURL
+
 BrowserWindow = require 'browser-window'

 paths =


Node has a toString method

Node has a toString method

--- src/node.coffee
+++ src/node.coffee
@@ -9,6 +9,9 @@
     @setPoint(point)
     @setHandleIn(handleIn) if handleIn
     @setHandleOut(handleOut) if handleOut
+
+  toString: ->
+    "(#{@getPoint()}, #{@getHandleIn()}, #{@getHandleOut()})"

   on: (args...) -> @emitter.on(args...)


More fancy new db work

More fancy new db work

--- coffeescript/db.coffee
+++ coffeescript/db.coffee
@@ -22,6 +22,22 @@
 	constructor: (@notebookdir) ->

 	###
+	# Finds the filename of a particular note id
+	# @param {String} id The note you're searching for
+	# @return {String} filename The found filename
+	###
+	filenameNote: (id) ->
+		files = fs.readdirSync @notebookdir
+
+		# Low level loop because perf?
+		i = 0
+		while i >= 0
+			# Finds the filename
+			if files[i] is undefined or files[i].match("."+id+".noted")
+				return files[i]
+			i++
+
+	###
 	# Creates a new notebook
 	# @param {String} name The notebook name
 	# @return {String} id The new notebook id
@@ -30,11 +46,10 @@
 		id = generateUid()

 		# there's a 16^16 chance of conflicting, but hey
-		while fs.existsSync(path.join(@notebookdir, id))
+		while fs.existsSync(path.join(@notebookdir, id  + ".json"))
 			id = generateUid()

-		fs.mkdirSync(path.join(@notebookdir, id))
-		fs.writeFile path.join(@notebookdir, id, "meta.json"),
+		fs.writeFile path.join(@notebookdir, id + ".json"),
 			JSON.stringify {
 				id: id
 				name: name
@@ -51,10 +66,10 @@
 	createNote: (name, notebook, content) ->
 		id = generateUid()

-		while fs.existsSync(path.join(@notebookdir, notebook, id  + ".noted"))
+		while fs.existsSync(path.join(@notebookdir, notebook + "." + id  + ".noted"))
 			id = generateUid()

-		fs.writeFile path.join(@notebookdir, notebook, id  + ".noted"),
+		fs.writeFile path.join(@notebookdir, notebook + "." + id  + ".noted"),
 			JSON.stringify {
 				id: id
 				name: name
@@ -63,7 +78,45 @@
 			}
 		return id

+	###
+	# List notebooks
+	# @return {Array} notebooks List of Notebooks
+	###
+	readNotebooks: ->
+		files = fs.readdirSync @notebookdir
+		notebooks = []
+
+		files.forEach (file) ->
+			notebooks.push file if file.substr(16,5) is ".json"
+
+		return notebooks
+
+	###
+	# Read a notebook
+	# @param {String} id The notebook id
+	# @return {Object} notebook Notebook metadata with list of notes
+	###
+
+	###
+	# Read a note
+	# @param {String} id The note id
+	# @return {Object} note Note metadata with content
+	###
+
+	###
+	# Deletes a notebook
+	# @param {String} id The notebook id
+	###
+
+	###
+	# Deletes a note
+	# @param {String} id The note id
+	###
+	deleteNote: (id) ->
+		fs.unlink path.join(@notebookdir, @filenameNote(id))
+

 noteddb = new db(notebookdir())
-notebook = noteddb.createNotebook("Getting Started")
-noteddb.createNote("Awesome", notebook, "YOLO SWAGGGG")
+# notebook = noteddb.createNotebook("Getting Started")
+# noteddb.createNote("Awesome", notebook, "YOLO SWAGGGG")
+console.log noteddb.readNotebooks()


Fixed Message for TextMessage refactor

Fixed Message for TextMessage refactor

--- src/hubot/talker.coffee
+++ src/hubot/talker.coffee
@@ -35,7 +35,7 @@
     bot.on "Message", (message)->
       console.log message
       author = self.userForId(message.user.id)
-      self.receive new Robot.Message(author, message.content.replace(/^\s*@hubot\s+/, "Hubot: "))
+      self.receive new Robot.TextMessage(author, message.content.replace(/^\s*@hubot\s+/, "Hubot: "))

     @bot = bot

@@ -61,7 +61,7 @@
     #callback
     @socket.addListener 'data', (data) ->
       console.log data
-      message = JSON.parse data
+      message = JSON.parse(data)

       if message.type == "connected"
         console.log "Succesfully connected, listing users:"


unused

unused

--- extensions/tree/tree-pane.coffee
+++ extensions/tree/tree-pane.coffee
@@ -25,7 +25,6 @@
       path = decodeURIComponent el.data 'path'

       if fs.isDirectory path
-        window.x = @tree
         if el.hasClass 'open'
           @tree.hideDir path
           el.removeClass 'open'


oauth: throw error if no session when fetching provider (not sure how this happens, but it does: http://d.pr/n/kgvG)

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -489,6 +489,8 @@
     {sessionToken} = client
     JSession.one {clientId: sessionToken}, (err, session) =>
       return callback err  if err
+      return callback createKodingError "No session"  unless session
+
       kallback = (err, resp={}) ->
         {account, replacementToken} = resp
         callback err, {
@@ -498,7 +500,7 @@
           replacementToken
         }
       @fetchUserByProvider provider, session, (err, user) =>
-        return callback createKodingError err.message if err
+        return callback createKodingError err.message  if err
         if isUserLoggedIn
           if user
             callback createKodingError """


fixing user

fixing user

--- src/poll.coffee
+++ src/poll.coffee
@@ -55,7 +55,7 @@

 	robot.respond /vote\s+(\d)/i, (msg) ->
 		vote = msg.match[1]
-		user = msg.user
+		user = @envelope.user
 		@robot.logger.info "Adding a vote for #{vote} for #{user}"
 		#check if the user already voted
 		for option in @robot.brain.data.poll.options
@@ -63,7 +63,7 @@
 			if option.users != undefined
 				@robot.logger.info "Some have voted for this one"
 				option.users = option.users.filter (currentUser) -> currentUser isnt user
-		if @robot.brain.data.poll.options[vote].users != undefined
+		if @robot.brain.data.poll.options[vote].users.length > 0
 			@robot.logger.info "add user to this option since it's defined"
 			@robot.brain.data.poll.options[vote].users.push(user)
 		else


Minor mods

Minor mods

git-svn-id: f0ad932f837d50b52f73eb632e8f284266842b45@64 46e4088a-c2b5-4d24-90db-838aeec2e325

--- resources/static/brunch/src/app/collections/rules_list_collection.coffee
+++ resources/static/brunch/src/app/collections/rules_list_collection.coffee
@@ -1,6 +1,7 @@
 class RulesList extends Backbone.Collection

   model: Rule
+  url: -> "#{@document.url()}/rules"

   done: ->
     return @filter( (rule) ->


Automatic Extension Support

Automatic Extension Support

The bin test was failing in the build dir. Fixed.

--- test/bin.coffee
+++ test/bin.coffee
@@ -17,10 +17,16 @@
 # with the bin file as a closure. Just a little utility function for our tests.
 binGen = (binName, opts={}) ->
   opts.addBinPath       ?= true
+  opts.autoExtension    ?= true
   opts.includeBinExec   ?= true

   # Our internal args list is prepended to every user args list.
   _args=[]
+
+  # Add the extension of this file to the binName, if binName is missing an
+  # extension.
+  if opts.autoExtension and not path.extname(binName)?
+    binName += path.extname __filename

   # If our bin is just the name, we add the proper bin path.
   if opts.addBinPath and binName.indexOf('/') < 0
@@ -55,7 +61,7 @@

 describe 'bin/kdc-plus', ->
   bin = null
-  before -> bin = binGen 'kdc-plus.coffee'
+  before -> bin = binGen 'kdc-plus'

   it 'should log to stderr not stdout', (done) ->
     bin ['-h'], (err, stdout, stderr) ->


add pv_driver status for a vm in spec

add pv_driver status for a vm in spec

--- src/spec.coffee
+++ src/spec.coffee
@@ -460,6 +460,13 @@

       power_state: -> @genval.power_state

+      pv_driver: ->
+        {guest_metrics} = @data
+        if guest_metrics
+          guest_metrics.PV_drivers_up_to_date
+        else
+          false
+
       CPUs: {
         number: ->
           {metrics} = @data


add bios strings

add bios strings

--- src/spec.coffee
+++ src/spec.coffee
@@ -351,6 +351,8 @@
         val: -> @key
       }

+      bios_strings: -> @genval.bios_strings
+
       CPUs: -> @genval.cpu_info

       enabled: -> @genval.enabled


Fix skipped tour item.

Fix skipped tour item.

--- src/tour.coffee
+++ src/tour.coffee
@@ -62,7 +62,7 @@
                     $container.click()
         },
         {
-            element: '.service-hover-color-50003'
+            element: '.service-hover-background-color-light-50003'
             placement: 'right'
             backdrop: true
         },


Added logic to create a cache of queries needed during construction to reduce the need for individual queries. Greatly increases performance for large collection trees.

--- src/tree.coffee
+++ src/tree.coffee
@@ -15,7 +15,7 @@
     items = @collection
   @items = items

-  @model = data.model ? new TreeModel(collection: @collection)
+  @model = data.model ? new TreeModel(collection: @collection, template: @)
   @selection = new IdSet({exclusive: !settings.multiSelect})
   @check = new IdSet({exclusive: false})
   # Allow modifying the underlying logic of the tree model.
@@ -398,14 +398,15 @@
     @collection = args.collection
     unless @collection
       throw new Error('No collection provided when creating tree data')
+    @_setUpQueries(args)

   getChildren: (doc, args) ->
     args ?= {}
     # Search for root document if doc is undefined
     id = doc?._id ? null
-    children = @collection.find({parent: id}).fetch()
+    children = _.map _.keys(@_queries[id]?.children ? []), (childId) =>
+      @collection.findOne(childId)
     children.sort(@compareDocs)
-
     children

   hasChildren: (doc) -> @getChildren(doc).length == 0
@@ -448,6 +449,42 @@
   compareDocs: (docA, docB) ->
     if docA.name < docB.name then -1 else 1

+  # Run queries beforehand to prevent the need for expensive queries for individual nodes.
+  _setUpQueries: (args) ->
+    template = args.template
+    unless template
+      throw new Error('Template instance is needed to observe collection.')
+
+    @_queries = {}
+    setUp = (doc) =>
+      id = doc._id
+      parentId = doc.parent
+      queries = @_queries[id] ?= {}
+      if parentId?
+        queries.parent = parentId
+        parentQueries = @_queries[parentId] ?= {}
+        parentQueries.children ?= {}
+        parentQueries.children[id] = true
+
+    removed = (doc) =>
+      id = doc._id
+      delete @_queries[id]
+      parentId = doc.parent
+      if parentId?
+        parentQueries = @_queries[parentId]
+        if parentQueries
+          children = parentQueries.children
+          delete children[id]
+
+    @collection.find({parent: {$exists: true}}).forEach (doc) => setUp(doc)
+
+    template.autorun =>
+      Collections.observe @collection,
+        added: setUp
+        changed: setUp
+        removed: removed
+        triggerExisting: true
+

 class IdSet


only print auth messages when necessary

only print auth messages when necessary

--- src/redis-client.coffee
+++ src/redis-client.coffee
@@ -40,13 +40,13 @@

     if @info.auth
       @authed = Q.ninvoke @client, "auth", @info.auth.split(":")[1]
+
+      @authed.then ->
+        robot.logger.info "Successfully authenticated to Redis"
+      @authed.fail ->
+        robot.logger.error "Failed to authenticate to Redis"
     else
       @authed = Q()
-
-    @authed.then ->
-      robot.logger.info "Successfully authenticated to Redis"
-    @authed.fail ->
-      robot.logger.error "Failed to authenticate to Redis"

     @ready = Q.all [@connected, @authed]


fix(regexp-utils): Also update the regex101 link with test cases

--- src/regexp-utils.coffee
+++ src/regexp-utils.coffee
@@ -41,7 +41,7 @@
   # 5. the closing tag
   linkTagRegex: -> new RegExp(/(<a.*?href\s*?=\s*?['"])(.*?)(['"].*?>)([\s\S]*?)(<\/a>)/gim)

-  # Test cases: https://regex101.com/r/cK0zD8/3
+  # Test cases: https://regex101.com/r/cK0zD8/4
   # Catches link tags containing which are:
   # - Non empty
   # - Not a mailto: link


Eliminate duplicate middlewares

Eliminate duplicate middlewares

--- src/routes/index.coffee
+++ src/routes/index.coffee
@@ -5,17 +5,12 @@
 # Express Components
 router = express.Router()

-router.all '*', (req,res,next) ->
-  res.header("Access-Control-Allow-Origin", "*")
-  res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With")
-  next()

 #Internal Dependencies
 employer_size_router = require './employer_size'
 survey_router = require './survey'

 router.all '*', (req,res,next) ->
-  debug "In router"
   res.header("Access-Control-Allow-Origin", "*")
   res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With")
   next()


fix get_model

fix get_model

--- lib/assets/javascripts/views/select.coffee
+++ lib/assets/javascripts/views/select.coffee
@@ -19,29 +19,22 @@

   initialize: (options) ->
     @rendered = false
-
-    console.log "in select intialize"

-    @label = if options.label then options.label else @defaults.label
-    @value = if options.value then options.value else false
-    @default_to= if options.default_to then options.default_to else false
-    @sort = if options.sort? then options.sort else @defaults.sort
-    @helper =options.helper if options.helper?
+    @label      = if options.label? then options.label else @defaults.label
+    @value      = if options.value? then options.value else false
+    @default_to = if options.default_to? then options.default_to else false
+    @sort       = if options.sort? then options.sort else @defaults.sort
+    @helper     = options.helper if options.helper?

     # The select view needs a collection.
     if !@collection
-      console.log "creating collection"
       @collection = @collection_from_values(options.values)
     else if @collection.length > 0
-      console.log "using existing collection"
       t = @
       @collection.each (model) ->
         t.set_model_attributes(model)
     else
-      console.log "no collection or values"
       throw new Error "Select needs either a collection or an array of values"
-
-    console.log @collection

     @template = JST["templates/select"]
     @listenTo(@collection, 'add', @add_model)
@@ -61,19 +54,16 @@
     @set_model_attributes(m)
     select_el = @$el.find('select').first()
     value = if m.get('value') then "value='#{m.get('value')}'" else ""
-    label = if m.get('label') then "label='#{m.get('label')}'" else ""
-    select_el.append("<option #{value}>#{label}</option>")
+    select_el.append("<option #{value}>#{m.get('label')}</option>")

     sort_by_name = (a, b) ->
-      a.innerHTML.toLowerCase().localeCompare(b.innerHTML.toLowerCase())
-
+      a.innerHTML.trim().toLowerCase().localeCompare(b.innerHTML.trim().toLowerCase())
     options = select_el.children().get()
     options = options.sort(sort_by_name) if @sort
     for opt in options
       select_el.append(opt)

   set_model_attributes: (model) =>
-    console.log "set_model_attributes"
     model.set('label',model.get(@label))
     model.set('value',model.get(@value)) if @value
     if @default_to and @value and @default_to is model.get(@value)
@@ -92,9 +82,9 @@

     @select.on('change', (v) ->
       if t.value
-        selected_model = t.collection.find((model) -> model.get(t.value) is v.value)
+        selected_model = t.collection.find( (model) -> model.get(t.value) is v.value)
       else
-        selected_model = t.collection.find((model) -> model.get(t.label) is v.value)
+        selected_model = t.collection.find( (model) -> model.get(t.label) is v.value)

       t.trigger('change', selected_model)
     )
@@ -102,7 +92,7 @@
   get_value: -> @select.value
   get_model: ->
     value = @select.value
-    if model.get('value')
+    if @value
       @collection.find (model) => model.get('value') is value
     else
       @collection.find (model) => model.get('label') is value


More documentation/comments

More documentation/comments

--- lib/atom-perl6-editor-tools-builder.coffee
+++ lib/atom-perl6-editor-tools-builder.coffee
@@ -1,8 +1,8 @@
 fs = require("fs")
 path = require("path")

-# This provides linter support for Perl 6 using linter
-# Please see https://atom.io/packages/linter
+# This provides builder support for Perl 6 using linter
+# Please see https://atom.io/packages/build
 module.exports =
 class Perl6BuildProvider

@@ -16,12 +16,9 @@
     return 'Perl 6 Builder'

   isEligible: ->
-    # REQUIRED: Perform operations to determine if this build provider can
-    # build the project in `cwd` (which was specified in `constructor`).
     return fs.existsSync(path.join(@cwd, 't'))

   settings: ->
-    # REQUIRED: Return an array of objects which each define a build description.
     return [
       "name": "Run Perl 6 tests"
       "exec": "prove"


Fix mysterious beveling bug... I think

Fix mysterious beveling bug... I think

--- src/view.coffee
+++ src/view.coffee
@@ -1178,8 +1178,6 @@
             multilineView = @view.getViewNodeFor multilineChild.child
             multilineBounds = multilineView.bounds[line - multilineChild.startLine]

-            # Draw the upper-right corner
-            right.push new draw.Point bounds.right(), bounds.y

             # If the multiline child here is invisible,
             # draw the line just normally.
@@ -1199,6 +1197,7 @@
             # Otherwise, avoid the block by tracing out its
             # top and left edges, then going to our bound's bottom.
             else
+              right.push new draw.Point bounds.right(), bounds.y
               right.push new draw.Point bounds.right(), multilineBounds.y
               right.push new draw.Point multilineBounds.x + @view.opts.bevelClip, multilineBounds.y
               right.push new draw.Point multilineBounds.x, multilineBounds.y + @view.opts.bevelClip
@@ -1355,7 +1354,8 @@
             else
               glueTop = @bounds[line].bottom()

-            right.push new draw.Point multilineBounds.x, glueTop
+            unless multilineChild.child.type is 'indent'
+              right.push new draw.Point multilineBounds.x, glueTop

             # Draw the tab at the top of the indent if necessary
             if multilineChild.child.type is 'indent'


Get prototype safely.

Get prototype safely.

--- src/view.coffee
+++ src/view.coffee
@@ -16,7 +16,7 @@

   cacheTemplate: (el) ->
     # Cache on prototype so it can be reused
-    @::_cachedEl = el
+    (Object.getPrototypeOf @)._cachedEl = el

   renderTemplate: (ctx, cache=true) ->
     if @_cachedEl? and cache


deselect link if not close

deselect link if not close

--- src/main/webapp/lib/collections/LinkListView.coffee
+++ src/main/webapp/lib/collections/LinkListView.coffee
@@ -59,9 +59,11 @@
     geo = google.maps.geometry.poly
     marker = markerView.marker
     model = markerView.model
-    _.each(@views, (view) ->
+    _.each(@views, (view) ->
             if geo.isLocationOnEdge(marker.getPosition(), view.link, 0.0006)
                 view.linkSelect()
                 model.set_link(view.model)
-                setTimeout (-> view.clearSelected()), 1000
+                setTimeout (-> view.clearSelected()), 1000
+            else
+                view.clearSelected()
           )


Added debugging strings

Added debugging strings

--- test/generators/relational/has_many.coffee
+++ test/generators/relational/has_many.coffee
@@ -336,10 +336,10 @@

                   owner.get 'reverses', (err, updated_reverses) ->
                     assert.ok(!err, "No errors: #{err}")
-                    assert.equal(updated_reverses.length, 3, "loaded correct models.")
+                    assert.equal(updated_reverses.length, 3, "loaded correct models. Expected: #{3}. Actual: #{updated_reverses.length}")
                     updated_reverse_ids = (reverse.id for reverse in updated_reverses)

-                    assert.equal(updated_reverse_ids.length, 3, "Moved the reverse")
+                    assert.equal(updated_reverse_ids.length, 3, "moved the reverse. Expected: #{3}. Actual: #{updated_reverses.length}")
                     assert.ok(_.contains(updated_reverse_ids, moved_reverse_id), "Moved the reverse_id")
                     updated_moved_reverse = updated_reverses[_.indexOf(updated_reverse_ids, moved_reverse_id)]


:art: Improve Type, Label Also add description

--- lib/autocomplete-hack.coffee
+++ lib/autocomplete-hack.coffee
@@ -11,6 +11,30 @@
     Provider =
       selector: '.source.cpp, .source.hack, .source.php'
       disableForSelector: '.comment'
+      Map:{
+        int: 'Integer'
+        float: 'Float'
+        string: 'String'
+        bool: 'Boolean'
+        array: 'Array'
+        num: 'Number'
+        mixed: 'Mixed'
+      }
+      getType:(Text, Label)->
+        leftLabel = Label
+        Type = Label
+        if Label is 'class' or Text is '$this'
+          Type = 'class'
+          leftLabel = 'Class'
+        else if typeof Provider.Map[Label] isnt 'undefined'
+          Type = if Text.substr(0,1) is '$' then 'variable' else 'property'
+          leftLabel = Provider.Map[Label]
+        else
+          Type = if Text.substr(0,1) is '$' then 'variable' else 'property'
+          leftLabel = /(\w+)/.exec(Label)[0] || ''
+        if leftLabel is 'function'
+          Type = 'function'
+        return {Type, leftLabel}
       getPrefix:(editor, bufferPosition)->
         regex = /::([\$\w0-9_-]+)$|\)\s*:(\w+)$|(:[\$\w0-9_-]+)$|([\$\w0-9_-]+)$/
         line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
@@ -28,20 +52,17 @@
         new Promise (Resolve) ->
           Hack.exec(Command, Path.dirname(editor.getPath())).then (Result)->
             Result = Result.stdout.split("\n").filter((e) -> e)
+            console.log Result
             ToReturn = Result.map((Entry)->
               Entry = Entry.split(' ')
               Text = Entry[0]
               Label = Entry.slice(1).join(' ')
-              if Text.substr(0,1) is '$'
-                Type = 'variable'
-              else if Text is 'class' and Label is 'string'
-                Type = 'class'
-              else
-                Type = 'property'
+              {leftLabel, Type} = Provider.getType(Text, Label)
               return {
                 type: Type
                 text: Text
-                leftLabel: Label
+                leftLabel: leftLabel
+                description: Label
                 replacementPrefix: prefix
                 score: prefix.length > 0 and Text.score(prefix)
               }


Stop completion if the word was already confirmed, Fix #25 and Fix #23

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -15,6 +15,7 @@
   originalCursorPosition: null
   aboveCursor: false
   debug: false
+  lastConfirmedWord: null

   initialize: (@editorView) ->
     super
@@ -137,9 +138,11 @@
   ###
   confirmed: (match) ->
     @editor.getSelection().clear()
-
     @cancel()
+
     return unless match
+
+    @lastConfirmedWord = match.word
     @replaceTextWithMatch match
     position = @editor.getCursorBufferPosition()
     @editor.setCursorBufferPosition([position.row, position.column])
@@ -172,6 +175,9 @@

     selection = @editor.getSelection()
     prefix = @prefixOfSelection selection
+
+    # Stop completion if the word was already confirmed
+    return if prefix is @lastConfirmedWord

     # No prefix? Don't autocomplete!
     return unless prefix.length


Observe changes to the maxSize config and resize immediately

--- lib/glsl-preview-view.coffee
+++ lib/glsl-preview-view.coffee
@@ -284,6 +284,7 @@
   handleEvents: ->
     @disposables.add atom.grammars.onDidAddGrammar => _.debounce((=> @renderView()), 250)
     @disposables.add atom.grammars.onDidUpdateGrammar _.debounce((=> @renderView()), 250)
+    @disposables.add atom.config.onDidChange 'glsl-preview.maxSize', @_onResize.bind(this)

     changeHandler = =>
       @renderView()


Fix #377

Fix #377

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -86,7 +86,7 @@
   else
     init().then -> startCommit()
     .catch (message) ->
-      if message.includes 'CRLF'
+      if message.includes?('CRLF')
         startCommit()
       else
         notifier.addInfo message


Change server connection path to 'environment'

--- lib/nsync/initializer.coffee
+++ lib/nsync/initializer.coffee
@@ -25,7 +25,7 @@
   ,
     host: 'ile.learn.co',
     port: 443,
-    path: 'fs_server'
+    path: 'environment'

   {host, port, path} = config
   protocol = if port is 443 then 'wss' else 'ws'
@@ -204,6 +204,7 @@
         localRoot: _path.join(atom.configDirPath, '.learn-ide')
         connection:
           url: "#{WS_SERVER_URL}?token=#{token}&version=#{atomHelper.learnIdeVersion()}"
+          socketKey: 'environment'

   atomHelper.waitForTerminalConnection().then(connect).catch(connect)


Disallow ':' as a separator (breaks list-edit on JSON objects)

--- lib/text-manipulation.coffee
+++ lib/text-manipulation.coffee
@@ -214,7 +214,8 @@

   # assumes c is character
   isSeparator: (c) ->
-    ',;:'.indexOf(c) != -1
+    ',;'.indexOf(c) != -1 # ':' is sometimes a separator, but allowing it breaks list-edit for JSON objects
+  # TODO: Use priorities for determining the separator, so we only take ':' when there is no ',' or ';'?

   # Strings (using whitespace as separator) are tricky, as they can be mistakenly assumed to be open/close bracket
   # e.g. '["Blinky", Inky, "Pinky"]' with cursor on Inky may recognize '", Inky ,"' as list.


debug logging

debug logging

--- v1meta.coffee
+++ v1meta.coffee
@@ -76,6 +76,8 @@

         build_asset_class_from_xml: (xml) ->
+            console.log "Asset class"
+            console.log et.dump(xml)
             asset_type_name = xml.get('name')
             cls = class extends AssetClassBase
                     _v1_asset_type_name: asset_type_name


config: fix gatheringestor starting in test instances

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -687,7 +687,9 @@
       group             : "environment"
       instances         : 1
       supervisord       :
-        command         : "#{GOBIN}/watcher -run koding/workers/gatheringestor -c #{configName}"
+        command         :
+          run           : "#{GOBIN}/gatheringestor -c #{configName}"
+          watch         : "#{GOBIN}/watcher -run koding/workers/gatheringestor -c #{configName}"
       nginx             :
         locations       : [ { location: "/-/gatheringestor" } ]
       healthCheckURL    : "http://localhost:#{KONFIG.gatheringestor.port}/healthCheck"


xhr: message locations are added into main dev

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -537,6 +537,14 @@
             proxyPass   : "http://socialapi/notificationsetting/$1$is_args$args"
           }
           {
+            location    : "~ /api/social/message/(.*)"
+            proxyPass   : "http://socialapi/message/$1$is_args$args"
+          }
+          {
+            location    : "~ /api/social/channel/(.*)"
+            proxyPass   : "http://socialapi/channel/$1$is_args$args"
+          }
+          {
             location    : "~ /api/social/collaboration/ping"
             proxyPass   : "http://socialapi/collaboration/ping$1$is_args$args"
           }


stop using DOM events. is this bad? i don't like e.details.

let me know if there is a good reason i'm missing to use DOM
events for our custom events.

i like this:

  class Dingus
    constructor: ->
      atom.on 'window:load', @thing

    thing: (window) =>
      console.log 'hooray'

--- src/stdlib/event.coffee
+++ src/stdlib/event.coffee
@@ -1,28 +1,20 @@
-# Using the DOM event system, and copying the JQuery Event API
-# https://developer.mozilla.org/en/DOM/Creating_and_triggering_events
+_ = require 'underscore'

 module.exports =
 class Event
   events: {}

   on: (name, callback) ->
-    window.document.addEventListener name, callback
-    callback
+    @events[name] ?= []
+    @events[name].push callback

   off: (name, callback) ->
-    window.document.removeEventListener name, callback
+    delete @events[name][_.indexOf callback] if @events[name]

   trigger: (name, data, bubbleToApp=true) ->
     if bubbleToApp and name.match /^app:/
       OSX.NSApp.triggerGlobalEvent_data name, data
       return

-    event = @events[name]
-    if not event
-      event = window.document.createEvent "CustomEvent"
-      event.initCustomEvent name, true, true, null
-      @events[name] = event
-
-    event.details = data
-    window.document.dispatchEvent event
+    _.each @events[name], (callback) => callback.call data, data
     null


Tweak service name and warning text

Tweak service name and warning text

--- src/packages/github-sign-in/lib/sign-in-view.coffee
+++ src/packages/github-sign-in/lib/sign-in-view.coffee
@@ -77,8 +77,8 @@

       success: ({token}={}) =>
         if token?.length > 0
-          unless keytar.replacePassword('GitHub.com', 'github', token)
-            console.warn 'Unable to save GitHub.com token to keychain'
+          unless keytar.replacePassword('github.com', 'github', token)
+            console.warn 'Unable to save GitHub token to keychain'
         @detach()

       error: (response={}) =>


Add dev task to grunt

Add dev task to grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,14 +6,13 @@
   replaceMap = {}

   config = GruntVTEX.generateConfig grunt, pkg,
-    relativePath: '/'
     replaceMap: replaceMap

   config.coffee.main.files = [
     expand: true
     cwd: 'src'
     src: ['**/*.coffee']
-    dest: "build/"
+    dest: "build/<%= relativePath %>/"
     rename: (path, filename) ->
       path + filename.replace("coffee", "js")
   ]
@@ -21,19 +20,25 @@
   config.concat =
     main:
       files:
-        'build/vtex.js': ['build/*.js']
+        'build/<%=relativePath%>/vtex.js': [
+          'build/<%= relativePath %>/extended-ajax.js'
+          'build/<%= relativePath %>/catalog.js'
+          'build/<%= relativePath %>/checkout.js'
+        ]

   config.uglify =
     main:
       files: [
         expand: true
-        cwd: 'build/'
+        cwd: 'build/<%=relativePath%>/'
         src: '*.js'
-        dest: 'build'
+        dest: 'build/<%=relativePath%>/'
         rename: (dest, src) -> dest + '/' + src.replace('.js', '.min.js')
       ]
       options:
         sourceMap: true
+
+  config.watch.coffee.files = ['src/*.coffee']

   tasks =
   # Building block tasks
@@ -44,9 +49,10 @@
     test: []
     vtex_deploy: ['shell:cp', 'shell:cp_br']
   # Development tasks
-    default: ['build', 'watch']
+    dev: ['nolr', 'build', 'watch']
+    default: ['dev']

   # Project configuration.
   grunt.initConfig config
   grunt.loadNpmTasks name for name of pkg.devDependencies when name[0..5] is 'grunt-' and name isnt 'grunt-vtex'
-  grunt.registerTask taskName, taskArray for taskName, taskArray of tasks+  grunt.registerTask taskName, taskArray for taskName, taskArray of tasks


grunt tweaks

grunt tweaks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -154,8 +154,8 @@
         files: ['demo/styles/*.styl']
         tasks: ['stylus:demo']
       src:
-        files: ['src/**/*.coffee', 'node_modules/tandem-core/src/*']
-        tasks: ['browserify', 'concat', 'copy:build']
+        files: ['src/**/*.coffee']
+        tasks: ['browserify', 'coffee', 'string-replace', 'concat']
       test:
         files: ['tests/mocha/**/*.coffee']
         tasks: ['coffee:test']


Stub out science team for #37

Stub out science team for #37

--- project.coffee
+++ project.coffee
@@ -19,6 +19,16 @@
   pages: [
     {'Science': translate 'div', 'science'}
   ]
+
+  scientists: [{
+    name: 'Andrew Zisserman'
+  }, {
+    name: 'Caitlin Black'
+  }, {
+    name: 'Tom Hart'
+  }, {
+    name: 'Yusuf Aytar'
+  }]

   developers: [{
     name: "Adam McMaster"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -7,7 +7,7 @@
 ###

 ###*
-
+Goes to

 @name update
 @methodOf TitleScreen#


添加用户的注释，用法

添加用户的注释，用法

--- core/model/User.coffee
+++ core/model/User.coffee
@@ -6,10 +6,14 @@
 module.exports = class User extends Model
   @validateData:
     group: ['admin','user','trial']
-
+  # 必须重写
   @create: (data) ->
     new User data
-
+  # 注册新用户
+  # @callback的第二个参数是新注册的model
+  # 用法：
+  #     User.register 'wangzi','wangzi@gmail','wangzi',(err,result)->
+  #       console.log result
   @register: (username, email, passwd, callback = null) ->
     passwd_salt = auth.randomSalt()

@@ -24,7 +28,15 @@
       attribure: {}
       tokens: []
     @insert data, callback
-
+  # 添加分组的功能
+  # @group 可以是数组，也可以是字符串，但是必须在['admin','user','trial']中
+  # @callback 第一个参数是err,第二个参数是添加分组后的model
+  # 用法：
+  #     user.addToGroup ['admin','users'],(err,result)->
+  #       console.log result
+  # 或
+  #     user.addToGroup 'admin',(err,result)->
+  #       console.log result
   addToGroup: (group,callback) ->
     group = [].push group if not _.isArray group
     for i in group


do not combine deltas caused by undo/redo

--- src/undo-manager.coffee
+++ src/undo-manager.coffee
@@ -26,6 +26,7 @@
 _change = (source, dest) ->
   if @stack[source].length > 0
     change = @stack[source].pop()
+    @lastRecorded = 0
     _ignoreChanges.call(this, =>
       @editor.applyDelta(change[source], { source: 'user' })
       index = getLastChangeIndex(change[source])


[#63730868] fixed sandboxShipmentsSaving

--- app/assets/javascripts/trade/controllers/sandbox_shipments_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/sandbox_shipments_controller.js.coffee
@@ -4,7 +4,7 @@
   updatesVisible: false
   currentShipment: null
   errorParams: null
-  sandboxShipmentsSaving: false # FIXME
+  sandboxShipmentsSaving: false

   columns: [
     'appendix', 'species_name',
@@ -21,10 +21,10 @@
     Ember.Object.create({id: 'N', name: 'N'})
   ]

-  # FIXME !!!
-  #sandboxShipmentsSaving: ( ->
-  #  @get('content.isSaving')
-  #).property('content.isSaving')
+  sandboxShipmentsSaving: ( ->
+    return false unless @get('content.isLoaded')
+    @get('content').filterBy('isSaving', true).length > 0
+  ).property('content.@each.isSaving')

   transitionToPage: (forward) ->
     page = if forward


Add daterange query if more than one incident


crash fix attempt 3

crash fix attempt 3

--- server.coffee
+++ server.coffee
@@ -77,13 +77,12 @@
   lobby.emit('netrunner', {type: "games", games: games})

   socket.on 'disconnect', () ->
-    if socket.gameid
-      try
-        if games[socket.gameid].started
-          requester.send(JSON.stringify({action: "notification", gameid: socket.gameid, text: "#{socket.request.user.username} disconnected."}))
-      catch err
-        console.log(err)
+    gid = socket.gameid
+    game = games[gid]
+    if game
       removePlayer(socket)
+      if game.started and game.players.length > 0
+        requester.send(JSON.stringify({action: "notification", gameid: gid, text: "#{socket.request.user.username} disconnected."}))

   socket.on 'netrunner', (msg) ->
     switch msg.action
@@ -104,13 +103,12 @@

       when "leave-game"
         gid = socket.gameid
-        msg.action = "quit"
         game = games[gid]
-        try
-          requester.send(JSON.stringify(msg)) if game and game.players.length > 1
-        catch err
-          console.log(err)
-        removePlayer(socket)
+        if game
+          removePlayer(socket)
+          msg.action = "quit"
+          if game.players.length > 0
+            requester.send(JSON.stringify(msg))

       when "join"
         joinGame(socket, msg.gameid)


make reloadServer global

make reloadServer global

--- server.coffee
+++ server.coffee
@@ -32,7 +32,7 @@

 #####

-reloadServer = (msg) ->
+global.reloadServer = (msg) ->
     cb ?= ->
     if not msg then sendChatMessage 'Restarting...'
     exec 'git pull', (err, stdout, stderr) ->
@@ -44,7 +44,7 @@
             sendChatMessage msg or stdout
             setTimeout restartServer, 1000

-restartServer = () ->
+global.restartServer = () ->
     exec 'forever restart florinda.js', (err, stdout, stderr) ->
         if err then sendChatMessage "Aaaarrgh! I'm hurt"


Add honor

Add honor

--- server.coffee
+++ server.coffee
@@ -10,6 +10,10 @@
 multer         = require 'multer'
 errorHandler   = require 'errorhandler'
 meshbluHealthcheck = require 'express-meshblu-healthcheck'
+
+process.on 'SIGTERM', =>
+  console.log 'Dieing a clean, honorable death'
+  process.exit 0

 TemperatureController = require './controllers/temperature-controller'
 temperatureController = new TemperatureController()


Added FIXME comment

Added FIXME comment

--- server.coffee
+++ server.coffee
@@ -12,6 +12,8 @@
     query_list.push k
     query_list.push v
   return query_list
+
+# FIXME: try to pass auth token more securely

 config =
   port: 8080


Disable Nodefly except in production.

Disable Nodefly except in production.

Fixes #5

--- server.coffee
+++ server.coffee
@@ -5,25 +5,29 @@
 presenter = require './lib/presenter'
 helpers = require "./lib/helpers"

-throw "Nodefly API Key not configured! ($NODEFLY_API_KEY)" unless process.env.NODEFLY_API_KEY
-require('nodefly').profile(
-  process.env.NODEFLY_API_KEY,
-  'Busted',
-  {}
-)
+# Configuration

 app = express()
-
 app.set 'view engine', 'mustache'
 app.set 'layout', 'layout'
 # app.set 'partials', head: 'head'
-app.enable 'view cache' if app.settings.env is "production"
 app.engine 'mustache', require 'hogan-express'
-
 app.use express.bodyParser()

 throw "CTA API Key not configured! ($CTA_API_KEY)" unless process.env.CTA_API_KEY
 app.set 'apiKey', process.env.CTA_API_KEY
+
+if app.settings.env is "production"
+  app.enable 'view cache'
+
+  throw "Nodefly API Key not configured! ($NODEFLY_API_KEY)" unless process.env.NODEFLY_API_KEY
+  require('nodefly').profile(
+    process.env.NODEFLY_API_KEY,
+    'Busted',
+    {}
+  )
+
+# Routes

 app.get "/", (req, res) ->
   renderHTML
@@ -63,6 +67,8 @@
   else
     renderSMS res, 400, "Error: Your message must include a bus number"

+# Helpers
+
 renderSMS = (res, status, message) ->
   console.log "[Resposne (SMS)]: ", {status, message}
   truncatedMessage = message.substring 0, 160
@@ -75,6 +81,8 @@
   res.locals = context
   res.render template, partials: partials

+# Start server
+
 port = process.env.PORT || 5000
 app.listen port, ->
   console.log "Server up on port #{port}"


add debug info

add debug info

--- server.coffee
+++ server.coffee
@@ -337,7 +337,8 @@
           res.send 500, { error: "API call failed" }
           return
         rc.hmget "communities", community, (err, reply) ->
-          if not err
+          console.log "repo from cache", err, reply
+          if not err and reply
             repo = JSON.parse reply
             repo.members.push req.user
             repo.members_count = repo.members.length + repo.admins.length


server: encapsulate everything in the App class

--- server.coffee
+++ server.coffee
@@ -1,11 +1,3 @@
-express = require('express')
-ehbs = require("express3-handlebars")
-
-app = express()
-
-app.engine('.hbs.html', ehbs({defaultLayout: 'main',extname: '.hbs.html'}))
-app.set('view engine', '.hbs.html')
-
 class ControllerAction
   constructor: (@req,@res) ->
     @params = @req.params
@@ -48,12 +40,31 @@
       else
         @res.render("slidecast",slidecast: content)

+http = require("http")
+express = require('express')
+ehbs = require("express3-handlebars")
+
 class App
-  setup: (@express) ->
+  constructor: () ->
+    @setupExpress()
+
+  setupExpress: ->
+    @express = express()
+
+    @express.engine('.hbs.html', ehbs({defaultLayout: 'main',extname: '.hbs.html'}))
+    @express.set('view engine', '.hbs.html')
+    @express.use(express.static(__dirname))
+
     @handle "get", "/", Home
     @handle "get", '/ide/:name', ShowIDE
     @handle "get", '/bootstrap', ShowBootstrapDemo
     @handle "get", '/:name', ShowSlideCast
+
+  start: (port) ->
+    @server = http.createServer(@express)
+    @server.listen(port || 3000)
+    @socket = require("socket.io").listen(@server)
+    console.log "Listening to port #{port}"

   handle: (verb,path,action) ->
     handler = (req,res) ->
@@ -61,8 +72,8 @@

     @express[verb](path, handler)

-(new App()).setup(app)
+app = new App()
+app.start(3000)

-app.use(express.static(__dirname))

-app.listen(3000)
+


start using new message passing scheme

start using new message passing scheme

--- server.coffee
+++ server.coffee
@@ -43,7 +43,7 @@
   server: meshbluConfig.server
   port: meshbluConfig.port

-# app.use meshbluAuth meshbluOptions
+meshbluAuthorizer = meshbluAuth meshbluOptions

 app.options '*', cors()

@@ -60,7 +60,11 @@
   req.session.token = token
   next null, uuid: profile.uuid

-app.post '/api/proxy', (req, res) ->
+app.post '/api/proxy', meshbluAuthorizer, (req, res) ->
+
+  debug '/api/proxy', req.body
+  return res.status(500).end()
+
   clientID = req.body.clientID
   uri = req.body.uri
   qs  = req.body.qs
@@ -128,10 +132,22 @@
     else
       meshbluHttp.register
         type: 'auth:little-bits-cloud-proxy'
-        configureWhitelist: [octobluStrategyConfig.clientID, userUuid]
-        discoverWhitelist: [octobluStrategyConfig.clientID, userUuid]
+        configureWhitelist: [octobluStrategyConfig.clientID]
+        discoverWhitelist: [octobluStrategyConfig.clientID]
         clientID: clientID
         clientSecret: clientSecret
+        meshblu:
+          messageForward: [octobluStrategyConfig.clientID]
+      , (error, device) ->
+        meshbluHttp.register
+          type: 'auth:user:little-bits-cloud-proxy'
+          configureWhitelist: [userUuid]
+          discoverWhitelist: [octobluStrategyConfig.clientID, userUuid]
+          parentDevice: device.uuid
+          meshblu:
+            messageForward: [device.uuid]
+        , (error, userDevice) ->
+          meshbluHttp.update device.uuid, sendWhitelist: [userDevice.uuid]

 app.get '/', passport.authenticate('octoblu')
 app.get '/api/octoblu/callback', passport.authenticate('octoblu'), (req, res) ->


CHeck error event handling

CHeck error event handling

--- test/bot.coffee
+++ test/bot.coffee
@@ -79,6 +79,16 @@
     @slackbot.topic {room: 'D1232'}, 'DM'
     should.not.exists(@stubs._topic)

+describe 'Receiving an error event', ->
+  it 'Should propogate that error', ->
+    @hit = false
+    @slackbot.robot.on 'error', (error) =>
+      error.msg.should.equal 'ohno'
+      @hit = true
+    @hit.should.equal false
+    @slackbot.error {msg: 'ohno', code: -2}
+    @hit.should.equal true
+
 describe 'Handling incoming messages', ->
   it 'Should handle regular messages as hoped and dreamed', ->
     @slackbot.message {text: 'foo', user: @stubs.user, channel: @stubs.channel}


add displayNames to app components

add displayNames to app components

--- app/app.coffee
+++ app/app.coffee
@@ -27,17 +27,20 @@
 settings.default 'app', 'intro_command', "help 'introduction'"

 AppComponent = React.createClass
+  displayName: 'AppComponent'
   render: ->
     React.DOM.div {className: 'lead'},
       React.DOM.div {className: 'nav-bar'}, 'lead'
       this.props.activeRouteHandler()

 HelpWrapperComponent = React.createClass
+  displayName: 'HelpWrapperComponent'
   mixins: [Context.ContextAwareMixin]
   render: ->
     Builtins.help_component @state.ctx, @props.key

 HelpComponent = React.createClass
+  displayName: 'HelpComponent'
   render: ->
     # TODO don't lie about class. fix the stylesheet to apply
     React.DOM.div {className: 'output cell'},
@@ -45,6 +48,7 @@
         HelpWrapperComponent {key: @props.params.key}

 NewNotebookComponent = React.createClass
+  displayName: 'NewNotebookComponent'
   render: ->
     intro_command = settings.get 'app', 'intro_command'
     if intro_command? and intro_command != ''
@@ -55,6 +59,7 @@
       }

 GistNotebookComponent = React.createClass
+  displayName: 'GistNotebookComponent'
   render: ->
     gist = @props.params.gist
     Notebook.NotebookComponent {imports, module_names, init: (notebook) ->
@@ -66,11 +71,13 @@
     }

 Base64EncodedNotebookCellComponent = React.createClass
+  displayName: 'Base64EncodedNotebookCellComponent'
   render: ->
     value = atob @props.params.splat
     SingleCoffeeScriptCellNotebookComponent {value}

 SingleCoffeeScriptCellNotebookComponent = React.createClass
+  displayName: 'SingleCoffeeScriptCellNotebookComponent'
   render: ->
     value = @props.value
     Notebook.NotebookComponent {imports, module_names, init: (notebook) ->


hopefully make public path update more robust.

--- src/smc-project/public-paths.coffee
+++ src/smc-project/public-paths.coffee
@@ -2,7 +2,7 @@
 Monitoring of public paths in a running project.
 ###

-UPDATE_INTERVAL_S = 30
+UPDATE_INTERVAL_S = 20
 #UPDATE_INTERVAL_S = 5  # for testing

 fs         = require('fs')
@@ -113,8 +113,10 @@
                     cb()
                 else
                     d('change -- update database table')
-                    @_table.set({id:id, last_edited:new Date()}, 'deep', cb)
+                    @_table.set({id:id, last_edited:new Date()}, 'shallow')
                     @_table.save()  # and also cause change to get saved to database.
+                    # This can be more robust (if actually connected).
+                    @_client.query({query:{id:id, last_edited:new Date()}, cb:cb})
         ], (err) =>
             # ignore err
             cb?()


Classroom: Chapter validation method is now async.

--- client/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
+++ client/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
@@ -34,8 +34,14 @@
     options.buttons.unshift
       title      : "Submit Code"
       cssClass   : "cupid-green"
+      loader     :
+        color    : "#FFFFFF"
+        diameter : 13
       callback   : (panel, workspace) =>
-        @validateChapter panel, workspace
+        button   = @getActivePanel().headerButtons['Submit Code']
+        button.showLoader()
+        @validateChapter panel, workspace, =>
+          button.hideLoader()
     ,
       title      : "Join"
       cssClass   : "cupid-green join-button"
@@ -54,16 +60,18 @@
         title    : "Show Chapter information"
       click      : => @animateContent @chapterDescription

-  validateChapter: (panel, workspace) ->
+  validateChapter: (panel, workspace, callback) ->
     {config}   = @getData()
-    try
-      result = config.validation? panel, workspace
-      if result
-        @handleChapterSuccess()
-        config.onSuccess? panel, workspace
-      else
-        @handleChapterFailed()
-        config.onFailed?  panel, workspace
+    if config.validate
+      config.validate panel, workspace, (result) =>
+        if result
+          @handleChapterSuccess()
+          config.onSuccess? panel, workspace
+        else
+          @handleChapterFailed()
+          config.onFailed?  panel, workspace
+    else
+      callback null

   handleChapterSuccess: ->
     data           = @getData()


AuthPanel is now rid aware.

AuthPanel is now rid aware.

--- picoCTF-web/web/coffee/front-page.coffee
+++ picoCTF-web/web/coffee/front-page.coffee
@@ -53,7 +53,7 @@
           <Row>
             <div>
               {if @props.groupName.length > 0 then showGroupMessage() else <span/>}
-              {if @props.emailFilter.length > 0 then showEmailFilter() else <span/>}
+              {if @props.emailFilter.length > 0 and not @props.rid then showEmailFilter() else <span/>}
             </div>
             <Col md={6}>
               <Input type="text" id="first-name" valueLink={@props.firstname} label="First Name"/>
@@ -158,6 +158,7 @@
     page: "Login"
     settings: {}
     gid: params.g
+    rid: params.r
     status: params.status
     groupName: ""
     eligibility: "eligible"
@@ -170,6 +171,7 @@
       .done ((req) ->
         @setState update @state,
           groupName: $set: req.data.name
+          affiliation: $set: req.data.name
           settings: $merge: req.data.settings
           page: $set: "Register"
       ).bind this
@@ -200,7 +202,7 @@
             message: "You have been sent a verification email. You will need to complete this step before logging in."

           if @state.settings.max_team_size > 1
-            if @state.settings.email_verification
+            if @state.settings.email_verification and not @state.rid
               apiNotify verificationAlert
             else
               apiNotify resp
@@ -273,7 +275,7 @@
         <Col md={6} mdOffset={3}>
           <LoginForm setPage={@setPage} status={@state.page} onRegistration={@onRegistration}
             onLogin={@onLogin} onPasswordReset={@onPasswordReset} emailFilter={@state.settings.email_filter}
-            groupName={@state.groupName}{...links}/>
+            groupName={@state.groupName} rid={@state.rid} gid={@state.gid} {...links}/>
         </Col>
       </div>


Added tracking for the real search

Added tracking for the real search

--- core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
@@ -23,6 +23,7 @@
       placeholder: @placeholder(options.type)

     @bindTo @_text_input_view, 'focus', @focus, @
+    @bindTo @model, 'change', @queryChanges, @

   placeholder: (type) ->
     if type == "supporting"
@@ -79,3 +80,8 @@
     @setQuery ''
     @wheel.reset()
     @wheel_region.currentView.render()
+
+  queryChanges: ->
+    unless @query_has_changed
+      @query_has_changed = true
+      mp_track "Evidence: Started searching"


push to pull

push to pull

--- src/app.coffee
+++ src/app.coffee
@@ -10,7 +10,7 @@
   center: center
   zoom: zoom
   minZoom: 7
-  maxZoom: 12
+  maxZoom: 13

 app.geoserverUrl = "http://data.usgin.org/arizona/ows"


fix login

fix login

--- src/app.coffee
+++ src/app.coffee
@@ -51,7 +51,7 @@
     appName: 'Scrumble'
     tokenExpiration: 'never'
     scope: ['read', 'account'] #, 'write']
-    returnUrl: window.location.origin + window.location.pathname
+    returnUrl: window.location.origin
   }

 app.config ($mdIconProvider) ->


comment

comment

--- app/coffee/go_rules.coffee
+++ app/coffee/go_rules.coffee
@@ -8,6 +8,7 @@
   return state[y][x] is 'empty'

 go_rules.getNewState = (color, x, y, state) ->
+  # given a color stone to play at (x,y), return the new board state
   newState = $.extend(true, [], state)  # deep copy
   newState[y][x] = color
   for [xn, yn] in neighboringPoints(x, y, newState)


Enable session spectating for WebDev levels

--- app/lib/LevelLoader.coffee
+++ app/lib/LevelLoader.coffee
@@ -153,7 +153,8 @@
       url += "?course=#{@courseID}" if @courseID

     session = new LevelSession().setURL url
-    session.project = ['creator', 'team', 'heroConfig', 'codeLanguage', 'submittedCodeLanguage', 'state', 'submittedCode'] if @headless
+    if @headless and not @level.isType('web-dev')
+      session.project = ['creator', 'team', 'heroConfig', 'codeLanguage', 'submittedCodeLanguage', 'state', 'submittedCode']
     @sessionResource = @supermodel.loadModel(session, 'level_session', {cache: false})
     @session = @sessionResource.model
     if @opponentSessionID


Fix collection paging

Fix collection paging

Closes #1006

--- app/pages/collections.cjsx
+++ app/pages/collections.cjsx
@@ -49,9 +49,10 @@
     'collection-show'

   listCollections: ->
-    query = Object.create @props.query ? {}
+    query = {}
     query.owner = @props.params.owner if @props.params?.owner?
     query.include = 'owner'
+    Object.assign query, @props.query

     apiClient.type('collections').get query


validationResults expanded

validationResults expanded

--- src/hit.coffee
+++ src/hit.coffee
@@ -4,7 +4,9 @@
   constructor: ->
     @schemaVersion = 2
     @request =
-      validationResults: {}
+      validationResults:
+        headers: undefined
+        body: undefined

       defined:
         userSchemas: []
@@ -20,7 +22,9 @@

     @response =
-      validationResults: {}
+      validationResults:
+        headers: undefined
+        body: undefined

       defined:
         userSchemas: []


artik10.coffee: Add jumpers instructions

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- artik10.coffee
+++ artik10.coffee
@@ -2,7 +2,7 @@
 { networkOptions, commonImg, instructions } = deviceTypesCommon

 BOARD_SHUTDOWN_ARTIK = 'The device has performed a shutdown. Press the power switch PSW1 to the off position.'
-SET_JUMPER_SD = 'Set SW2 dip switch to position 1:on, 2:on.'
+SET_JUMPER_SD = 'Set SW2 dip switch to position 1:on, 2:on. Also, make sure jumpers J20 and J36 are set towards the edge of the board.'
 SET_JUMPER_EMMC = 'Set SW2 dip switch to position 1:off, 2:off.'
 BOARD_POWERON_ARTIK = 'Press the power switch PSW1 to the on position. Press and hold for 1 second the SW3 POWER push button.'


Bootstrap is no longer vendored

Bootstrap is no longer vendored

--- tasks/build-task.coffee
+++ tasks/build-task.coffee
@@ -41,7 +41,6 @@
       path.join('git-utils', 'deps')
       path.join('oniguruma', 'deps')
       path.join('vendor', 'apm')
-      path.join('vendor', 'bootstrap', 'docs')
     ]
     ignoredPaths = ignoredPaths.map (ignoredPath) -> "(#{ignoredPath})"
     nodeModulesFilter = new RegExp(ignoredPaths.join('|'))


Update coffee.coffee

Update coffee.coffee

--- templates/coffee.coffee
+++ templates/coffee.coffee
@@ -65,7 +65,8 @@
   s ")(this, (#{['root'].concat(args).join(', ')}) ->"

   s options.indent + options.contents
-
+  unless options.global
+    s options.indent + 'return'
   s ")"

   buff.join('\n')


Added Thief and Pirate Ship to the BasicAI actionPriority list.

--- basicAI.coffee
+++ basicAI.coffee
@@ -283,6 +283,8 @@
     "Chapel" if my.ai.wantsToTrash(state)
     "Trade Route" if my.ai.wantsToTrash(state)
     "Mint" if my.ai.choose('mint', state, my.hand)
+    "Pirate Ship"
+    "Thief"
     "Bureaucrat"
     "Conspirator" if my.actions < 2
     "Herbalist"


Remove keymaps

Remove keymaps

--- keymaps/deprecation-cop.cson
+++ keymaps/deprecation-cop.cson
@@ -1,11 +0,0 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
-  'ctrl-alt-o': 'deprecation-cop:toggle'


Skip FF tests

Skip FF tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -81,10 +81,6 @@
               browserName: 'internet explorer'
               platform: 'Windows 8.1',
               version: '11'
-            ,
-              browserName: 'firefox'
-              platform: 'Windows 7',
-              version: '33'
           ]
           build: process.env.TRAVIS_JOB_ID
           testname: 'Create.js cross-browser tests'


Spoke too soon, saucelabs was starting to work

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -129,7 +129,7 @@

   grunt.registerTask 'build', ['clean', 'compileParser', 'coffee:lib', 'browserify', 'uglify']

-  grunt.registerTask 'test', ['coffee:test', 'qunit', 'simplemocha']
+  grunt.registerTask 'test', ['coffee:test', 'qunit', 'simplemocha', 'saucelabs']

   grunt.registerTask 'saucelabs', ['connect', 'saucelabs-qunit']


fix running on mobile, disable android for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -136,6 +136,7 @@
       options:
         configFile: 'tests/karma/karma.conf.coffee'
         exclude: ['tests/mocha/editor.js']
+        singleRun: true
       karma:
         singleRun: false
       unit:
@@ -158,7 +159,7 @@
         browsers: ['linux-chrome', 'linux-firefox']
         reporters: 'dots'
       'remote-mobile':
-        browsers: ['ipad', 'iphone', 'android']
+        browsers: ['ipad', 'iphone']      # Testing on android is currently too slow
         reporters: 'dots'

     watch:


set `startDate` and `endDate` for date range facet close #55

--- _src/js/views/facets/daterange.coffee
+++ _src/js/views/facets/daterange.coffee
@@ -4,7 +4,16 @@
 	template: require( "../../tmpls/daterange.jade" )

 	forcedDateRangeOpts: =>
-		opens: "right"
+		_opts =
+			opens: "right"
+
+		if @model.get("value")?[0]?
+			_opts.startDate = @model.get("value")[0]
+
+		if @model.get("value")?[1]?
+			_opts.endDate = @model.get("value")[1]
+
+		return _opts

 	events: =>
 		return


regarde:js copies first, to speed up reload

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -154,7 +154,7 @@
       # Changes to app code should be validated and re-copied to the `build`, triggering `regarde:build`
       js:
         files:      '<%= CLIENT_DIR + JS_FILES %>'
-        tasks:      [ 'parallel:jshint', 'copy:js' ]
+        tasks:      [ 'copy:js', 'parallel:jshint' ]

       # Changes to app styles should re-compile, triggering `regarde:build`
       less:


don't conflict with other project I work on

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,7 +43,7 @@

     connect:
       options:
-        port: 9001
+        port: 8999
         hostname: '0.0.0.0'
         livereload: 35728
         middleware: (connect, options) ->


Push mapfiles to github.io. For now, do not lint JavaScript.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,9 @@
             src: [
                 'index.html'
                 'app.min.css'
+                'app.min.css.map'
                 'app.min.js'
+                'app.min.js.map'
                 'require.min.js'
             ]

@@ -86,10 +88,11 @@
                     'src/css/*.css'
                 ]
                 tasks: [
+                    'htmlmin'
                     'cssmin'
                     'babel'
                     'uglify'
-                    'jshint'
+                    # 'jshint'
                     'gh-pages'
                     # 'mocha-chai-sinon'
                 ]


updated the `grunt patch` task to ensure it'll do the right thing in the future -- there was a one-time-patch bit in there -- a tested it to make sure it does what it's supposed to do.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -157,7 +157,7 @@

         content = fs.readFileSync file
         content = String(content)
-        content = content.replace(/<script id="profile_code_chunk">[\s\S]+?<\/script>/, '<script id="profile_code_chunk">\n' + chunk_data + '    </script>\n    <script id="profile_code_chunk_2">\n      debounce_profile_stop();\n    </script>');
+        content = content.replace(/<script id="profile_code_chunk">[\s\S]+?<\/script>/, '<script id="profile_code_chunk">\n' + chunk_data + '    </script>');
         fs.writeFileSync(file, content)
       )


Updated grunt with more tasks

Updated grunt with more tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -250,7 +250,7 @@
       "connect:dev"
     ]

-  grunt.registerTask "serve", (target) ->
+  grunt.registerTask "serveLaunchApp", (target) ->
     grunt.task.run [
       "buildDev"
       "open:serveChrome"
@@ -259,12 +259,18 @@
       "watch"
     ]

-  grunt.registerTask "serveTest", (target) ->
+  grunt.registerTask "serveLaunchTest", (target) ->
     grunt.task.run [
       "buildDev"
       "open:testChrome"
       "open:testFirefox"
       "open:testIE"
+      "watch"
+    ]
+
+  grunt.registerTask "serve", (target) ->
+    grunt.task.run [
+      "buildDev"
       "watch"
     ]


* grunt test use dot report.

* grunt test use dot report.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -35,18 +35,9 @@
           ui: 'bdd'
           quiet: false
           require: 'coffee-script/register'
-          #reporter: 'dot'
+          reporter: 'dot'
           check: ['src/**/*.coffee', 'test/**/*.js', 'test/**/*.coffee']
         src: ['test/**/*.coffee']
-
-    powerbuild:
-      options:
-        sourceMap: true
-        node: false
-        handlers:
-          '.coffee': (src, canonicalName) ->
-            {js, v3SourceMap} = coffee.compile src, sourceMap: true, bare: true
-            return {code: js, map: v3SourceMap}

     watch:
       options:
@@ -72,9 +63,8 @@
   grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-contrib-clean')
   grunt.loadNpmTasks('grunt-newer')
-  grunt.loadNpmTasks('powerbuild')

-  grunt.registerTask('build', ['newer:coffee', 'copy'])
+  grunt.registerTask('build', ['newer:coffee', 'newer:copy'])
   grunt.registerTask('rebuild', ['clean', 'build'])
   grunt.registerTask('test', ['build', 'mochaTest'])
   grunt.registerTask('watch', ['watch'])


Rename 'me' page to 'workspace'

Rename 'me' page to 'workspace'

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -137,7 +137,7 @@
               'cs!pages/home/home'
               'cs!pages/contents/contents'
               'cs!pages/search/search'
-              'cs!pages/me/me'
+              'cs!pages/workspace/workspace'

               # FIX: edit modules should be loaded in separate modules
               'select2'


organizing session mock

organizing session mock

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,7 +12,7 @@
 				res.setHeader "Content-Type", "application/json; charset=UTF-8"
 				res.end JSON.stringify(cookies: req.cookies)
 				return
-			'/new_session.js': (req, res, next) ->
+			'/my_current_session_mock.js': (req, res, next) ->
 				res.setHeader "Set-Cookie", "_my_session_token=#{tokens._my_session_token}"
 				res.setHeader "Content-Type", "text/javascript; charset=UTF-8"
 				res.end "var expectedTokens = #{JSON.stringify(tokens)};"
@@ -115,7 +115,7 @@
 					'test/vendor/**/*.js'
 				]
 				helpers: [
-					'new_session.js'
+					'my_current_session_mock.js'
 				]
 				host: 'http://localhost:<%= connect.server.options.port %>/'
 				specs: [


Fix cca.js getting suck on user input the first time building android (#2623)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -246,7 +246,8 @@

     exec: {
       ccaCreateDev: {
-        command: '<%= ccaJsPath %> create <%= androidDevPath %> org.uproxy.uProxy "uProxy" --link-to=<%= ccaDevPath %>'
+        # Pipe 'no' for the first time cca.js asks whether to send usage stats.
+        command: 'echo no | <%= ccaJsPath %> create <%= androidDevPath %> org.uproxy.uProxy "uProxy" --link-to=<%= ccaDevPath %>'
       }
       ccaCreateDist: {
         command: '<%= ccaJsPath %> create <%= androidDistPath %> org.uproxy.uProxy "uProxy" --link-to=<%= ccaDistPath %>'


Clarified tasks

Clarified tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -122,7 +122,7 @@
       core:
         files:
           'dist/server/factlink.core.min.js':               ['dist/factlink.core.js']
-      all:
+      all_except_core:
         files:
           'dist/server/factlink.start_annotating.min.js':   ['dist/factlink.start_annotating.js']
           'dist/server/factlink.stop_annotating.min.js':    ['dist/factlink.stop_annotating.js']
@@ -193,11 +193,12 @@
     content_with_hash = content.replace /&\*HASH_PLACE_HOLDER\*&/, source_file_hash
     grunt.file.write destination_file_path, content_with_hash

-  grunt.registerTask 'compile', ['copy', 'coffee', 'less', 'concat:core', 'uglify:core', 'corehasher', 'concat']
+  grunt.registerTask 'core', ['concat:core', 'uglify:core', 'corehasher']
+  grunt.registerTask 'compile', ['copy', 'coffee', 'less', 'core', 'concat']
   grunt.registerTask 'test',    ['jshint', 'qunit']

-  grunt.registerTask 'default', ['compile', 'test', 'uglify:all']
-  grunt.registerTask 'server',  ['compile', 'uglify:all', 'cssmin']
+  grunt.registerTask 'default', ['compile', 'test', 'uglify:all_except_core']
+  grunt.registerTask 'server',  ['compile', 'uglify:all_except_core', 'cssmin']

   grunt.loadNpmTasks 'grunt-contrib-less'
   grunt.loadNpmTasks 'grunt-contrib-uglify'


Fix issue for stores that are not https only

Fix #34

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -25,7 +25,10 @@
   # portalHost is also used by connect-http-please
   # example: basedevmkp.vtexcommercestable.com.br
   portalHost = "#{accountName}.#{environment}.com.br"
-  portalProxyOptions = url.parse("https://#{portalHost}/")
+  if secureUrl
+    portalProxyOptions = url.parse("https://#{portalHost}/")
+  else
+    portalProxyOptions = url.parse("http://#{portalHost}/")
   portalProxyOptions.preserveHost = true

   rewriteLocation = (location) ->


remove initial current folder from distil and server commands

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,16 +37,15 @@
         tasks: ['shell:distil_bootstrap']
     shell:
       distil_game:
-        command: './node_modules/moonshine/bin/moonshine distil -d lua lua'
+        command: 'node_modules/moonshine/bin/moonshine distil -d lua lua'
       distil_bootstrap:
-        command: './node_modules/moonshine/bin/moonshine distil -o js/boot.lua.json js/boot.lua'
+        command: 'node_modules/moonshine/bin/moonshine distil -o js/boot.lua.json js/boot.lua'
       distil_examples:
-        command: './node_modules/moonshine/bin/moonshine distil -d examples examples'
+        command: 'node_modules/moonshine/bin/moonshine distil -d examples examples'
     connect:
       server:
         options:
           port: 8000
-          base: '.'

   grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-contrib-watch')


Add comment explaining the "cca build" commands

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -202,6 +202,8 @@
       cwd: '<%= androidDistPath %>'
       command: '<%= ccaAddPluginsCmd %>'
     }
+    # This pair of "cca build" commands is exactly as recommended at
+    # https://github.com/MobileChromeApps/mobile-chrome-apps/blob/master/docs/Publish.md
     ccaBuildAndroid: {
       cwd: '<%= androidDevPath %>'
       command: '<%= ccaJsPath %> build android --debug --webview=system --android-minSdkVersion=21; <%= ccaJsPath %> build android --debug --webview=crosswalk'


parameters in display video

parameters in display video

--- common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
+++ common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
@@ -36,6 +36,7 @@
       @volumeControl = new VideoVolumeControl el: @$('.secondary-controls')
     @speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
     @progressSlider = new VideoProgressSlider el: @$('.slider')
+    @debugger_1
     @player = new YT.Player @video.id,
       playerVars:
         controls: 0
@@ -44,6 +45,8 @@
         showinfo: 0
         enablejsapi: 1
         modestbranding: 1
+        start: 10
+        end: 20
       videoId: @video.youtubeId()
       events:
         onReady: @onReady


Remove useless file

Remove useless file

--- test.coffee
+++ test.coffee
@@ -1,2 +0,0 @@
-define (test) ->
-  test.property = "foo"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -25,8 +25,8 @@
     alpha: 0.2

   5.times (n) ->
-    if n is 0
-
+
+     p

   player.update()
   equals player.I.alpha, 0.2


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -17,8 +17,8 @@
       class: "aaaaa"

 test "#closest", ->
-  o GameObject()
-  equals
+  o = GameObject()
+  equals ,

 test "[event] create", 2, ->
   GameObject


Add support for gitignores other than .gitignore

--- grammars/generic-config.cson
+++ grammars/generic-config.cson
@@ -1,5 +1,7 @@
 'fileTypes': [
   'gitattributes'
+  '.config/git/ignore'
+  '.git/info/exclude'
   'gitignore'
   'conf'
   'ctags'


more robust to callback errors

more robust to callback errors

--- lib/connection/client.coffee
+++ lib/connection/client.coffee
@@ -17,9 +17,11 @@
       else
         @handlers[type] data
     else if @callbacks.hasOwnProperty type
-      @callbacks[type] data
-      delete @callbacks[type]
-      @loading.done()
+      try
+        @callbacks[type] data
+      finally
+        delete @callbacks[type]
+        @loading.done()
     else
       console.log "julia-client: unrecognised message #{type}"
       console.log data


also use em instead of defaultCharWidth

also use em instead of defaultCharWidth

--- lib/editor/highlights.coffee
+++ lib/editor/highlights.coffee
@@ -29,7 +29,7 @@
     v.classList.add('ink-profile-line')
     v.classList.add(cl) for cl in classes

-    v.style.width = count*ed.preferredLineLength*ed.defaultCharWidth + 'px'
+    v.style.width = 0.1 + count*ed.preferredLineLength + 'em'
     v

   profileLines: (ls) ->


update Jamesfile.

update Jamesfile.

--- Jamesfile.coffee
+++ Jamesfile.coffee
@@ -17,15 +17,8 @@
     dist.write                   'dist/transparency.js'
     dist.transform(uglify).write 'dist/transparency.min.js'

-# james.task 'watch', ->
-
-#   james.watch 'src/**/*.coffee', -> james.run 'build'
-
 james.task 'watch', ->

-  james.watch '*', (e, f) ->
-    console.log e, f
-    Q.delay(400).done -> throw new Error('foobar')
-
+  james.watch 'src/**/*.coffee', -> james.run 'build'

 james.task 'default', ['build']


Redirect old teachers/units dashboard to new dashboard (#589)

Blocks user from accidentally navigating back to the old dashboard.
Instead user is redirected to new dashboard.

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -287,8 +287,8 @@
     'teachers/projects/:classroomId': go('core/SingletonAppVueComponentView')
     'teachers/classes/:classroomID/:studentID': go('teachers/TeacherStudentView', { redirectStudents: true, teachersOnly: true })
     'teachers/classes/:classroomID': teacherProxyRoute(go('courses/TeacherClassView', { redirectStudents: true, teachersOnly: true }))
-    'teachers/courses': redirect('/teachers/units') # Redirected 8/20/2019
-    'teachers/units': go('courses/TeacherCoursesView', { redirectStudents: true })
+    'teachers/courses': redirect('/teachers') # Redirected 8/20/2019
+    'teachers/units': redirect('/teachers') # Redirected 9/10/2020
     'teachers/course-solution/:courseID/:language': go('teachers/TeacherCourseSolutionView', { redirectStudents: true })
     'teachers/demo': redirect('/teachers/quote')
     'teachers/enrollments': redirect('/teachers/licenses')


Improve differ, fix #4066

Improve differ, fix #4066

--- app/core/deltas.coffee
+++ app/core/deltas.coffee
@@ -96,7 +96,7 @@

   delta

-objectHash = (obj) -> if obj? then (obj.name or obj.id or obj._id or JSON.stringify(_.keys(obj))) else 'null'
+objectHash = (obj) -> if obj? then (obj.name or obj.id or obj._id or JSON.stringify(_.keys(obj).sort())) else 'null'

 module.exports.makeJSONDiffer = ->


Fix expand snippets

Fix expand snippets

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -49,7 +49,7 @@
   confirm: (suggestion) ->
     @replaceTextWithMatch(suggestion)
     setTimeout(=>
-      atom.commands.dispatch('snippets:expand')
+      atom.commands.dispatch(atom.views.getView(@editor), 'snippets:expand')
     , 1)
     return false

@@ -67,10 +67,6 @@
     cursorPosition = @editor.getCursorBufferPosition()
     buffer.delete(Range.fromPointWithDelta(cursorPosition, 0, -match.prefix.length))
     @editor.insertText(match.word)
-
-    # Move the cursor behind the new word
-    suffixLength = match.word.length - match.prefix.length
-    @editor.setSelectedBufferRange([startPosition, [startPosition.row, startPosition.column + suffixLength]])

   ###
    * Finds possible matches for the given string / prefix


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -85,7 +85,7 @@

         path.transition().duration(1500).attrTween('d', (d,i) ->
             `var i`
-            theOldDataInPie = @oldPieData.empty() ? piedata
+            theOldDataInPie = @oldPieData ? piedata
             # Interpolate the arcs in data space
             s0 = undefined
             e0 = undefined


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -224,7 +224,7 @@

         pie = d3.layout.pie()
             .sort(null)
-            #.value((d) -> d.value)
+            .value((d) -> d.value)

         arc = d3.svg.arc()
           .outerRadius(radiuso)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,17 +1,23 @@
 class Dashing.FullpieAgent extends Dashing.Widget

     @accessor 'data'
+    @width = 750

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()
         @update(data.data)
+
     update: (dataSet) ->
+
         if !dataSet
             dataSet = @get("data")
         if !dataSet
             return
+
+        # Remove any previous svg
         $(@node).children("svg").remove();
-        width = 750
+
+        width = @width
         height = 400
         radius = Math.min(width, height) / 2
         radiuso = 130


Agent pie: edit pointer transition

Agent pie: edit pointer transition

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -324,7 +324,7 @@
                 else
                     'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
             )
-        pointers.transition().duration(500).style('opacity',1)
+        pointers.transition().delay(1000).duration(500).style('opacity',1)

         # Display total count
         totalTickets = 0


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -293,7 +293,8 @@
             .attrTween("d", (d,i) ->
                 console.log("d",d)
                 console.log("i",i)
-                i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+                #i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+                i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: 90, endAngle: 135})
                 return (t) ->
                     b = i(t)
                     return arc(b)


added validation before creating team

added validation before creating team

--- servers/lib/server/handlers/createteam.coffee
+++ servers/lib/server/handlers/createteam.coffee
@@ -1,31 +1,16 @@
 Bongo                                   = require "bongo"
 koding                                  = require './../bongo'
 { argv }                                = require 'optimist'
+{ uniq }                                = require 'underscore'
+{ dash, daisy }                         = Bongo
+{ getClientId, handleClientIdNotFound } = require './../helpers'
 KONFIG                                  = require('koding-config-manager').load "main.#{argv.c}"
-{ getClientId, handleClientIdNotFound } = require './../helpers'
-{ dash }                                = Bongo
-{ uniq }                                = require 'underscore'

 module.exports = (req, res, next) ->

   { body }                       = req
   { JUser, JGroup, JInvitation } = koding.models
-  { # companyName, team name, basically a title, can be changed
-    companyName
-    # slug is team slug, unique name. Can not be changed
-    slug
-    redirect
-    # invitees are comma separated emails which will be invited to that team
-    invitees
-    # domains holds allowed domains for signinup without invitation, comma separated
-    domains
-    # newsletter holds announcements config.
-    newsletter
-    # is group creator already a member
-    alreadyMember
-    # username of the user, can be already registered or a new one for creation
-    username
-  } = body
+

   redirect     ?= '/'
   context       = { group: slug }
@@ -49,61 +34,103 @@
     body.emailFrequency         or= {}
     body.emailFrequency.marketing = newsletter is 'true' # convert string boolean to boolean

-    createGroup = (err, result) ->
+    handleGroupCreation = createGroup.bind null, body

-      return res.status(400).send getErrorMessage err  if err?
+    queue = [

-      # don't set the cookie we don't want that
-      # bc we're going to redirect the page to the
-      # group subdomain, if you can set the cookie for
-      # the subdomain - SY cc/ @cihangir
+      ->
+        JGroup.one { slug }, (err, group) ->
+          return res.status(500).send 'an error occured'  if err
+          return res.status(403).send "Sorry,
+            Team URL '#{slug}.koding.com' is already in use"  if group
+          queue.next()

-      # res.cookie 'clientId', result.newToken, path : '/'
+      ->
+        JUser.one { email }, (err, user) ->
+          return res.status(500).send 'an error occured'  if err
+          alreadyMember = true  if user
+          queue.next()

-      # set session token for later usage down the line
-      client.sessionToken         = result.newToken
-      owner                       = result.account
-      client.connection.delegate  = result.account
+      ->
+        if alreadyMember
+        then JUser.login client.sessionToken, body, handleGroupCreation
+        else JUser.convert client, body, handleGroupCreation

-      JGroup.create client,
-        title           : companyName
-        slug            : slug
-        visibility      : 'hidden'
-        defaultChannels : []
-        initialData     : body
-        allowedDomains  : convertToArray domains # clear & convert domains into array
-      , owner, (err, group) ->
+    ]

-        if err or not group
-          console.error 'Error while creating the group', err
-          return res.status(500).send "Couldn't create the group."
+    daisy queue

-        queue = [
-          # add other parallel operations here
-          -> createInvitations client, invitees, (err) ->
-              console.error "Err while creating invitations", err  if err
-              queue.fin()
-        ]
-        dash queue, (err)->
-          # do not block group creation
-          console.error "Error while creating group artifacts", body, err if err

-          # temp code for creating the team-access cookie for development
-          # sandbox and latest share the same config file
-          # so sandbox.koding.com covers both cases ಠ_ಠ - SY
-          if /ngrok|sandbox\.koding/.test KONFIG.hostname
-            console.log "setting cookie for #{slug}.dev.koding.com"
-            res.cookie 'team-access', 'yes', domain : ".dev.koding.com"
+createGroup = (body, err, result) ->

-          # handle the request as an XHR response:
-          return res.status(200).end() if req.xhr
-          # handle the request with an HTTP redirect:
-          res.redirect 301, redirect
+  return res.status(400).send getErrorMessage err  if err?

-    if alreadyMember
-    then JUser.login client.sessionToken, body, createGroup
-    else JUser.convert client, body, createGroup
+  { # companyName, team name, basically a title, can be changed
+    companyName
+    # slug is team slug, unique name. Can not be changed
+    slug
+    redirect
+    # invitees are comma separated emails which will be invited to that team
+    invitees
+    # domains holds allowed domains for signinup without invitation, comma separated
+    domains
+    # newsletter holds announcements config.
+    newsletter
+    # is group creator already a member
+    alreadyMember
+    # username of the user, can be already registered or a new one for creation
+    username
+    email
+  } = body

+  { JUser, JGroup, JInvitation } = koding.models
+
+  # don't set the cookie we don't want that
+  # bc we're going to redirect the page to the
+  # group subdomain, if you can set the cookie for
+  # the subdomain - SY cc/ @cihangir
+
+  # res.cookie 'clientId', result.newToken, path : '/'
+
+  # set session token for later usage down the line
+  client.sessionToken         = result.newToken
+  owner                       = result.account
+  client.connection.delegate  = result.account
+
+  JGroup.create client,
+    title           : companyName
+    slug            : slug
+    visibility      : 'hidden'
+    defaultChannels : []
+    initialData     : body
+    allowedDomains  : convertToArray domains # clear & convert domains into array
+  , owner, (err, group) ->
+
+    if err or not group
+      console.error 'Error while creating the group', err
+      return res.status(500).send "Couldn't create the group."
+
+    queue = [
+      # add other parallel operations here
+      -> createInvitations client, invitees, (err) ->
+          console.error "Err while creating invitations", err  if err
+          queue.fin()
+    ]
+    dash queue, (err)->
+      # do not block group creation
+      console.error "Error while creating group artifacts", body, err if err
+
+      # temp code for creating the team-access cookie for development
+      # sandbox and latest share the same config file
+      # so sandbox.koding.com covers both cases ಠ_ಠ - SY
+      if /ngrok|sandbox\.koding/.test KONFIG.hostname
+        console.log "setting cookie for #{slug}.dev.koding.com"
+        res.cookie 'team-access', 'yes', domain : ".dev.koding.com"
+
+      # handle the request as an XHR response:
+      return res.status(200).end() if req.xhr
+      # handle the request with an HTTP redirect:
+      res.redirect 301, redirect

 # convertToArray converts given comma separated string value into cleaned,


Don't create tokens on project by default

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -6,7 +6,6 @@
 sanitize = require('sanitizer')
 concreteObjectId = require('mongoose').Types.ObjectId
 Errors  = require "../Features/Errors/Errors"
-ProjectTokenGenerator = require '../Features/Project/ProjectTokenGenerator'

 Schema = mongoose.Schema
 ObjectId = Schema.ObjectId
@@ -34,14 +33,8 @@
 	imageName         : { type: String }
 	track_changes     : { type: Object }
 	tokens            :
-		readOnly        : {
-			type: String,
-			default: () -> ProjectTokenGenerator.readOnlyToken()
-		}
-		readAndWrite    : {
-			type: String,
-			default: () -> ProjectTokenGenerator.readAndWriteToken()
-		}
+		readOnly        : { type: String }
+		readAndWrite    : { type: String }
 	tokenAccessReadOnly_refs         : [ type:ObjectId, ref:'User' ]
 	tokenAccessReadAndWrite_refs     : [ type:ObjectId, ref:'User' ]
 	overleaf          :


can enable / disable live model update

can enable / disable live model update

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -7,6 +7,7 @@
       start: "@"
       step: "@"
       end: "@"
+      live: "@"
       ngModel: "="
       ngFrom: "="
       ngTo: "="
@@ -14,16 +15,18 @@
     link: (scope, element, attrs) ->
       slider = $(element)

+      callbackType = if scope.live then 'slide' else 'set'
+
       if scope.ngFrom? and scope.ngTo?
         fromParsed = null
         toParsed = null

-        slider.noUiSlider(
+        config =
           range: [scope.start, scope.end]
           start: [scope.ngFrom or scope.start, scope.ngTo or scope.end]
           step: scope.step or 1
           connect: true
-          slide: ->
+        config[callbackType] = ->
             [from, to] = slider.val()

             fromParsed = parseFloat from
@@ -35,7 +38,8 @@
               scope.ngFrom = fromParsed
               scope.ngTo = toParsed
             )
-        )
+
+        slider.noUiSlider(config)

         scope.$watch('ngFrom', (newVal, oldVal) ->
           if newVal isnt fromParsed
@@ -49,18 +53,19 @@
       else
         parsedValue = null

-        slider.noUiSlider(
+        config =
           range: [scope.start, scope.end],
           start: scope.ngModel or scope.start,
           step: scope.step or 1
           handles: 1
-          slide: ->
+        config[callbackType] = ->
             parsedValue = slider.val()

             scope.$apply(->
               scope.ngModel = parseFloat parsedValue
             )
-        )
+
+        slider.noUiSlider(config)

         scope.$watch('ngModel', (newVal, oldVal) ->
           if newVal isnt parsedValue


Use cookies for setting or getting the selected language

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -6,6 +6,7 @@
     'gridster',
     'ngResource',
     'ngRoute',
+    'ngCookies',
     'leaflet-directive',
     'dynform',
     'angular-flash.service',
@@ -46,7 +47,8 @@
       return s.toString().toLowerCase().replace( /\b([a-z])/g, (ch) -> return ch.toUpperCase()))

   .run (gettextCatalog,$rootScope) ->
-    gettextCatalog.currentLanguage = 'en'
+    $cookies.locale = 'en' unless !!$cookies.locale
+    gettextCatalog.currentLanguage = $cookies.locale
     $rootScope.fields =
       catalog: gettextCatalog
       languages:


Commented paging back inn for user.

Commented paging back inn for user.

--- app/users/index.coffee
+++ app/users/index.coffee
@@ -44,7 +44,7 @@

 list = (req, reply) =>
 	@get req.url.path, (status, response) =>
-		#pagination = makePrevNextLink(req.url.query, req.url.pathname, response.beers.length)
+		pagination = makePrevNextLink(req.url.query, req.url.pathname, response.users.length)
 		reply @renderer.page
 			title: "Brewers"
 			navigationState: 'brewers'
@@ -56,8 +56,8 @@
 					mode: 'list'
 					user: req?.auth?.credentials?.user
 					results: response.users
-		#			nextPage: pagination.next
-		#			prevPage: pagination.prev
+					nextPage: pagination.next
+					prevPage: pagination.prev

 show = (req, reply) =>
 	if(req.auth.isAuthenticated)


Latest Atom configuration

Latest Atom configuration


relative asset build urls

relative asset build urls

# Conflicts:
#	dist/app.css
#	dist/app.css.map
#	dist/app.js
#	dist/app.js.map
#	dist/manifest.json
#	index.html

--- webpack.coffee
+++ webpack.coffee
@@ -50,7 +50,7 @@
   # JS doesn't need this as it gets unique URLs via PHP.
   output:
     path:          "#{process.cwd()}/dist"
-    publicPath:    if hmr then 'http://localhost:'+port+'/dist/' else '/dist/'
+    publicPath:    if hmr then 'http://localhost:'+port+'/dist/' else './dist/'
     filename:      '[name].js'
     chunkFilename: '[id].js'

@@ -112,7 +112,7 @@
   {
     test: /\.(png|gif|jpe?g|svg)$/
     loaders: [
-      'url?limit=10000&name=img/[hash:8].[ext]'
+      'url?limit=10000&name=./img/[hash:8].[ext]'
       'img?progressive=true'
     ]
   }
@@ -122,7 +122,7 @@
   # mean inlining multiple formats that are unncessary for a given user.
   {
     test: /\.(eot|ttf|woff|woff2)$/
-    loader: 'file-loader?name=fonts/[hash:8].[ext]'
+    loader: 'file-loader?name=../fonts/[hash:8].[ext]'
   }

   # JSON #


Test client request is a writable stream

--- test/test_client.coffee
+++ test/test_client.coffee
@@ -4,7 +4,7 @@
 config = __dirname + "/fixtures/echo.ru"

 exports.testClientRequest = (test) ->
-  test.expect 8
+  test.expect 10

   p = process.createProcess config
   p.on 'ready', () ->
@@ -17,9 +17,14 @@
     test.same "/foo", request.path
     test.same "/foo", request.headers['PATH_INFO']

+    test.ok request.writeable
+
     request.end()

-    request.on "response", (response) ->
+    request.on 'close', () ->
+      test.ok true
+
+    request.on 'response', (response) ->
       test.ok response
       test.same 200, response.statusCode


after countdown to sunrise completes, redirect to index

--- source/javascripts/countdown/flipclock.coffee
+++ source/javascripts/countdown/flipclock.coffee
@@ -1,5 +1,7 @@
 //= require ../vendor/flipclock
 //= require ../site/sunrise
+
+redirect = -> window.location.href = "/index.html"

 secondsToSunrise = ->
   today = moment().startOf('day')
@@ -11,7 +13,10 @@
   seconds = secondsToSunrise()
   clock.setTime(seconds)
   console.log "Resetting sunrise clock, with #{seconds} seconds left" if console?
-  setTimeout((-> countDown(clock)), 1e4) if seconds > 10
+  if seconds > 10
+    setTimeout((-> countDown(clock)), 1e4)
+  else
+    setTimeout(redirect, (seconds + 4) * 1000)

 $ ->
   clock = $('.countdown-to-sunrise').FlipClock 60, countdown:true


Update rootView references in benchmarks

--- benchmark/benchmark-suite.coffee
+++ benchmark/benchmark-suite.coffee
@@ -1,19 +1,19 @@
 require './benchmark-helper'
-{$, _, RootView} = require 'atom'
+{$, _, WorkspaceView} = require 'atom'
 TokenizedBuffer = require '../src/tokenized-buffer'

 describe "editorView.", ->
   editorView = null

   beforeEach ->
-    window.rootViewParentSelector = '#jasmine-content'
-    window.rootView = new RootView
-    window.rootView.attachToDom()
-
-    rootView.width(1024)
-    rootView.height(768)
-    rootView.openSync()
-    editorView = rootView.getActiveView()
+    atom.workspaceViewParentSelector = '#jasmine-content'
+    atom.workspaceView = new WorkspaceView
+    atom.workspaceView.attachToDom()
+
+    atom.workspaceView.width(1024)
+    atom.workspaceView.height(768)
+    atom.workspaceView.openSync()
+    editorView = atom.workspaceView.getActiveView()

   afterEach ->
     if editorView.pendingDisplayUpdate
@@ -40,7 +40,7 @@

   describe "300-line-file.", ->
     beforeEach ->
-      rootView.openSync('medium.coffee')
+      atom.workspaceView.openSync('medium.coffee')

     describe "at-begining.", ->
       benchmark "insert-delete", ->
@@ -171,11 +171,11 @@

   describe "9000-line-file.", ->
     benchmark "opening.", 5, ->
-      rootView.openSync('huge.js')
+      atom.workspaceView.openSync('huge.js')

     describe "after-opening.", ->
       beforeEach ->
-        rootView.openSync('huge.js')
+        atom.workspaceView.openSync('huge.js')

       benchmark "moving-to-eof.", 1, ->
         editorView.moveCursorToBottom()


bugfix bar chart

bugfix bar chart

--- packages/custom/transparency/coffee/public/directives/lineGraphDirective.coffee
+++ packages/custom/transparency/coffee/public/directives/lineGraphDirective.coffee
@@ -71,9 +71,7 @@
                     chart.xAxis
                     .tickValues [0...16]
                     .tickFormat (d) ->
-                        dx = $scope.data.data.values[
-
-                        ]
+                        dx = $scope.data.data.values[d]
                         result = ""
                         if (events[dx])
                             result = events[dx][0]


Update whereami spec.

Update whereami spec.

The behavior changed, it no longer trys to keep ranges in bounds.

--- tests/commands/whereami_spec.coffee
+++ tests/commands/whereami_spec.coffee
@@ -34,14 +34,5 @@
           subject.execute([], next: sinon.spy())
           complete()

-        it 'stops on the first index', ->
-          expect(spy.calledWith(0, 8)).to.equal true
-
-      describe 'given I call it with a long tail', ->
-
-        before (complete) ->
-          subject.execute([1, 100], next: sinon.spy())
-          complete()
-
-        it 'bleeds past the last index', ->
-          expect(spy.calledWith(2, 10)).to.equal true
+        it 'blindly puts lines numbers', ->
+          expect(spy.calledWith(-2, 8)).to.equal true


removed some useless auto creating files (using w flag)

--- src/fs.coffee
+++ src/fs.coffee
@@ -776,9 +776,6 @@
 		if typeof options.mode == 'undefined' then options.mode = 438
 		if typeof options.flag == 'undefined' then options.flag = 'w'

-		if !@existsSync(filename)
-			@_addPath(filename, data: data, mode: options.mode)
-
 		fd = @openSync(filename, options.flag, options.mode)
 		@writeSync(fd, new Buffer(data, options.encoding), 0, data.length, 0)
 		@closeSync(fd)
@@ -806,9 +803,6 @@
 		if typeof options.encoding == 'undefined' then options.encoding = 'utf8'
 		if typeof options.mode == 'undefined' then options.mode = 438
 		if typeof options.flag == 'undefined' then options.flag = 'w'
-
-		if !@existsSync(filename)
-			@_addPath(filename, data: '', mode: options.mode)

 		if typeof data == 'string'
 			data = new Buffer(data, options.encoding)


drag: append to header on stop

drag: append to header on stop

--- app/assets/javascripts/bits.js.coffee
+++ app/assets/javascripts/bits.js.coffee
@@ -15,9 +15,9 @@

 	$(".bit").draggable
 	  handle: "p"
-	  # axis: 'y'
 	  stop: (event, ui) ->
-	  	# alert("Bit position saved: T: " + Math.round(ui.position.top) + " L: " + Math.round(ui.position.left))
+	  	#alert("Bit position saved: T: " + ui.position.top + "L:" + ui.position.left + "!")
+
 	  	request = $.ajax(
 	    	url: '/bits/4/position'
 	    	type: 'PUT'
@@ -29,9 +29,10 @@

 	    request.done (data) ->
 	    	console.log(data)
+	    	console.log("Bit position saved: T: " + Math.round(data.x) + " L: " + Math.round(data.y))
+	    	$('#my-menus').append "Bit position saved: T: " + data.x + "L:" + data.y + "!"
 	    	true

-	    #.done(data) -> $('#my-menus').append "Bit position saved: T: " + ui.position.top + "L:" + ui.position.left + "!"

 	    request.fail -> $('my-menus').show()


comment cpu face selection

comment cpu face selection

--- app/assets/javascripts/game.js.coffee
+++ app/assets/javascripts/game.js.coffee
@@ -64,7 +64,7 @@
 #          p1_cards_obj[key] = game_faces().faces[Number(key)]

         p1_face = p1_cards_obj[Object.keys(p1_cards_obj)[Math.floor(Math.random() * 24)]]
-        p1_face.obj.addClass('sos')
+#        p1_face.obj.addClass('sos')
       else
         # send request to channel


Populate data before setting fields to view mode

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -194,9 +194,9 @@
 ready = ->
   if is_resume_page()
     install_handlers()
+    populate_data()
     set_fields_to_edit_mode(false)
     set_dirty(false)
-    populate_data()
     display_data()

 $(document).ready(ready)


Changed Lara close protocol to use a canClose message

--- app/assets/javascripts/labbook.coffee
+++ app/assets/javascripts/labbook.coffee
@@ -19,7 +19,7 @@
     @isUpload = @$element.data('is-upload')

     @implementsIFramePhone = false
-    @hasUnsavedState = false
+    @canClose = false

     unless @isUpload
       intId = @$element.data('interactive-id')
@@ -80,10 +80,9 @@
     @$dialog.dialog('open')

   onDialogClosing: (event) ->
-    if @implementsIFramePhone and @hasUnsavedState
-      confirm("You have unsaved Labbook content.  Are you sure you want to close?")
-    else
-      true
+    return true if not @implementsIFramePhone or @canClose
+    @phone.post('canClose')
+    return false

   onDialogClose: ->
     # Notify LARA that dialog has been closed, so album has been (probably!) updated.
@@ -104,22 +103,24 @@
     })

   setIframeUrl: (newUrl) ->
-    @hasUnsavedState = false
+    @canClose = false
     @implementsIFramePhone = false

     @startWaiting(t('LOADING_LABBOOK'))
     @$iframe.hide()
     @$iframe.attr('src', newUrl)
     @$iframe.one 'load', =>
-      @phone?.disconnect()
-      @phone = IframePhoneManager.getPhone(@$iframe[0], => @phoneAnswered())
+      @setupPhone()
       @stopWaiting()
       @$iframe.fadeIn()

-  phoneAnswered: ->
-    @implementsIFramePhone = true
-    @phone.addListener 'interactiveState', (state) =>
-      @hasUnsavedState = state isnt "nochange"
+  setupPhone: ->
+    @phone = IframePhoneManager.getPhone @$iframe[0], =>
+    @phone.addListener 'close', =>
+      @canClose = true
+      @$dialog.dialog('close')
+    @phone.addListener 'connected', (connected) =>
+      @implementsIFramePhone = connected

   startWaiting: (message) ->
     startWaiting(message, WAIT_MSG_SEL) # defined in wait-message.js


Put the updater at the correct place.

Put the updater at the correct place.

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -157,6 +157,8 @@
             #console.log($(@))
             reader_set = true
             return false
+        $("#statusbar").text "Your settings have been detected."
+

       #on change event
       $selects.change () ->
@@ -180,5 +182,4 @@
       #trigger mappings on load
       $selects.find('option:selected').each () ->
         triggerMappings($(@).data('mappings'), $results.attr('id')) if @.value
-        $("#statusbar").text "Your settings have been detected."


Store original map button text in data-text

--- app/assets/javascripts/maps.js.coffee
+++ app/assets/javascripts/maps.js.coffee
@@ -50,6 +50,7 @@

     # Create a map if not present
     if $box.find("iframe").length < 1
+      $(@).attr('data-text', $(@).text())
       $(@).text("Dölj karta")
       $box.append $("

")

@@ -60,5 +61,5 @@

     # Map is present, remove it
     else
-      $(@).text("Visa på karta")
+      $(@).text($(@).attr('data-text'))
       $box.find(".map-box").remove()


group/transferOwnership: add password and user type checks

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1489,24 +1489,31 @@
         return callback err if err
         return callback new KodingError 'You must be the owner to perform this action!' unless owner

-        JAccount.one { _id:accountId }, (err, account) =>
-          return callback err if err
-
-          @fetchRolesByAccount account, (err, newOwnersRoles) =>
+        @checkUserPassword delegate, currentPassword, (err) =>
+
+          return callback new KodingError err  if err
+
+          JAccount = require '../account'
+          JAccount.one { _id: accountId }, (err, account) =>
             return callback err if err

-            kallback = (err) ->
-              callback err
-
-            # give rights to new owner
-            queue = difference(['member', 'admin'], newOwnersRoles).map (role) => (fin) =>
-              @addMember account, role, fin
-
-            async.parallel queue, (err) ->
-              return kallback err  if err
-              # transfer ownership
-              owner.update { $set: { targetId: account.getId() } }, kallback
-
+            unless account.type is 'registered'
+              return callback new KodingError 'You cannot transfer ownership to this account'
+
+            @fetchRolesByAccount account, (err, newOwnersRoles) =>
+              return callback err if err
+
+              kallback = (err) ->
+                callback err
+
+              # give rights to new owner
+              queue = difference(['member', 'admin'], newOwnersRoles).map (role) => (fin) =>
+                @addMember account, role, fin
+
+              async.parallel queue, (err) ->
+                return kallback err  if err
+                  # transfer ownership
+                  owner.update { $set: { targetId: account.getId() } }, kallback
   ensureUniquenessOfRoleRelationship:(target, options, fallbackRole, roleUnique, callback) ->
     unless callback
       callback   = roleUnique