Fix toggle dashboard options on load

Fix toggle dashboard options on load

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -572,14 +572,17 @@
     e.preventDefault()
     $(".page-dashboard").toggleClass("dashboard-post")

+  if $(".js-press-comment-correct input:checked").val() == "no"
+    $(".js-press-comment-feeback").addClass("section-confirmed")
   $(".js-press-comment-correct input").change ->
     $(".js-press-comment-feeback").removeClass("section-confirmed")
-
     if $(".js-press-comment-correct input:checked").val() == "no"
       $(".js-press-comment-feeback").addClass("section-confirmed")

+
+  if $(".js-accept-nomination input:checked").val() == "yes"
+    $(".js-accept-nomination-details").addClass("section-confirmed")
   $(".js-accept-nomination input").change ->
     $(".js-accept-nomination-details").removeClass("section-confirmed")
-
     if $(".js-accept-nomination input:checked").val() == "yes"
       $(".js-accept-nomination-details").addClass("section-confirmed")


d3の読み込みpathが変更されていたのに対応

d3の読み込みpathが変更されていたのに対応

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -5,7 +5,7 @@
 #= require jquery_ujs
 #= require moment/moment
 #= require moment/locale/ja
-#= require d3/d3.min
+#= require d3/build/d3.min
 #= require cal-heatmap/cal-heatmap.min
 #= require fullcalendar/dist/fullcalendar.min
 #= require fullcalendar/dist/lang/ja


Set path for pinned nav cookie

Set path for pinned nav cookie

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -257,7 +257,7 @@

   # Sidenav pinning
   if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
-    $.cookie('pin_nav', 'false')
+    $.cookie('pin_nav', 'false', { path: '/' })
     $('.page-with-sidebar')
       .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
       .removeClass('page-sidebar-pinned')
@@ -271,7 +271,7 @@
       $(this).toggleClass 'is-active'

       if $.cookie('pin_nav') is 'true'
-        $.cookie 'pin_nav', 'false'
+        $.cookie 'pin_nav', 'false', { path: '/' }
         $('.page-with-sidebar')
           .removeClass('page-sidebar-pinned')
           .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
@@ -279,6 +279,6 @@
           .removeClass('header-pinned-nav')
           .toggleClass('header-collapsed header-expanded')
       else
-        $.cookie 'pin_nav', 'true'
+        $.cookie 'pin_nav', 'true', { path: '/' }
         $('.page-with-sidebar').addClass('page-sidebar-pinned')
         $('.navbar-fixed-top').addClass('header-pinned-nav')


#142 fix broken search caused by merge

#142 fix broken search caused by merge

--- app/assets/javascripts/main/paginator.coffee
+++ app/assets/javascripts/main/paginator.coffee
@@ -6,24 +6,24 @@
 ###

 class Paginator
-
+

   constructor: (@pageSelectionDiv, data=null) ->
-
+
     @rowsPerPage = 100
     @allElements = null
     @elementsToShow = null
-
+
     tableID = @pageSelectionDiv.data("paged-table-id")
     @tbody = $("#"+tableID).find("tbody")

     @extractTemplate()
-
+
     if data
       @handleData(data)
     else
       @retrieveData()
-
+
     searchboxElement = @pageSelectionDiv.find(".pagination-searchbox")
     @addSearchboxListener(searchboxElement)
     @addHashListener(searchboxElement)
@@ -39,7 +39,7 @@

   extractTemplate : ->
-
+
     templateSource = _.unescape(@tbody.html())
     # compile
     @template = _.template(templateSource)
@@ -49,25 +49,26 @@

   retrieveData : ->
-
+
     ajaxOptions =
       url : @pageSelectionDiv.data("url")
       dataType : "json"
       type : "get"

-    $.ajax(ajaxOptions).then(@handleData)
+    @dataRetrievalPromise = $.ajax(ajaxOptions)
+    @dataRetrievalPromise.then(@handleData)

   handleData: (responseData) =>
-
+
     @allElements = @elementsToShow = responseData.data

     pageCount = Math.ceil(@allElements.length / @rowsPerPage)
-
+
     pageSelectionHandler = (event, number) =>
       index = number - 1
       json = @getElementsForPage(index)
       @displayJSON(json)
-
+
     @pageSelectionDiv.bootpag(
       total: pageCount
       page: 1
@@ -89,13 +90,13 @@

   getElementsForPage : (index) ->
-
+
     start = @rowsPerPage * index
     return @elementsToShow.slice(start, start + @rowsPerPage)

   updatePageCount : ->
-
+
     @pageSelectionDiv.bootpag(
       total: Math.ceil(@elementsToShow.length / @rowsPerPage)
       page:  1
@@ -103,7 +104,7 @@

   addSearchboxListener : (searchboxElement) ->
-
+
     lastQuery = null
     currentQuery = null

@@ -127,16 +128,16 @@
               @elementsToShow.push task
         else
           @elementsToShow = @allElements
-
+
         @updatePageCount()
         @displayJSON(@getElementsForPage(0))
       )
-
+

   displayJSON : (jsonArray) ->
-
+
     htmlArray = []
-
+
     for element in jsonArray
       htmlArray.push(@generateHTML(element))

@@ -148,7 +149,7 @@
     contains = false

     $.each data, (key, value) =>
-
+
       if _.isObject(value) or _.isArray(value)
          contains = @JSONcontains value, query
          return !contains # if contains then break else continue
@@ -159,11 +160,11 @@
         if value.indexOf(query) > -1
           contains = true
           return false # break
-
+
     return contains

   generateHTML : (element) ->
-
+
     return @template({controllers : jsRoutes.controllers, element : element})


Better inspect for layer

Better inspect for layer

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -738,7 +738,7 @@
 	## EVENTS

 	addListener: (eventNames..., originalListener) =>
-
+
 		# To avoid an error in Framer Studio we return if no originalListener was given
 		if not originalListener
 			return
@@ -820,5 +820,5 @@
 			return Utils.round(value, 1)

 		if @name
-			return "<Layer id:#{@id} name:#{@name} (#{round(@x)},#{round(@y)}) #{round(@width)}x#{round(@height)}>"
-		return "<Layer id:#{@id} (#{round(@x)},#{round(@y)}) #{round(@width)}x#{round(@height)}>"
+			return "<#{@constructor.name} id:#{@id} name:#{@name} (#{round(@x)},#{round(@y)}) #{round(@width)}x#{round(@height)}>"
+		return "<#{@constructor.name} id:#{@id} (#{round(@x)},#{round(@y)}) #{round(@width)}x#{round(@height)}>"


Fixing other functions that were influenced by the ancestors change

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -442,7 +442,7 @@
 		importable: true
 		exportable: false
 		get: ->
-			return Utils.convertFrameToContext(@frame, @, true, false)
+			return Utils.boundingFrame(@)
 		set: (frame) ->
 			@frame = Utils.convertFrameFromContext(frame, @, true, false)

@@ -450,7 +450,7 @@
 		importable: true
 		exportable: false
 		get: ->
-			return Utils.convertFrameToContext(@frame, @, false, false)
+			return Utils.boundingFrame(@, false)
 		set: (frame) ->
 			@frame = Utils.convertFrameFromContext(frame, @, false, false)

@@ -490,9 +490,6 @@
 		@x = parseInt @x
 		@y = parseInt @y

-	@define "devicePixelRatio",
-		get: -> @context.devicePixelRatio
-
 	updateForDevicePixelRatioChange: =>
 		for cssProperty in ["width", "height", "webkitTransform", "boxShadow", "textShadow", "borderRadius", "border", "fontSize", "letterSpacing", "wordSpacing", "textIndent"]
 			@_element.style[cssProperty] = LayerStyle[cssProperty](@)
@@ -516,28 +513,40 @@
 		scale = 1
 		scale = @scale * @scaleX if self
 		for parent in @ancestors(context=true)
-			scale = scale * parent.scale * parent.scaleX
+			if parent.scale?
+				scale *= parent.scale
+			if parent.scaleX?
+				scale *= parent.scaleX
 		return scale

 	canvasScaleY: (self=true) ->
 		scale = 1
 		scale = @scale * @scaleY if self
 		for parent in @ancestors(context=true)
-			scale = scale * parent.scale * parent.scaleY
+			if parent.scale?
+				scale *= parent.scale
+			if parent.scaleY?
+				scale *= parent.scaleY
 		return scale

 	screenScaleX: (self=true) ->
 		scale = 1
 		scale = @scale * @scaleX if self
 		for parent in @ancestors(context=false)
-			scale = scale * parent.scale * parent.scaleX
+			if parent.scale?
+				scale *= parent.scale
+			if parent.scaleX?
+				scale *= parent.scaleX
 		return scale

 	screenScaleY: (self=true) ->
 		scale = 1
 		scale = @scale * @scaleY if self
 		for parent in @ancestors(context=false)
-			scale = scale * parent.scale * parent.scaleY
+			if parent.scale?
+				scale *= parent.scale
+			if parent.scaleY?
+				scale *= parent.scaleY
 		return scale

@@ -556,9 +565,10 @@
 		layers.reverse()

 		for parent in layers
-			factorX = if parent._parentOrContext() then parent._parentOrContext().screenScaleX() else 1
-			factorY = if parent._parentOrContext() then parent._parentOrContext().screenScaleY() else 1
-			layerScaledFrame = parent.scaledFrame()
+			p = parentOrContext(parent)
+			factorX = p?.screenScaleX?() ? 1
+			factorY = p?.screenScaleY?() ? 1
+			layerScaledFrame = parent.scaledFrame?() ? {x: 0, y: 0}
 			frame.x += layerScaledFrame.x * factorX
 			frame.y += layerScaledFrame.y * factorY


Resize map and chart on hash change

Resize map and chart on hash change

--- app/controllers/classify-steps/reveal.coffee
+++ app/controllers/classify-steps/reveal.coffee
@@ -97,8 +97,8 @@
         shared: true

     $(window).on 'hashchange', =>
-      # @map.invalidateSize()
-      # @chart.setSize @graphContainer.width(), @graphContainer.height()
+      @map.invalidateSize()
+      @chart.setSize @graphContainer.width(), @graphContainer.height()

   enter: ->
     super


Fix for links stopped working

Fix for links stopped working

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -411,8 +411,6 @@
 		RoomHistoryManager.clear(template?.data?._id)

 	'click .message': (e, template) ->
-		e.preventDefault()
-		e.stopPropagation()
 		if template.selectable.get()
 			document.selection?.empty() or window.getSelection?().removeAllRanges()
 			data = Blaze.getData(e.currentTarget)


:art: Tweak setting description

:art: Tweak setting description

--- lib/tablr.coffee
+++ lib/tablr.coffee
@@ -48,7 +48,7 @@
       type: 'string'
       default: 'vertically'
       enum: ['horizontally', 'vertically']
-      description: "If the clipboard content comes from a multiple selection copy in a text editor, each selection will be considered as part of the same column (vertically) or of the same row (horizontally)."
+      description: "If the clipboard content comes from a multiple selection copy in a text editor, each selection will be considered as part of the same column (`vertically`) or of the same row (`horizontally`)."

   activate: ({csvConfig}) ->
     CSVConfig ?= require './csv-config'


Added logic to get parameters from Box

Added logic to get parameters from Box

--- routes/templatesRoute.coffee
+++ routes/templatesRoute.coffee
@@ -8,11 +8,11 @@
     data: ->
         privateTemplates: Template.find({owner: Meteor.userId()}, {sort: {name: 1}})
     onBeforeAction: ->
-        console.log("this.params: " + JSON.stringify(this.params))
-        console.log("this.request: " + JSON.stringify(this.request))
-        console.log("this.request.body: " + JSON.stringify(this.request.body))
-        console.log("this.request.query: " + JSON.stringify(this.request.query))
-        console.log("this.req: " + JSON.stringify(this.req))
+        console.log("this.params: " + decodeURIComponent(JSON.stringify(this.params)))
+        console.log("this.request: " + decodeURIComponent(JSON.stringify(this.request)))
+        console.log("this.request.body: " + decodeURIComponent(JSON.stringify(this.request.body)))
+        console.log("this.request.query: " + decodeURIComponent(JSON.stringify(this.request.query)))
+        console.log("this.req: " + decodeURIComponent(JSON.stringify(this.req)))
         @next()


Social: convert follow/unfollow functions into addParticipants/removeParticipants function

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -12,42 +12,43 @@
   @set
     sharedMethods :
       static      :
-        byId              :
-          (signature Object, Function)
-        byName            :
-          (signature Object, Function)
-        fetchActivities   :
-          (signature Object, Function)
-        fetchActivityCount:
-          (signature Object, Function)
-        fetchChannels     :
-          (signature Object, Function)
-        fetchParticipants :
-          (signature Object, Function)
-        fetchPopularTopics:
-          (signature Object, Function)
-        fetchPopularPosts:
-          (signature Object, Function)
-        fetchPinnedMessages:
-          (signature Object, Function)
-        pinMessage    :
-          (signature Object, Function)
-        unpinMessage  :
-          (signature Object, Function)
-        follow:
-          (signature Object, Function)
-        unfollow:
+        byId                 :
+          (signature Object, Function)
+        byName               :
+          (signature Object, Function)
+        fetchActivities      :
+          (signature Object, Function)
+        fetchActivityCount   :
+          (signature Object, Function)
+        fetchChannels        :
+          (signature Object, Function)
+        fetchParticipants    :
+          (signature Object, Function)
+        listParticipants     :
+          (signature Object, Function)
+        addParticipants      :
+          (signature Object, Function)
+        removeParticipants   :
+          (signature Object, Function)
+        fetchPopularTopics   :
+          (signature Object, Function)
+        fetchPopularPosts    :
+          (signature Object, Function)
+        fetchPinnedMessages  :
+          (signature Object, Function)
+        pinMessage           :
+          (signature Object, Function)
+        unpinMessage         :
           (signature Object, Function)
         fetchFollowedChannels:
           (signature Object, Function)
-        searchTopics:
-          (signature Object, Function)
-
-        fetchProfileFeed:
-          (signature Object, Function)
-        updateLastSeenTime:
-          (signature Object, Function)
-        glancePinnedPost:
+        searchTopics         :
+          (signature Object, Function)
+        fetchProfileFeed     :
+          (signature Object, Function)
+        updateLastSeenTime   :
+          (signature Object, Function)
+        glancePinnedPost     :
           (signature Object, Function)

     schema             :
@@ -147,19 +148,21 @@
   # fetchFollowedChannels - lists followed channels(topics) of an account
   @fetchFollowedChannels = secureRequest fnName: 'fetchFollowedChannels'

-  # follow - endpoint for users to follow topics
-  @follow = secureRequest
-    fnName  : 'followTopic'
-    validate: ["channelId"]
-
-  # unfollow - endpoint for users to give up following a channel
-  @unfollow = secureRequest
-    fnName  : 'unfollowTopic'
-    validate: ["channelId"]
-
   # updateLastSeenTime - updates user's channel presence data
   @updateLastSeenTime = secureRequest
     fnName  : 'updateLastSeenTime'
+    validate: ["channelId"]
+
+  @listParticipants = secureRequest
+    fnName  : 'listParticipants'
+    validate: ["channelId"]
+
+  @addParticipants = secureRequest
+    fnName  : 'addParticipants'
+    validate: ["channelId"]
+
+  @removeParticipants = secureRequest
+    fnName  : 'removeParticipants'
     validate: ["channelId"]

   # glancePinnedPost - updates user's lastSeenDate for pinned posts


Fix expand icon always showing up in repeat

--- src/resources/forms/orbeon/newbuilder/resources/grid/action-icons.coffee
+++ src/resources/forms/orbeon/newbuilder/resources/grid/action-icons.coffee
@@ -20,7 +20,7 @@
 # Rules defining when certain triggers are relevant
 isNotEmpty = (gridTd) -> $(gridTd).children('.fr-grid-content').children().length > 0
 relevanceRules =
-    'fb-expand-trigger':            (gridTd) -> gridTd.rowSpan <= $(gridTd).parent().nextAll('tr').length
+    'fb-expand-trigger':            (gridTd) -> gridTd.rowSpan <= $(gridTd).parent().nextAll('tr:not(.xforms-repeat-delimiter):not(.xforms-repeat-template):not(.xforms-repeat-begin-end):not(.xforms-group-begin-end)').length
     'fb-shrink-trigger':            (gridTd) -> gridTd.rowSpan >= 2
     'fb-delete-trigger':            isNotEmpty
     'fb-edit-details-trigger':      isNotEmpty


Remove exemplar/project urls from course schema (#558)

These have been moved to level schema

--- app/schemas/models/course.schema.ozar.coffee
+++ app/schemas/models/course.schema.ozar.coffee
@@ -37,10 +37,6 @@
         totalTimeRange: { type: 'string', title: 'Total class time (range)', description: 'Relevant for curriculum guides hover tooltip'}
       }}
       lessonSlidesUrl: c.url { title: 'Lesson Slides URL' }
-      # TODO remove these after changing the curr guides and projects page to read from capstone level
-      exemplarProjectUrl: c.url { title: 'Exemplar Project URL', description: 'Only relevant for capstone module' }
-      exemplarCodeUrl: c.url { title: 'Exemplar Code URL', description: 'Only relevant for capstone module' }
-      projectRubricUrl: c.url { title: 'Project Rubric URL', description: 'Only relevant for capstone module' }
     }} # TODO move module name from utils.coffee to schema
   }


set window icon

set window icon

--- app.coffee
+++ app.coffee
@@ -1,4 +1,4 @@
-{app, BrowserWindow, ipcMain, nativeImage, Tray} = require 'electron'
+{app, BrowserWindow, ipcMain, Tray} = require 'electron'
 path = require 'path-extra'
 fs = require 'fs-extra'

@@ -18,6 +18,8 @@
 update = require './lib/update'
 shortcut = require './lib/shortcut'
 {log, warn, error} = require './lib/utils'
+
+poiIconPath = path.join ROOT, 'assets', 'icons', 'poi.ico'

 # Add shortcut to start menu when os is windows
 app.setAppUserModelId 'org.poi.poi'
@@ -73,23 +75,17 @@
     y: y
     width: width
     height: height
+    title: 'poi'
+    icon: poiIconPath
     resizable: config.get 'poi.content.resizeable', true
     alwaysOnTop: config.get 'poi.content.alwaysOnTop', false
     titleBarStyle: 'hidden'
     webPreferences:
-      webSecurity: false
       plugins: true
       enableLargerThanScreen: true
   # Default menu
   mainWindow.reloadArea = 'kan-game webview'
-  mainWindow.setResizable(config.get 'poi.content.resizeable', true)
-  if process.platform == 'darwin'
-    if /electron$/i.test process.argv[0]
-      icon = nativeImage.createFromPath("#{ROOT}/assets/icons/poi.png")
-      app.dock?.setIcon? icon
-  else
-    mainWindow.setMenu null
-
+  mainWindow.setMenu null if process.platform isnt 'darwin'
   mainWindow.loadURL "file://#{__dirname}/index.html"
   if config.get 'poi.window.isMaximized', false
     mainWindow.maximize()
@@ -108,7 +104,7 @@

   # Tray icon
   if process.platform == 'win32'
-    global.appIcon = appIcon = new Tray(path.join(ROOT, 'assets', 'icons', 'poi.ico'))
+    global.appIcon = appIcon = new Tray(poiIconPath)
     appIcon.on 'click', ->
       win = mainWindow
       if win.isMinimized() then win.restore() else win.show()


Say branding one more time.

Say branding one more time.

--- app.coffee
+++ app.coffee
@@ -62,7 +62,7 @@

   # Boot HTTP server
   http.createServer(app).listen app.get('port'), () ->
-    console.log('Express server listening on port ' + app.get('port'))
+    console.log('GGA-API is listening on port ' + app.get('port'))

   # Boot up job processing system.
   requireFu(__dirname + '/jobs')(jobs, db)


Enable pretty HTML on development

Enable pretty HTML on development

--- app.coffee
+++ app.coffee
@@ -11,6 +11,7 @@
   @locals
     url: process.env.URL or "http://localhost:#{@settings.port}/"
     pubkey_address: process.env.PUBKEY_ADDRESS
+    pretty: @settings.env is 'development'

   @db = mongoose.connect process.env.MONGO_URI or 'mongodb://localhost/'
   @models = require('./models')(@db)


redirect if fail

redirect if fail

--- app.coffee
+++ app.coffee
@@ -22,6 +22,7 @@
   .dial process.env.FORWARD,
     timeout: 8
     action: '/ringonce'
+  .redirect '/gather'

   send_xml res, twiml


new design for password rest confirmation page

--- app.coffee
+++ app.coffee
@@ -268,12 +268,40 @@
       User.checkResetToken(token)
         .then ->
           res.send """
-
Now you can reset your password!

-            <form method="post" action="#">
-              <input name="password" type="password" placeholder="Password">
-              <input name="passwordConfirmation" type="password" placeholder="Confirmation">
-              <button>Reset Password</button>
-            </form>
+<!DOCTYPE html>
+
+<meta charset="utf-8">
+<title>Reset your password</title>
+<link href='http://fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet'>
+
+
Now you can reset your password!

+<form method="post" action="#">
+  <input name="password" type="password" placeholder="Password">
+  <input name="passwordConfirmation" type="password" placeholder="Confirmation">

+  <button>Reset Password</button>
+</form>
+
           """
         .fail (err) ->
           res.send """


WindowController: updated a warning

WindowController: updated a warning

--- src/core/windowcontroller.coffee
+++ src/core/windowcontroller.coffee
@@ -167,7 +167,7 @@
   revertKeyView:(view)->

     unless view
-      warn "you must pass the view as a param, which doesn't want to be keyview anymore!"
+      warn 'view, which shouldn\'t be the keyview anymore, must be passed as a parameter!'
       return

     if view is @keyView and @keyView isnt @oldKeyView


fix bugs

fix bugs

--- mock.coffee
+++ mock.coffee
@@ -25,6 +25,7 @@
     studentproperties: trequire("#{data_dir}/studentproperties")
     teachers: trequire("#{data_dir}/teachers")
     teacherproperties: trequire("#{data_dir}/teacherproperties")
+  console.log "loaded #{clever.db[key].length} #{key}" for key, val of clever.db

   sandbox = sinon.sandbox.create()

@@ -43,6 +44,11 @@
     return s.stream()

   sandbox.stub clever.Resource.prototype, 'properties', (obj, cb) ->
+    if arguments.length is 1
+      cb = obj
+      obj = undefined
+    else if arguments.length isnt 2
+      throw new Error("expected 1 or 2 arguments to properties")
     resource = @constructor.name.toLowerCase() + 's'
     resource_singular = _(resource).rtrim('s')
     id = @_properties.id
@@ -51,9 +57,7 @@
     if not prop_obj
       prop_obj = _.object [[resource_singular, id], ['data', {}]]
       clever.db["#{resource_singular}properties"].push prop_obj
-    if _(obj).isFunction()
-      cb = obj
-    else
+    if obj
       dotty.put prop_obj.data, k, v for k, v of obj
     return cb null, prop_obj.data


Call load on package

Call load on package

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -150,6 +150,7 @@
           pack = new ThemePackage(packagePath, metadata)
         else
           pack = new Package(packagePath, metadata)
+        pack.load()
         @loadedPackages[pack.name] = pack
         pack
       catch error


Filters out references to 'this'

Filters out references to 'this'

--- platform/tools/convert_from_directory.coffee
+++ platform/tools/convert_from_directory.coffee
@@ -20,7 +20,7 @@
 # FUNCTIONS
 toJson = (obj) -> JSON.stringify(obj, null, 2)

-parsedToObj = (parsed) ->
+parsedToObj = (parsed, codeTransformer) ->
   switch parsed.type
     when "Literal"
       return parsed.value
@@ -28,11 +28,11 @@
       obj = {}
       for property in parsed.properties
         propertyName = property.key.name || property.key.value
-        obj[propertyName] = parsedToObj(property.value)
+        obj[propertyName] = parsedToObj(property.value, codeTransformer)
       return obj
     when "FunctionExpression"
       # Don't include the upper-level BlockStatement
-      return (escodegen.generate(body, CODE_GENERATION_OPTIONS) for body in parsed.body.body).join("\n")
+      return (codeTransformer(escodegen.generate(body, CODE_GENERATION_OPTIONS)) for body in parsed.body.body).join("\n")
     else
       util.error("How do I handle parsed type #{parsed.type}?")
       process.exit(2)
@@ -53,7 +53,7 @@

 # This object will be written out as JSON at the end
 outputObj =
-  version: OUTPUT_VERSION
+  fileVersion: OUTPUT_VERSION
   model: {}
   layout: {}
   actions: {}
@@ -62,8 +62,8 @@

 # Actions are a JS file that needs to be parsed
 parsedActions = esprima.parse(fs.readFileSync(path.join(inputDir, "actions.js"), { encoding: "utf8" }))
-# TODO: remove references to "this"
-outputObj.actions = parsedToObj(parsedActions.body[0].expression)
+# Remove references to "this"
+outputObj.actions = parsedToObj(parsedActions.body[0].expression, (code) -> code.replace(/this./g, ""))

 # Copy over layout JSON
 outputObj.layout = JSON.parse(fs.readFileSync(path.join(inputDir, "layout.json"), { encoding: "utf8" }))
@@ -73,8 +73,8 @@

 # Tools are a JS file that needs to be parsed
 parsedTools = esprima.parse(fs.readFileSync(path.join(inputDir, "tools.js"), { encoding: "utf8" }))
-# TODO: remove references to "this"
-outputObj.tools = parsedToObj(parsedTools.body[0].expression)
+# Replace "this" references with tools
+outputObj.tools = parsedToObj(parsedTools.body[0].expression, (code) -> code.replace(/this./g, "tools."))

 # Create data-URI encoded versions of all assets
 assetMap = JSON.parse(fs.readFileSync(path.join(inputDir, "assets.json"), { encoding: "utf8" }))


Track pageviews and views of plans/pricing in MP.

--- src/main/webapp/coffeescripts-hidden/landing.coffee
+++ src/main/webapp/coffeescripts-hidden/landing.coffee
@@ -16,6 +16,8 @@
   return [];

 $(document).ready ->
+  mixpanel.track("Viewed Landing Page")
+
   $('span[rel=tipsy]').tipsy({fade: true})

   window.liftAjax.lift_actualAjaxCall = (data, version, onSuccess, onFailure) ->
@@ -43,6 +45,16 @@

     $(scrollableElem).animate({scrollTop: targetTop}, 600)

+  plansAndPricingHasBeenViewed = false
+  $(window).on 'scroll', ->
+    unless plansAndPricingHasBeenViewed
+      heightOfElemsAbovePricing = $(".im-on-a-boat").outerHeight() + $(".how-it-works").outerHeight() + $(".features").outerHeight()
+      currentBottomScrollIndex = $(window).scrollTop() + $(window).innerHeight()
+
+      if (currentBottomScrollIndex >= heightOfElemsAbovePricing)
+        plansAndPricingHasBeenViewed = true
+        mixpanel.track("Viewed Plans & Pricing")
+
   $(".go-to-manager").on 'click', ->
     document.location = "/manager/dashboard"


Verified that publication autorun is rerun only when one of selected fields is changed.

--- client/publication.coffee
+++ client/publication.coffee
@@ -137,7 +137,6 @@
       console.error "Error rendering page #{ page.page.pageNumber }", args...

   # Fields needed when showing (rendering) the publication: those which are needed for PDF URL to be available and slug
-  # TODO: Verify that it works after support for filtering fields on the client will be released in Meteor
   @SHOW_FIELDS: ->
     fields:
       foreignId: 1
@@ -150,7 +149,6 @@
     Meteor.subscribe 'annotations-by-publication', Session.get 'currentPublicationId'

 Deps.autorun ->
-  # TODO: Limit only to fields necessary to display publication so that it is not rerun on field changes
   publication = Publications.findOne Session.get('currentPublicationId'), Publication.SHOW_FIELDS()

   return unless publication


Annotation resize logic changed

Annotation resize logic changed

Fixes #372

--- client/publication.coffee
+++ client/publication.coffee
@@ -133,15 +133,10 @@

             # TODO: We currently change this based on the width of the last page, but pages might not be of same width, what can we do then?

-            # We store current display wrapper width because we will later on
-            # reposition annotations for the ammount display wrapper width changes
-            displayWidth = @_$displayWrapper.width()
             # We remove all added CSS in publication destroy
             $('footer.publication').add(@_$displayWrapper).css
               width: viewport.width
-            # We reposition annotations if display wrapper width changed
-            $('.annotations-control, .annotations-list').css
-              left: "+=#{ viewport.width - displayWidth }"
+            resizeAnnotationsControl()

             $('.annotations-list .invite .body, .annotations-list .local .body').balanceText()

@@ -788,6 +783,16 @@

     return # Make sure CoffeeScript does not return anything

+resizeAnnotationsControl = ->
+  padding = parseInt($('.annotations-control').css('right'))
+  displayWrapper = $('.display-wrapper')
+  left = displayWrapper.offset().left + displayWrapper.outerWidth() + padding
+  $('.annotations-control, .annotations-list').css
+    left: left
+
+Template.annotationsControl.rendered = ->
+  resizeAnnotationsControl()
+
 Template.publicationAnnotations.annotations = ->
   viewport = currentViewport()
   highlights = currentHighlights()


Correctly register dependencies to remove notification correctly.

--- client/publication.coffee
+++ client/publication.coffee
@@ -606,9 +606,8 @@
   LocalAnnotation.documents.insert annotation

 Deps.autorun ->
-  publicationSubscribing() # To register dependency
-  return unless publicationHandle?.ready()
-
+  # We first register a dependency on the publication, so that we correctly
+  # remove the notification when we go away from the publication page
   publication = Publication.documents.findOne Session.get('currentPublicationId'),
     fields:
       _id: 1
@@ -620,6 +619,10 @@
       'sticky.notProcessedPublicationId':
         $exists: true
     return
+
+  # We proceed only if we are fully subscribed to the publication
+  publicationSubscribing() # To register dependency
+  return unless publicationHandle?.ready()

   # To remove any existing notification for previous publication
   Notify.documents.remove


remove debugging

remove debugging

--- client/tx/lib/util.coffee
+++ client/tx/lib/util.coffee
@@ -6,7 +6,6 @@
 format_locals = (data) ->
   data[k] = bytesToHex data[k] for k in ['bob', 'alice', 'trent'] when data[k]?
   data.bob_priv = get_address data.bob_priv, ADDR_PRIV if data.bob_priv?
-  console.log 'got data', data
   data.terms = UTF8.bytesToString data.terms if data.terms?
   data.proof = bytesToBase64 data.proof if data.proof?
   data


atom keymap for linux

atom keymap for linux

--- atom.symlink/keymap.cson
+++ atom.symlink/keymap.cson
@@ -1,5 +1,6 @@
 '.editor:not(.mini)':
-  'ctrl-cmd-i': 'editor:auto-indent'
+  'ctrl-cmd-i': 'editor:auto-indent',
+  'ctrl-alt-i': 'editor:auto-indent'

 'atom-workspace atom-text-editor:not([mini])':
   'cmd-ctrl-shift-up': 'editor:add-selection-above',


1px slack to determine whether we're at bottom to make up for firefox rounding

--- src/angular-scrollable-feed.coffee
+++ src/angular-scrollable-feed.coffee
@@ -44,7 +44,7 @@
     $scope.unlock = ->
       if $scope.autoScroll
         $scope.locked = false
-        $scope.position = $scope.bottom - $scope.visibleHeight
+        $scope.position = $scope.bottom + $scope.visibleHeight

     $scope.hideScrollBar = ->
       if $scope.autoHide then $scope.hidden = true
@@ -53,7 +53,7 @@
       if $scope.visibleHeight < $scope.bottom then $scope.hidden = false

     $scope.atBottom = () ->
-      return $scope.position + $scope.visibleHeight >= $scope.bottom
+      return $scope.position + $scope.visibleHeight >= $scope.bottom - 1

   restrict: 'A'
   scope:


use absolute path

use absolute path

--- app.coffee
+++ app.coffee
@@ -9,7 +9,7 @@
 	else
 		res.send 'invalid'

-app.use(express.static('wwwfiles'))
+app.use(express.static('/home/hyz/iportal/wwwfiles'))

 server = app.listen 8000, ->
 	console.log "Listening on #{server.address().address}:#{server.address().port}"


fix the auto playback start (master)

fix the auto playback start (master)

--- app.coffee
+++ app.coffee
@@ -23,7 +23,7 @@
   youtube_url      = "http://gdata.youtube.com/feeds/api/videos?q=<%= code %>&format=5&max-results=1&v=2&alt=jsonc"

   YTPlayer = ''
-
+  STARTED_PLAYBACK = false;

   # this is implicitly returned by coffee-script.
   self = {
@@ -81,6 +81,7 @@
       $li.parent().find(".playing").addClass "disabled"
       $li.siblings().removeClass "playing"
       $li.addClass "playing"
+      STARTED_PLAYBACK = true;

     start_playback: ->
       start  = self.get_playing($b_list) + 1
@@ -132,7 +133,8 @@
           html = template tdata
           $ul.append html

-          self.start_playback() if num is 0
+
+          self.start_playback() if not STARTED_PLAYBACK

     fill_output: ($target, urls) ->
       $target.val urls.join "\n"


Arrange tokens etc.

Arrange tokens etc.

--- app.coffee
+++ app.coffee
@@ -40,18 +40,17 @@
     #dev = $blab.resource "dev"
     #token: dev.token
     #gistId: dev.gistId
+    gistId: "9a18fe6158f60f061ba8"
+    userName: "garyballantyne"

     constructor: ->

-        #github = new Github
-        #    token: @token
-        #    auth: "oauth"
+        github = new Github
+            #    token: @token
+            #    auth: "oauth"
+            username: @userName

-        github = new Github
-            username: "garyballantyne"
-
-        #@gist = github.getGist(@gistId)
-        @gist = github.getGist("9a18fe6158f60f061ba8")
+        @gist = github.getGist(@gistId)

         $("#widget-menu").menu select: (event, ui) ->
             switch ui.item[0].innerHTML


Remove manual updating

Remove manual updating

--- app.coffee
+++ app.coffee
@@ -74,10 +74,12 @@
 config.ip ?= '0.0.0.0'
 config.packageFolder ?= "#{__dirname}/packages/"
 config.packageFolder += '/' unless /\/$/.test config.packageFolder
-config.enableManualUpdate ?= on
 config.enableStatistics ?= on
 config.enableHash ?= on
 config.deterministic ?= off
+
+if config.enableManualUpdate?
+	warn 'config.enableManualUpdate is obsolete and ignored in this version'

 # initialize express
 app = do express
@@ -316,7 +318,7 @@

 					versions = versions.filter (versionFile) ->
 						if versionFile is 'latest'
-							debug "The latest symlink is obsolete now. The information are taken from the newest package automatically!"
+							warn "The latest symlink is obsolete now. The information are taken from the newest package automatically!"
 							false
 						else if (versionFile.substring 0, 1) is '.'
 							debug "Skipping dotfile #{packageFolder}/#{versionFile}"
@@ -563,12 +565,6 @@
 		return
 	res.redirect 301, "#{host}/#{req.params[0]}/#{versionNumber.toLowerCase().replace (new RegExp ' ', 'g'), '_'}"

-# manual update via {{packageServerHost}}/update
-if config.enableManualUpdate
-	app.get '/update', (req, res) ->
-		warn 'Manual update was requested'
-		readPackages -> res.redirect 303, config.basePath ? "#{req.protocol}://#{req.header 'host'}/"
-
 # throw 404 on any unknown route
 app.all '*', (req, res) -> res.status(404).send '404 Not Found'


Indentation.

Indentation.

--- app.coffee
+++ app.coffee
@@ -38,11 +38,11 @@

     # from dev.json
     dev = $blab.resource "dev"
-    #token: dev.token
-    #gistId: dev.gistId
-    username: "anonymous"
-    password:
-    auth: "basic"
+        #token: dev.token
+        #gistId: dev.gistId
+        username: "anonymous"
+        password:
+        auth: "basic"

     constructor: ->


fix a tricky bug, when accidentally passing a non-string

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -99,9 +99,9 @@
         sel = "using %s as the language selector for %s"
         return if _.isFunction(@t) and force isnt true
         assert _.isFunction sprintf = _.sprintf # format
-        retrieve = (s) => @translationMessages?[s] or s
+        rx = (src) => @translationMessages?[src] or src
         logger.info "installing the translation tookit"
-        this.t = (s, a...) => sprintf retrieve(s), a...
+        this.t = (s, a...) => sprintf "#{rx(s)}", a...
         @obtainTranslation 0, (language, translation) =>
             assert _.isString(language), unrecognized
             assert _.isObject(translation), unexpected


minor boolean logic correction...

minor boolean logic correction...

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -85,12 +85,12 @@
     # This implementation requests the necessary translation tables
     # off the server provider. Once is acknowledged, a client side
     # routines are setup and associated with the language and table.
-    setupTranslationTools: @awaiting "booted", ->
+    setupTranslationTools: @awaiting "booted", (force) ->
         unexpected = "received malformed translation"
         unrecognized = "unrecognized language received"
         noted = "loaded %s translation messages for %s"
         sel = "using %s as the language selector for %s"
-        return if _.isFunction(@t) and @obtainTranslation
+        return if _.isFunction(@t) and force isnt true
         assert _.isFunction sprintf = _.sprintf # format
         retrieve = (s) => @translationMessages?[s] or s
         logger.info "installing the translation tookit"


If payment is cancelled by user then status is optional

--- public/assets/coffee/order-id-payment.coffee
+++ public/assets/coffee/order-id-payment.coffee
@@ -55,7 +55,9 @@
           buyer_addr:   $info.data('address1')
           notice_url:   'https://test-share.theopencloset.net/webhooks/iamport'
         , (res) ->
+          payment_status = res.status
           unless res.success
+            payment_status = "cancelled"
             $.growl.error({ title: '결제실패', message: res.error_msg })

           $.ajax "/payments/#{data.id}",
@@ -67,7 +69,7 @@
               imp_uid: res.imp_uid
               merchant_uid: res.merchant_uid
               amount: res.paid_amount
-              status: res.status
+              status: payment_status
               pg_provider: res.pg_provider
               pay_method: res.pay_method
             success: (data, textStatus, jqXHR) ->


Removing mapmethod mapmodel from the coffeescript

--- wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee
+++ wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee
@@ -140,8 +140,6 @@
         $('input[name=num_perm]').val($('input[name=num_perm_rqtl_geno]').val())
         $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_rqtl]:checked').val())
         $('input[name=control_marker]').val($('input[name=control_rqtl_geno]').val())
-        $('input[name=mapmethod]').val($('input[name=mapmethod_rqtl_geno]').val())
-        $('input[name=mapmodel]').val($('input[name=mapmodel_rqtl_geno]').val())
         form_data = $('#trait_data_form').serialize()
         console.log("form_data is:", form_data)


fix: always trigger the switchChange event after a change event

--- src/coffee/bootstrap-switch.coffee
+++ src/coffee/bootstrap-switch.coffee
@@ -376,7 +376,8 @@
               .not(@$element)
               .prop("checked", false)
               .trigger "change.bootstrapSwitch", true
-            @$element.trigger "switchChange.bootstrapSwitch", [state]
+
+          @$element.trigger "switchChange.bootstrapSwitch", [state]

         "focus.bootstrapSwitch": (e) =>
           e.preventDefault()


fixed task type select

fixed task type select

--- app/assets/javascripts/admin/views/task/task_create_from_form_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_from_form_view.coffee
@@ -135,7 +135,7 @@
     taskTypeSelectionView = new SelectionView(
       collection: new TaskTypeCollection()
       childViewOptions :
-        modelValue: -> return "#{@model.get("name")}"
+        modelValue: -> return "#{@model.get("summary")}"
       data : "amIAnAdmin=true"
       name: "taskType"
     )


render editor on click instead of on load

--- app/assets/javascripts/states/monitoring/views/_check_table_row.js.coffee
+++ app/assets/javascripts/states/monitoring/views/_check_table_row.js.coffee
@@ -9,10 +9,7 @@
         v = @
         @check.destroy success: (model, response) ->
           v.parent.redraw()
+
       "click a.edit": ->
-      	@check_editor.show() # TODO load options here
-
-    post_render: ->
-    	super
-    	@check_editor ||= @partial(exports.CheckEditor, { check: @check, host: @host, hosts: @hosts })
-    	@
+        check_editor = @partial(exports.CheckEditor, { check: @check, host: @host, hosts: @hosts })
+        check_editor.show() # TODO load options here


sort out rendering reversed commands, item, goal

--- obot.coffee
+++ obot.coffee
@@ -35,23 +35,59 @@

   ctx.restore()

+  # goal
+  ctx.save()
+  ctx.lineWidth = 1.5
+  ctx.strokeStyle = 'white'
+  ctx.save()
+
+  ctx.translate(
+    (@initial_bot_location.xi+.5) * cell_size,
+    (@initial_bot_location.yi+.5) * cell_size)
+  ctx.beginPath()
+  ctx.arc(0, 0, cell_size*.4, 0, Math.PI)
+  ctx.stroke()
+  ctx.restore()
+
+  # item
+  @bot.has_item = true
+  if !@bot.has_item
+    ctx.translate( (@item_location.xi+.5)*cell_size,
+                   (@item_location.yi+.5)*cell_size)
+  else
+    ctx.translate( (@bot.showxi+.5)*cell_size,
+                   (@bot.showyi+.5)*cell_size)
+  ctx.beginPath()
+  ctx.arc(0,0,cell_size*.4,0,Math.PI,true)
+  ctx.stroke()
+  ctx.restore()
+
+
   # bot
   @bot.render(ctx)

   # commands
   ctx = prog_ctx
   for cmd, i in @commands
-    row = i // commands_per_row
-    col = i % commands_per_row
+    col = i
+
+    cur = (@show_current_command? and i == @show_current_command)
+    row = 0
     renderCommand(ctx, cmd, (x: col*command_size, y: row*command_size),
-      (@show_current_command? and i == @show_current_command))
-
-  # artificial current_command if we don't have an existing command selected
-  if @show_current_command != null && @show_current_command >= @commands.length
-    row = @commands.length // commands_per_row
-    col = @commands.length % commands_per_row
-    renderCommand(ctx, empty_cmd, (x: col*command_size, y: row*command_size),
-      true)
+      cur and (
+        @mode == levelMode.FULLPLAYING or
+        @mode == levelMode.TURNAROUND), false)
+
+    cur = (@show_current_command? and i == @show_current_command)
+    row = 1
+    renderCommand(ctx, cmd, (x: col*command_size, y: row*command_size),
+      cur and @mode == levelMode.REVPLAYING, true)
+
+  if @commands.length > 0
+    renderCommand(ctx, reverse_cmd,
+      (x: @commands.length*command_size, y: .5*command_size),
+      @show_current_command? and @commands.length == @show_current_command and
+      @mode == levelMode.REVPLAYING)

   return

@@ -129,9 +165,8 @@
   return

 # command graphics
-command_size = 38
+command_size = 32
 inner_command_size = 29
-commands_per_row = 8
 command_scrim_points = do ->
   ics = inner_command_size
   [
@@ -141,7 +176,7 @@
     (x: +ics/2, y: -ics/2)
   ]

-renderCommand = (ctx, what, where, current=false) ->
+renderCommand = (ctx, what, where, current, rev = false) ->
   cs = command_size
   ics = inner_command_size

@@ -159,7 +194,7 @@
     for pt in command_scrim_points
       ctx.fillRect(pt.x-ics*.1, pt.y-ics*.1, ics*.2, ics*.2)

-  what.render(ctx)
+  what.render(ctx, rev)
   ctx.restore()

   return
@@ -272,8 +307,10 @@
 noActionCommand = (level) ->
   return

-arrowCommandRender = (ctx) ->
-  renderArrow(ctx, @dir.theta, inner_command_size)
+arrowCommandRender = (ctx, rev) ->
+  ics = inner_command_size * if rev then -1 else 1
+
+  renderArrow(ctx, @dir.theta, ics)

 reverseCommandRender = (ctx) ->
   ics  = inner_command_size
@@ -309,22 +346,18 @@
   dir: DOWN
   action: moveCommand

-empty_cmd =
-  render: -> return
-  action: noActionCommand
+reverse_cmd =
+  render: reverseCommandRender

 # buttons

-max_commands = 8
+max_commands = 9

 addCommandButtonAction = (level) ->
   if level.commands.length >= max_commands
     return false

   switch level.mode
-    when levelMode.STOPPED
-      level.commands[level.current_command] = @cmd
-      level.advanceCurrentCommand()
     when levelMode.PLAYING, levelMode.FULLPLAYING
       level.commands[level.commands.length] = @cmd
     when levelMode.REVPLAYING, levelMode.TURNAROUND
@@ -387,7 +420,6 @@
 # level

 levelMode =
-  STOPPED: 1
   PLAYING: 2
   FULLPLAYING: 3
   TURNAROUND: 4
@@ -426,19 +458,17 @@
   (x1-x0)*t + x0

 animateLevel = (t) ->
-  console.log("mode = " + @mode + " animMode = " + @animation_mode)
-
-  if @mode == levelMode.STOPPED or @animation_mode == levelAnimMode.READY
-    @show_current_command = @current_command
+  #console.log("mode = " + @mode + " animMode = " + @animation_mode)
+
+  if @animation_mode == levelAnimMode.READY
+    if @mode == levelMode.PLAYING
+      @show_current_command = null
+    else
+      @show_current_command = @current_command
     @bot.showxi = @bot.xi
     @bot.showyi = @bot.yi
     @bot.showdir = @bot.dir

-  if @mode == levelMode.STOPPED
-    return
-
-  if @animation_mode == levelAnimMode.READY
-    @show_current_command = @current_command
     next_move_control =
       start: (t: t, x: @bot.xi, y: @bot.yi, dir: @bot.dir)
       duration: 1/bot_speed
@@ -475,18 +505,19 @@
     if @mode == levelMode.FULLPLAYING &&
        @current_command == @commands.length
       @mode = levelMode.TURNAROUND
-    true
+    return true
   else if @mode == levelMode.TURNAROUND
     @mode = levelMode.REVPLAYING
     @advanceCurrentCommand()
     return true
   else
-    false
-
-
-setupLevel = (initial_bot_location) ->
+    return false
+
+
+setupLevel = (initial_bot_location, item_location) ->
   o =
     initial_bot_location: initial_bot_location
+    item_location: item_location
     bot: (render: renderBot)
     commands: [ ]
     current_command: 0
@@ -517,7 +548,7 @@
   click: clickControlPanel

 control_panel = setupControlPanel()
-level_state = setupLevel((xi:0, yi:5, dir:0))
+level_state = setupLevel((xi:0, yi:5, dir:0), (xi:6, yi:5))

 stop = false


changed link to viewPractice

changed link to viewPractice

--- src/components/learning-guide.cjsx
+++ src/components/learning-guide.cjsx
@@ -39,7 +39,7 @@
         {section.title}

       <Router.Link
-      to='viewTask'
+      to='viewPractice'
       params={linkParams}
       className='btn btn-default progress-bar-button'>
         <BS.ProgressBar className={colorClass} now={sectionPercent} />
@@ -56,7 +56,7 @@
     sections = _.map(chapter.children, @renderSectionBars)
     chapterPercent = @_percent(chapter.current_level, 1)
     colorClass = @colorizeBar(chapterPercent)
-
+
     <BS.Col xl={3}>

@@ -66,7 +66,7 @@
           {chapter.chapter_section[0]}

{chapter.title}

         <Router.Link
-        to='viewTask'
+        to='viewPractice'
         params={linkParams}
         className='btn btn-default progress-bar-button'>
           <BS.ProgressBar className={colorClass} now={chapterPercent} />


re-order sections

re-order sections

--- bokehjs/demo/coffee/scatter.coffee
+++ bokehjs/demo/coffee/scatter.coffee
@@ -14,14 +14,6 @@
     color: ("rgb(#{ Math.floor(50+2*val[0]) }, #{ Math.floor(30+2*val[1]) }, 150)" for val in _.zip(x, y))
   }

-  options = {
-    title: "Scatter Demo"
-    plot_width: 600
-    plot_height: 600
-    x_range: [0, 100]
-    y_range: [0, 100]
-  }
-
   scatter = {
     type: 'Circle'
     source: "mydata"
@@ -32,6 +24,16 @@
     fill_alpha: 0.6
     line_color: null
   }
+
+  options = {
+    title: "Scatter Demo"
+    plot_width: 600
+    plot_height: 600
+    x_range: [0, 100]
+    y_range: [0, 100]
+  }
+
+

   xaxis = {
     type: "auto"


fix typo in message of github-notify.

fix typo in message of github-notify.

--- scripts/github-notify.coffee
+++ scripts/github-notify.coffee
@@ -40,10 +40,10 @@
     r.push(room)
     m[repo] = unique r
     robot.brain.set("gh-notify-repository-to-rooms", m)
-    msg.reply "#{repo} の Pull Request の通知を #{room} に送信します"
+    msg.reply "#{repo} の通知を #{room} に送信します"

   robot.respond /github\s+notify\s+show\s*$/, (msg) ->
-    msg.send "GitHub の Pull Request の通知設定一覧"
+    msg.send "GitHub の通知設定一覧"
     repos = (robot.brain.get("gh-notify-repository-to-rooms") || {})
     for repo, rooms of repos
       rooms ||= []


Add species to the herbarium workflow

Add species to the herbarium workflow

--- overrides.coffee
+++ overrides.coffee
@@ -57,6 +57,7 @@

       if page.workflow is 'herbarium'
         page.decisionTree.tasks.verify.setDefaults
+          species: metadata.species
           date: metadata.date
           locality: metadata.locality
           vc: metadata.vc


added dashboard to name-expander

added dashboard to name-expander

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -14,6 +14,12 @@
   "Jamie Winsor"
 ]

+dashboard_members = [
+  "Mat Schaffer",
+  "Aaron Feng",
+  "Trotter Cashion"
+]
+
 merlin_members = merlin_remote_members.concat merlin_lax_members

 module.exports = (robot) ->
@@ -25,3 +31,6 @@

   robot.hear /^merlin-remote( team| dudes| guys)*:(.+)/i, (msg) ->
     msg.send merlin_remote_members.join(", ") + ":" + msg.match[2]
+
+  robot.hear /^dashboard( team| dudes| guys)*:(.+)/i, (msg) ->
+    msg.send dashboard_members.join(", ") + ":" + msg.match[2]


Ensure newVisit set to false and turn off scroll listener

--- resources/assets/lib/react-turbolinks.coffee
+++ resources/assets/lib/react-turbolinks.coffee
@@ -29,7 +29,6 @@
     $(document).on 'turbolinks:before-cache', @onBeforeCache
     $(document).on 'turbolinks:before-visit', @onBeforeVisit
     $(document).on 'turbolinks:load', @onLoad
-    $(window).on 'scroll', @onWindowScroll

   allTargets: (callback) =>
@@ -76,6 +75,9 @@

   onLoad: =>
     @scrolled = false
+    $(window).off 'scroll', @onWindowScroll
+    $(window).on 'scroll', @onWindowScroll
+
     # Delayed to wait until cacheSnapshot finishes. The delay matches Turbolinks' defer.
     Timeout.set 1, =>
       @deleteLoadedMarker()
@@ -107,9 +109,11 @@

   scrollOnNewVisit: =>
-    return if !@newVisit || @scrolled
+    $(window).off 'scroll', @onWindowScroll
+    newVisit = @newVisit
+    @newVisit = false

-    @newVisit = false
+    return if !newVisit || @scrolled

     targetId = document.location.hash.substr(1)


Set .tigrc file type to "text.generic-config" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -58,6 +58,7 @@
     "^.shared_env.*": "source.shell"
     "^.spacemacs$": "source.lisp"
     ".*.sshconfig": "source.ssh-config"
+    "^.tigrc$": "text.generic-config"
     "^.vimrc.before$": "source.viml"
     "^.vimrc.after$": "source.viml"
     "^.wgetrc": "text.generic-config"


Pad the HUD leading / with a "hair space"

--- pages/hud.coffee
+++ pages/hud.coffee
@@ -58,7 +58,7 @@

   showFindMode: (data) ->
     hud = document.getElementById "hud"
-    hud.innerText = "/"
+    hud.innerText = "/\u200A" # \u200A is a "hair space", to leave enough space before the caret/first char.

     inputElement = document.createElement "span"
     inputElement.contentEditable = "plaintext-only"


problem

problem

--- src/enc.iced
+++ src/enc.iced
@@ -19,7 +19,7 @@
     header        : [ 0x1c94d7de, 1 ]  # The magic #, and also the version #
     pbkdf2_iters  : 1024               # Since we're using XOR, this is enough..
     salt_size     : 8                  # 8 bytes of salt is good enough!
-    hmac_key_size : 768/8              # The size of the key to use for HMAC (our choice)
+    hmac_key_size : 768/8              # The size of the key to split over the two HMACs.

 #========================================================================


Added CreditCard.same() function

Added CreditCard.same() function

--- app/assets/javascripts/sprangular/models/creditCard.coffee
+++ app/assets/javascripts/sprangular/models/creditCard.coffee
@@ -62,5 +62,8 @@
     token: @token
     cc_type: @type

+  same: (other) ->
+    @id == other.id
+
   _validateCardFormat: ->
     'invalid card number' unless Sprangular.Luhn.isValid(@number)


Renamed saveCoupon to savePromo in Checkout service

--- app/assets/javascripts/sprangular/services/checkout.coffee
+++ app/assets/javascripts/sprangular/services/checkout.coffee
@@ -1,12 +1,12 @@
 Sprangular.service "Checkout", ($http, _, Env, Account, Cart) ->

   checkout =
-    saveCoupon: ->
+    savePromo: ->
       order = Cart.current

       params =
         order:
-          coupon_code: order.couponCode
+          coupon_code: order.promoCode

       @put(params)


Cleanup

Cleanup

--- sync.coffee
+++ sync.coffee
@@ -1,12 +1,11 @@
 util = require 'util'
-_ = @_ or require 'underscore'
-Backbone = @Backbone or require 'backbone'
+_ = require 'underscore'
+Backbone = require 'backbone'
 inflection = require 'inflection'

 AjaxCursor = require './lib/ajax_cursor'
 Schema = require 'backbone-orm/lib/schema'
 Utils = require 'backbone-orm/lib/utils'
-inflection = require 'inflection'

 module.exports = class AjaxSync


'uncaughtException' now works only on node

--- stack.coffee
+++ stack.coffee
@@ -1,14 +1,16 @@
 'use strict'

 log = require 'neft-log'
+utils = require 'neft-utils'

 exports.currentScope = null
 exports.currentTest = null
 exports.messages = []
 exports.errors = []

-process.on 'uncaughtException', (err) ->
-	exports.fail err
+if utils.isNode
+	process.on 'uncaughtException', (err) ->
+		exports.fail err

 exports.fail = (err) ->
 	{errors, currentTest} = exports


make it possible to do delete expired on the database without throttling

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -691,21 +691,22 @@
     # delete every entry where expire is <= right now.  This saves disk space, etc.
     #
     # db._error_thresh=100000; db.delete_expired(count_only:false, repeat_until_done: true, cb:done())
-    #
+    # db._error_thresh=100000; db.delete_expired(count_only:false, limit:0, cb:done())
+
     delete_expired: (opts) =>
         opts = defaults opts,
             count_only        : true  # if true, only count the number of blobs that would be deleted
             limit             : 100   # only this many
-            throttle_s        : 5     # if repeat_until_done and limit set, waits this many secs until the next chunck is getting deleted
+            throttle_s        : 5     # if repeat_until_done and limit set, waits this many secs until the next chunk is deleted
             table             : undefined  # only this table
-            repeat_until_done : false # if true and limit set, keeps re-calling delete until nothing gets deleted -- useful to do deletes in many small chunks instead of one big one, to better tell when done, and stop when server load increases.
+            repeat_until_done : false  # if true and limit set, keeps re-calling delete until nothing gets deleted -- useful to do deletes in many small chunks instead of one big one, to better tell when done, and stop when server load increases.
             cb    : required
         dbg = @dbg("delete_expired(...)")
         dbg()
         f = (table, cb) =>
             dbg("table='#{table}'")
             query = @table(table).between(new Date(0),new Date(), index:'expire')
-            if opts.limit?
+            if opts.limit
                 query = query.limit(opts.limit)
             start = new Date()
             if opts.count_only


do not allow a large number of concurrent queries to the database

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -112,7 +112,8 @@
             all_hosts: false      # if true, finds all hosts based on querying the server then connects to them
             warning  : 30          # display warning and stop using connection if run takes this many seconds or more
             error    : 10*60       # kill any query that takes this long (and corresponding connection)
-            concurrent_warn : 500  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
+            concurrent_warn  : 150  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
+            concurrent_error : 200  # if nonzero, and this many queries at once, any query instantly fails!
             mod_warn : 2           # display MOD_WARN warning in log if any query modifies at least this many docs
             cache_expiry  : 15000  # expire cached queries after this many milliseconds (default: 15s)
             cache_size    : 250    # cache this many queries; use @...query.run({cache:true}, cb) to cache result for a few seconds
@@ -126,6 +127,7 @@
         @_error_thresh     = opts.error
         @_mod_warn         = opts.mod_warn
         @_concurrent_warn  = opts.concurrent_warn
+        @_concurrent_error = opts.concurrent_error
         @_all_hosts        = opts.all_hosts
         @_stats_cached     = undefined
         @_user_query_stats = new UserQueryStats(@dbg("user_query_stats"))
@@ -309,6 +311,16 @@
                         that._concurrent_queries ?= 0
                         that._concurrent_queries += 1

+                        winston.debug("[#{that._concurrent_queries} concurrent]  rethink: query -- '#{query_string}'")
+                        if that._concurrent_queries > that._concurrent_warn
+                            winston.debug("rethink: *** concurrent_warn *** CONCURRENT WARN THRESHOLD EXCEEDED!")
+
+                        if that._concurrent_error and that._concurrent_queries > that._concurrent_error
+                            winston.debug("rethink: *** concurrent_error *** CONCURRENT ERROR THRESHOLD #{that._concurrent_error} EXCEEDED -- FAILING QUERY")
+                            that._concurrent_queries -= 1
+                            cb("concurrent_error")
+                            return
+
                         # choose a random connection
                         id = misc.random_choice(misc.keys(that._conn))
                         conn = that._conn[id]
@@ -333,9 +345,6 @@
                         if that._error_thresh
                             error_timer   = setTimeout(error_too_long,   that._error_thresh*1000)

-                        winston.debug("[#{that._concurrent_queries} concurrent]  rethink: query -- '#{query_string}'")
-                        if that._concurrent_queries > that._concurrent_warn
-                            winston.debug("rethink: *** concurrent_warn *** CONCURRENT WARN THRESHOLD EXCEEDED!")
                         g = (err, x) ->
                             report_time = ->
                                 that._concurrent_queries -= 1


changes --> options

changes --> options

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -1838,8 +1838,8 @@
             # Can do via http POST request, rather than websocket messages
             data =
                 query   : misc.to_json(opts.query)
-                changes : if opts.options then misc.to_json(opts.options)
-            console.log 'doing post'
+                options : if opts.options then misc.to_json(opts.options)
+            console.log 'doing post', data
             jqXHR = $.post("#{window?.app_base_url ? ''}/user_query", data)  # todo change to use $.ajax and timeout... -- or maybe define by backend.
             jqXHR.fail ->
                 opts.cb("failed")


Updates to filter output

Updates to filter output

--- node/fabricate.coffee
+++ node/fabricate.coffee
@@ -759,90 +759,98 @@
       updated_at: "2014-02-24T16:34:25Z"

     when 'artist_filtered_search_suggest'
-      total: 65
+      total: 79
       related_gene: {}
       medium:
-        design: 1
-        drawing: 10
-        installation: 1
-        painting: 7
-        prints: 8
-        sculpture: 23
-        "work-on-paper": 41
+        design:
+          name: "Design"
+          count: 2
+        drawing:
+          name: "Drawing"
+          count: 10
+        painting:
+          name: "Painting"
+          count: 7
+        prints:
+          name: "Prints"
+          count: 8
+        sculpture:
+          name: "Sculpture"
+          count: 32
+        "work-on-paper":
+          name: "Work On Paper"
+          count: 44
       dimension:
-        96: 63
+        96:
+          name: "96"
+          count: 76
       gallery:
-        "armand-bartos-fine-art": 2
-        "artware-editions": 1
-        "barbara-krakow-gallery": 4
-        "carolina-nitsch-contemporary-art": 15
-        "casterline-goodman-gallery": 1
-        caviar20: 1
-        "cheim-and-read": 21
-        "galerie-le-minotaure": 1
-        "galerie-zlotowski": 1
-        "lora-reynolds-gallery": 1
-        mixografia: 1
-        "peter-blum-gallery": 4
-        "robert-miller-gallery": 2
-        "senior-and-shopmaker-gallery": 3
-        "susan-sheehan-gallery": 1
-        "xavier-hufkens": 3
+        "barbara-krakow-gallery":
+          name: "Barbara Krakow Gallery"
+          count: 3
+        "carolina-nitsch-contemporary-art":
+          name: "Carolina Nitsch Contemporary Art"
+          count: 17
+        caviar20:
+          name: "Caviar20"
+          count: 2
+        "cheim-and-read":
+          name: "Cheim & Read"
+          count: 28
+        "peter-blum-gallery":
+          name: "Peter Blum Gallery"
+          count: 4
+        "robert-miller-gallery":
+          name: "Robert Miller Gallery"
+          count: 2
+        "senior-and-shopmaker-gallery":
+          name: "Senior & Shopmaker Gallery"
+          count: 3
+        "xavier-hufkens":
+          name: "Xavier Hufkens"
+          count: 3
       institution:
-        "miniature-museum-ria-and-lex-daniels": 1
-        "museum-of-arts-and-design": 1
-        "serpentine-gallery": 1
+        "miniature-museum-ria-and-lex-daniels":
+          name: "Miniature Museum Ria and Lex Daniels"
+          count: 1
+        "museum-of-arts-and-design":
+          name: "Museum of Arts and Design"
+          count: 1
+        "serpentine-gallery":
+          name: "Serpentine Gallery"
+          count: 1
+        "whitechapel-gallery":
+          name: "Whitechapel Gallery"
+          count: 1
       period:
-        1940: 4
-        1960: 8
-        1970: 1
-        1980: 3
-        1990: 16
-        2000: 27
-        2010: 3
+        1940:
+          name: "1940s"
+          count: 5
+        1950:
+          name: "1950s"
+          count: 1
+        1960:
+          name: "1960s"
+          count: 9
+        1970:
+          name: "1970s"
+          count: 1
+        1980:
+          name: "1980s"
+          count: 3
+        1990:
+          name: "1990s"
+          count: 22
+        2000:
+          name: "2000s"
+          count: 32
+        2010:
+          name: "2010s"
+          count: 3
       sale_status: {}
       price_range:
-        "-1:1000000000000": 58
-
-    when 'artist_filtered_search_nested_suggest'
-      total: 65
-      related_gene: {}
-      medium:
-        design: 1
-        drawing: 10
-        installation: 1
-        painting: 7
-        prints: 8
-        sculpture: 23
-        "work-on-paper": 41
-      dimension:
-        96: 63
-      gallery:
-        "armand-bartos-fine-art":
-          'name': 'Armand Bartos Fine Art'
-          'count': 5
-        "artware-editions":
-          'name': 'Artware Editions'
-          'count': 4
-        "barbara-krakow-gallery":
-          'name': 'Barbara Krakow Gallery'
-          'count': 3
-        "carolina-nitsch-contemporary-art":
-          'name': 'Carolina Nitsch Contemporary Art'
-          'count': 2
-        "casterline-goodman-gallery":
-          'name': 'CASTERLINE GOODMAN GALLERY'
-          'count': 1
-      institution:
-        "museum-of-arts-and-design":
-          'name': 'Museum of Arts and Design'
-          'count': 1
-        "serpentine-gallery":
-          'name': 'Serpentine Gallery'
-          'count': 2
-      period: {}
-      sale_status: {}
-      price_range:
-        "-1:1000000000000": 58
+        "-1:1000000000000":
+          name: "-1:1000000000000"
+          count: 74

   , extObj


Snipers take up 2 hands to use

Snipers take up 2 hands to use

--- src/definitions/items.coffee
+++ src/definitions/items.coffee
@@ -196,6 +196,12 @@
 		log.info "Current ammo after reload: #{util.inspect @ammo} (#{@ammo.length})"
 		yes

+	getEquipSlotUse: (slot, creature) ->
+		if @gunType is 'sniper' and slot is 'hand'
+			return 2
+
+		super
+
 	fireType: ->
 		switch @gunType
 			when 'handgun', 'sniper' then 'line'


Add pages task

Add pages task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -9,3 +9,5 @@
   grunt.registerTask 'test', ['jshint']

   grunt.registerTask 'doc', ['groc']
+
+  grunt.registerTask 'pages', ['metalsmith', 'doc']


Added injection test generation

Added injection test generation

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -59,6 +59,13 @@
         variables:
           title: 'Environment'
           script: '../all/environment.js'
+      injection:
+        src: 'templates/browsertest.html'
+        dest: 'test/browser/injection.html'
+        engine: 'handlebars'
+        variables:
+          title: 'Environment'
+          script: '../all/injection.js'

   require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)


ignore clicks on the marking surface

ignore clicks on the marking surface

--- app/assets/javascripts/components/image-subject-viewer-transcribe.cjsx
+++ app/assets/javascripts/components/image-subject-viewer-transcribe.cjsx
@@ -162,6 +162,7 @@
     @state.classification = new Classification @state.subject

   handleInitStart: (e) ->
+    return
     console.log 'handleInitStart()'

     {horizontal, vertical} = @getScale()


Escape quote mark in JS

Escape quote mark in JS


make default password rules very permisive

--- services/web/config/settings.defaults.coffee
+++ services/web/config/settings.defaults.coffee
@@ -170,11 +170,11 @@
 	# -----------
 	# These restrict the passwords users can use when registering
 	# opts are from http://antelle.github.io/passfield
-	passwordStrengthOptions:
-		pattern: "aA$3"
-		length:
-			min: 8
-			max: 50
+	# passwordStrengthOptions:
+	# 	pattern: "aA$3"
+	# 	length:
+	# 		min: 8
+	# 		max: 50

 	# Email support
 	# -------------


refactoring simplifyPropertyClaims

refactoring simplifyPropertyClaims

--- src/helpers/simplify_claims.coffee
+++ src/helpers/simplify_claims.coffee
@@ -9,13 +9,11 @@

 # expects the 'claims' array of a particular property
 simplifyPropertyClaims = (propClaims)->
-  simplifiedClaim = []
-  for claim in propClaims
-    simpifiedStatement = simplifyClaim claim
-    # filter-out null values
-    if simpifiedStatement? then simplifiedClaim.push simpifiedStatement
+  propClaims
+  .map simplifyClaim
+  .filter nonNull

-  return simplifiedClaim
+nonNull = (obj)-> obj?

 # expects a single claim object
 simplifyClaim = (claim)->


Fix log message in krb5 server

Fix log message in krb5 server

--- actions/krb5_server.coffee
+++ actions/krb5_server.coffee
@@ -375,7 +375,7 @@
   createConfigPrincipals = ->
     each(principals)
     .on 'item', (principal, next) ->
-      ctx.log "Create principal host/#{ctx.config.host}@#{realm}"
+      ctx.log "Create principal {principal}"
       options =
         ssh: ctx.ssh
         log: ctx.log


 Merge branch 'master.mixpanel' into 'master'

Master.Mixpanel

Sinan, we're having a problem with mixpanel missing entries, so I'm trying to debug it by forcing it to log directly or throw an error if `mixpanel` isn't defined.

--- client/Intro/views/registerinlineform.coffee
+++ client/Intro/views/registerinlineform.coffee
@@ -135,6 +135,12 @@
       else
         KD.mixpanel.alias account.profile.nickname
         KD.mixpanel "Signup, success"
+
+        try
+          mixpanel.track "Alternate Signup, success"
+        catch
+          KD.logToExternal "mixpanel doesn't exist"
+
         _gaq.push ['_trackEvent', 'Sign-up']

         $.cookie 'newRegister', yes


whitespace

whitespace

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -25,6 +25,7 @@
       video : "Viewer"
       image : "Viewer"
       sound : "Viewer"
+
     @on 'AppManagerWantsToShowAnApp', @bound "setFrontApp"

     # temp fix, until router logic is complete


fix paste-manager scroll restoration

fix paste-manager scroll restoration

--- src/modules/paste-manager.coffee
+++ src/modules/paste-manager.coffee
@@ -15,8 +15,7 @@
     range = @quill.getSelection()
     return unless range?
     @container.innerHTML = ""
-    windowScrollX = window.scrollX
-    windowScrollY = window.scrollY
+    scrollTop = @quill.container.scrollTop
     @container.focus()
     _.defer( =>
       doc = new Document(@container, @quill.options)
@@ -30,9 +29,9 @@
       @quill.setSelection(range.start + lengthAdded, range.start + lengthAdded)
       [line, offset] = @quill.editor.doc.findLineAt(range.start + lengthAdded)
       lineBottom = line.node.offsetTop + line.node.offsetHeight
-      if lineBottom > iframeScrollY + @quill.root.offsetHeight
-        iframeScrollY = line.node.offsetTop - @quill.root.offsetHeight / 2
-      window.scrollTo(windowScrollX, windowScrollY)
+      if lineBottom > scrollTop + @quill.container.offsetHeight
+        scrollTop = line.node.offsetTop - @quill.container.offsetHeight / 2
+      @quill.container.scrollTop = scrollTop
     )


Collaboration: chat heads file menu positioning fix

--- client/ide/views/statusbar/avatarview.coffee
+++ client/ide/views/statusbar/avatarview.coffee
@@ -97,7 +97,7 @@
         y           : @getY()
         offset      :
           top       : -5000
-          left      : -86
+          left      : -82
         arrow       :
           placement : 'bottom'
           margin    : menuWidth / 2
@@ -107,8 +107,8 @@
       KD.utils.wait 200, =>
         h = MENU.getHeight()
         w = MENU.getWidth()
-        top  = -h
-        left = @getWidth()/2 - w/2
+        top  = -h - 10
+        left = @getWidth()/2 - w/2 - 4 # for an unknown reason - SY
         MENU.setOption 'offset', {left, top}
         MENU.positionContextMenu()


myteam: modify initial input value store

--- client/app/lib/flux/teams/stores/teaminvitationinputvaluesstore.coffee
+++ client/app/lib/flux/teams/stores/teaminvitationinputvaluesstore.coffee
@@ -22,7 +22,7 @@

   getInitialState: ->

-    ['admin', 'member', 'member'].reduce (state, role, index) ->
+    ['member', 'member', 'member'].reduce (state, role, index) ->
       state.set index, createRecord role
     , immutable.Map()


home/billing: activate payment history link/tab

--- client/home/lib/billing/components/paymentinformation/container.coffee
+++ client/home/lib/billing/components/paymentinformation/container.coffee
@@ -32,7 +32,7 @@

   onPaymentHistory: ->

-    console.log 'onPaymentHistory'
+    kd.singletons.router.handleRoute '/Home/payment-history'

   onSave: ->


Classroom: Prefetch for next chapter config.

--- client/landing/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -74,11 +74,17 @@
     return if not chapters or not @isCourseStarted()

     @readFileContent "/#{courseManifest.name}.kdcourse/#{chapters[chapterIndex].resourcesPath}", (config) =>
-      courseMeta   =
-        name       : courseManifest.name
-        index      : ++chapterIndex
+      @currentChapterIndex = chapterIndex
+      courseMeta          =
+        name              : courseManifest.name
+        index             : ++chapterIndex

       @addSubView new ClassroomWorkspace { delegate: this }, { config, courseManifest, courseMeta }
+      @fetchNextCourseConfig()
+
+  fetchNextCourseConfig: ->
+    @readFileContent "/#{@manifest.name}.kdcourse/#{@manifest.chapters[@currentChapterIndex + 1].resourcesPath}", (config) =>
+      @nextChapterConfig = config

   createCoursesView: (manifest) ->
     @addSubView new ClassroomCourseView { delegate: this }, manifest


Add tests for insert_final_newline

Add tests for insert_final_newline

--- test/brave-mouse/validate.coffee
+++ test/brave-mouse/validate.coffee
@@ -321,3 +321,51 @@
 						results.should.be.true

 						done()
+
+			describe 'insert_final_newline', ->
+				after ->
+					mockFs.restore()
+
+				it 'should validate if file has a final newline but a final newline is not expected', (done) ->
+					prepareFs 'insert_final_newline', false, 'insert_final_newline/newline.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.insert_final_newline.should.deep.equal
+							expected: false
+							is: true
+
+						done()
+
+				it 'should validate if file has a final newline and a final newline is expected', (done) ->
+					prepareFs 'insert_final_newline', true, 'insert_final_newline/newline.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()
+
+				it 'should validate if file has no final newline but a final newline is expected', (done) ->
+						prepareFs 'insert_final_newline', true, 'insert_final_newline/none.txt'
+
+						bmValidate 'file.txt', (err, results) ->
+							results.insert_final_newline.should.deep.equal
+								expected: true
+								is: false
+
+							done()
+
+				it 'should validate if file has no final newline and no final newline is expected', (done) ->
+						prepareFs 'insert_final_newline', false, 'insert_final_newline/none.txt'
+
+						bmValidate 'file.txt', (err, results) ->
+							results.should.be.true
+
+							done()
+
+				it 'should not validate if nothing is expected', (done) ->
+					prepareFs null, null, 'insert_final_newline/none.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()


Hihi, fix build. x

Hihi, fix build. x

--- core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
@@ -195,7 +195,7 @@
   onRender: ->
     @inputRegion.switchTo 'search_view'
     # TODO: Remove switchTo 'comment_view', only used for development
-    @inputRegion.switchTo 'add_comment_view'
+    # @inputRegion.switchTo 'add_comment_view'

   searchView: ->
     searchView = new AutoCompleteFactRelationsView


setItem bug, W3C API

setItem bug, W3C API

--- sync.coffee
+++ sync.coffee
@@ -17,7 +17,7 @@
         if not @clientid? or @clientid.indexOf("sjs") != 0
             @clientid = "sjs-#{@s.bversion}-#{@uuid(5)}"
             try
-                localStorage.setItem "#{@namespace}/clientid"
+                localStorage.setItem "#{@namespace}/clientid", @clientid
             catch error
                 console.log "#{@name}: couldnt set clientid"

@@ -90,7 +90,9 @@
         if not @s.supports_html5_storage()
             return
         total = 0
-        for own key, datastr of localStorage
+        for i in [0..localStorage.length-1]
+            key = localStorage.key(i)
+            datastr = localStorage.getItem( key )
             console.log "[#{key}]: #{datastr}"
             total = total + 1
         console.log "#{total} total"
@@ -162,16 +164,17 @@
         key = "#{@namespace}/e/#{id}"
         store_data = @data.store[id]
         datastr = JSON.stringify(store_data)
+        console.log "LSTORE:", key, datastr
         try
             localStorage.setItem key, datastr
         catch error
             return false
         ret_data = JSON.parse localStorage.getItem key
         if @jd.equals store_data, ret_data
-#            console.log "saved #{key} len: #{datastr.length}"
+            console.log "saved #{key} len: #{datastr.length}"
             return true
         else
-#            console.log "ERROR STORING ENTITY: store: #{JSON.stringify(store_data)}, retrieve: #{JSON.stringify(ret_data)}"
+            console.log "ERROR STORING ENTITY: store: #{JSON.stringify(store_data)}, retrieve: #{JSON.stringify(ret_data)}"
             return false

     _remove_entity: (id) =>


Added "This" Help

Added "This" Help

--- help/this.coffee
+++ help/this.coffee
@@ -2,8 +2,20 @@
 # # This Help
 #
 module.export = """
-This is a Terminal, in your Browser. Think of it like a text interfave to
+This is a Terminal, in your Browser. Think of it like a text interface to
 your Virtual Machine, provided by Koding.

-The machine itself is running the Ubuntu operating system.
+The virtual machine itself is running the Ubuntu operating system. It is a
+real and normal Linux operating system, meaning you can run any Linux
+program, library, and tool that you would expect. Real development in your
+browser, with very few limitations.
+
+To see what programs come with your VM by default, type "help programs" and
+press return.
+
+As always, additional help can be found on Koding University at:
+  http://learn.koding.com
+
+Or from the Koding community, at:
+  https://koding.com/Activity
 """


Add label to feedback

Add label to feedback

--- shared/src/components/question/feedback.cjsx
+++ shared/src/components/question/feedback.cjsx
@@ -16,6 +16,7 @@

     <ArbitraryHtmlAndMath
       {...htmlAndMathProps}
+      aria-label="Answer Feedback"
       className='question-feedback-content has-html'
       html={@props.children}
       block={true}/>
@@ -33,9 +34,12 @@
     wrapperClasses = classnames 'question-feedback', @props.position
     htmlAndMathProps = _.pick(@context, 'processHtmlAndMath')

-

-

+

+

       <SimpleFeedback {...htmlAndMathProps}>{@props.children}</SimpleFeedback>

-module.exports = {Feedback, SimpleFeedback}+module.exports = {Feedback, SimpleFeedback}


Don't show the FCE warning when use_fce is null.

Old scenarios do not have an explicit true/false value for "use_fce".
This causeed jQuery.fn.toggle to turn the FCE warning on. Setting now
treats use_fce=null as being false.

--- app/assets/javascripts/lib/models/setting.coffee
+++ app/assets/javascripts/lib/models/setting.coffee
@@ -12,6 +12,10 @@
     @set(use_fce: !! $(event.target).attr('checked'))

   on_fce_status_change: (setting, use_fce) =>
+    # Old scenarios may provide "null" instead of true/false, and this causes
+    # the FCE warning to toggle on.
+    use_fce = false unless fce is true
+
     # Status change may be triggered by things other than the checkbox.
     $('#settings_use_fce').attr('checked', use_fce)


value to innerHTML

value to innerHTML

--- app/assets/javascripts/me-add-to-playlist.coffee
+++ app/assets/javascripts/me-add-to-playlist.coffee
@@ -15,13 +15,14 @@
       button.appendTo(controls)

       button.click (event) ->
-        if $('#add_playlist_item_playlists_not_empty')[0].value != 'true'
+        unless $('#add_playlist_item_playlists_not_empty')[0].innerHTML == 'true'
           $('#playlist_item_description')[0].value = ''
           $('#playlist_item_title').val(player.options.playlistItemDefaultTitle)
           $('#add_to_playlist').show(500)
           $('#playlist_item_start').val(mejs.Utility.secondsToTimeCode(player.getCurrentTime(), true))
           $('#playlist_item_end').val(mejs.Utility.secondsToTimeCode(player.media.duration, true))
           $('#add_to_playlist_alert').hide(500)
+

       $('.add_playlist_item_submit').click (event) ->
         p = $('#post_playlist_id').val()


project: tweak "test" mode

project: tweak "test" mode

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -121,11 +121,13 @@
 if not process.env.SMC?
     process.env.SMC = path.join(process.env.HOME, '.smc')

-SMC = process.env.SMC
+SMC = if program.test then '/tmp' else process.env.SMC

 process.chdir(process.env.HOME)

 DATA = path.join(SMC, 'local_hub')
+
+winston.debug("DATA='#{DATA}'")

 # See https://github.com/sagemathinc/cocalc/issues/174 -- some stupid (?)
 # code sometimes assumes this exists, and it's not so hard to just ensure
@@ -340,13 +342,16 @@
             fs.writeFile(misc_node.abspath("#{DATA}/local_hub.pid"), "#{process.pid}", cb)
         (cb) ->
             winston.debug("initializing secret token...")
-            secret_token.init_secret_token (err, token) ->
-                if err
-                    cb(err)
-                else
-                    the_secret_token = token
-                    exports.client.secret_token = token
-                    cb()
+            if program.test
+                exports.client.secret_token = "1234567890"
+            else
+                secret_token.init_secret_token (err, token) ->
+                    if err
+                        cb(err)
+                    else
+                        the_secret_token = token
+                        exports.client.secret_token = token
+                        cb()
         (cb) ->
             winston.debug("start API server...")
             try


webapp: of course, I forgot a log line in my hurry to roll out a fix for #1054

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -257,7 +257,6 @@
         @setState
             email_address  : email
             is_email_valid : misc.is_valid_email_address(email)
-        console.log @state.email_address, @state.is_email_valid

     display_error : ->
         if @props.forgot_password_error?


Unimportant missing change.

Unimportant missing change.

--- examples/multitouch/main.coffee
+++ examples/multitouch/main.coffee
@@ -100,7 +100,7 @@
                     # Transform rot_to with the cam_rotation
                     vec3.transformQuat rot_to, rot_to, cam.rotation
                     # q = quat from rot_from to rot_to
-                    q = quat.rotationTo [], rot_from, rot_to
+                    q = quat.rotationTo quat.create(), rot_from, rot_to
                     # Applying q to the object rotation.
                     quat.mul ob.rotation, q, ob.rotation


Add mentions

Add mentions

--- assets/javascripts/views/new_post_form.js.coffee
+++ assets/javascripts/views/new_post_form.js.coffee
@@ -107,6 +107,9 @@
     )
     delete data.mentions

+    for entity in data.text.match(/\^(\S+)/g)
+      mentions.push { entity: entity.replace(/^\^/, '') }
+
     data.mentions = mentions if mentions.length
     data


Keep state of history

Keep state of history

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -36,12 +36,17 @@
             @history_view.clear_history event
             @clear_history()

-        open_close_history: (event) =>
-            @history_view.open_close_history event, @$('.close_queries_link')
+        open_close_history: (event, args) =>
+            @history_view.open_close_history(args)
             if @history_view.state is 'visible'
-                @$('.clear_queries_link').fadeIn 'fast'
+                @saved_data.history_state = 'visible'
+                if args?.no_animation is true
+                    @$('.clear_queries_link').show()
+                else
+                    @$('.clear_queries_link').fadeIn 'fast'
                 @$('.close_queries_link').addClass 'active'
             else
+                @saved_data.history_state = 'hidden'
                 @$('.clear_queries_link').fadeOut 'fast'
                 @$('.close_queries_link').removeClass 'active'

@@ -182,6 +187,7 @@
                     metadata: null
                     cursor_timed_out: true
                     view: 'tree'
+                    history_state: 'hidden'

             # Load history, keep it in memory for the session
             if not DataExplorerView.Container.prototype.history?
@@ -285,7 +291,6 @@
                 @error_on_connect()

             @$('.history_container').html @history_view.render().$el
-
             return @

         # This method has to be called AFTER the el element has been inserted in the DOM tree, mostly for codemirror
@@ -316,6 +321,10 @@
             @results_view.expand_raw_textarea()

             @draft = @codemirror.getValue()
+
+            if @saved_data.history_state is 'visible' # If the history was visible, we show it
+                @open_close_history event,
+                    no_animation: true

         on_blur: =>
             @prototype.focus_on_codemirror = false
@@ -2335,8 +2344,7 @@
                     @resize
                         size: 32

-        open_close_history: (event, container) =>
-            event?.preventDefault()
+        open_close_history: (args) =>
             that = @
             if @state is 'visible'
                 @state = 'hidden'
@@ -2355,7 +2363,7 @@
                 @state = 'visible'
                 @$('.arrow_history').show()
                 @$('.nano_border').show()
-                @resize()
+                @resize(args)
                 @$('.nano >.content').scrollTop $('.history_list').height()

@@ -2372,20 +2380,27 @@
                 else
                     size = Math.min @$('.history_list').height(), @height_history
             @$('.nano').css 'visibility', 'visible'
-            @desactivate_overflow()
-            @$('.nano').animate
-                height: size
-                , 200
-                , ->
-                    $('body').css 'overflow', 'auto'
-                    $(@).css 'visibility', 'visible' # In case the user trigger hide/show really fast
-                    $('.arrow_history').show() # In case the user trigger hide/show really fast
-                    $('.nano_border').show() # In case the user trigger hide/show really fast
-                    $(@).nanoScroller({preventPageScrolling: true})
-                    if args?.is_at_bottom is true
-                        $('.nano >.content').animate
-                            scrollTop: $('.history_list').height()
-                            , 300
+            if args?.no_animation is true
+                @$('.nano').height size
+                @$('.nano').css 'visibility', 'visible' # In case the user trigger hide/show really fast
+                @$('.arrow_history').show() # In case the user trigger hide/show really fast
+                @$('.nano_border').show() # In case the user trigger hide/show really fast
+                @$('.nano').nanoScroller({preventPageScrolling: true})
+            else
+                @desactivate_overflow()
+                @$('.nano').animate
+                    height: size
+                    , 200
+                    , ->
+                        $('body').css 'overflow', 'auto'
+                        $(@).css 'visibility', 'visible' # In case the user trigger hide/show really fast
+                        $('.arrow_history').show() # In case the user trigger hide/show really fast
+                        $('.nano_border').show() # In case the user trigger hide/show really fast
+                        $(@).nanoScroller({preventPageScrolling: true})
+                        if args?.is_at_bottom is true
+                            $('.nano >.content').animate
+                                scrollTop: $('.history_list').height()
+                                , 300

         # The 3 secrets of French cuisine is butter, butter and butter
         # We desactivate the scrollbar (if there isn't) while animating to have a smoother experience. We´ll put back the scrollbar once the animation is done.