Minor tweaks

Minor tweaks

--- spec/helpers.coffee
+++ spec/helpers.coffee
@@ -39,12 +39,13 @@

   api = tv4.freshApi()

+  api.banUnknown = false
   api.cyclicCheck = false
-  api.banUnknown = false

-  api.addSchema(k, v) for k, v of refs
+  api.addSchema(id, json) for id, json of fixed

-  result = api.validateResult(@actual, clone(expected))
+  result = api.validateResult @actual,
+    clone(expected), api.cyclicCheck, api.banUnknown

   if result.missing.length
     throw new Error 'Missing ' + result.missing.join(', ')
@@ -55,12 +56,11 @@

   formatValidators jay

-  jay.register(s) for s of refs
+  jay.register(clone(json)) for id, json of fixed

   result = jay.validate @actual, clone(expected)

-  if result.length
-    throw result.map((e) -> e.desc or e.message).join('\n') or
-      "Invalid schema #{JSON.stringify @actual}"
+  throw result.map((e) -> e.desc or e.message).join('\n') or
+    "Invalid schema #{JSON.stringify @actual}" if result.length

   true


store which player you are instead of user id

--- client/game.coffee
+++ client/game.coffee
@@ -43,13 +43,8 @@

 displayNotice = (msg) -> $(".nNotification").text(msg)

-setUrl = (gameId=null) ->
-  if gameId
-    window.history.pushState({}, "",
-        "?user_id=#{Meteor.userId()}&game_id=#{gameId}")
-  else
-    window.history.pushState({}, "",
-        "?user_id=#{Meteor.userId()}")
+setUrl = (gameId, player) ->
+    window.history.pushState({}, "", "?game_id=#{gameId}&player=#{player}")

 PlayScreen = me.ScreenObject.extend
   handleClick_: (tile) ->
@@ -134,7 +129,10 @@
   gameId = $.url().param("game_id")

   Meteor.autorun ->
-    setUrl(Session.get("gameId"))
+    game = noughts.Games.findOne Session.get("gameId")
+    if game
+      isXPlayer = game.currentPlayer == game.xPlayer
+      setUrl(game._id, if isXPlayer then "x" else "o")

   if gameId
     game = noughts.Games.findOne(gameId)


added save shortcut

added save shortcut

--- client/main.coffee
+++ client/main.coffee
@@ -45,6 +45,16 @@
     $(".ace_gutter").hide()
     $(".ace_scroller").css('width',  parseInt($(".ace_scroller").css('width')) + lineNumberWidth)

+    # Add save shortcut
+    canon = require('pilot/canon')
+    canon.addCommand
+      name: 'myCommand'
+      bindKey:
+        win: "Ctrl-S"
+        mac: "Command-S"
+        sender: 'editor'
+      exec: (env, args, request) =>
+        @save()

   getDocId: ->
     @model.get "id"


Add Zepto installer.

Add Zepto installer.

--- src/Deferred.coffee
+++ src/Deferred.coffee
@@ -304,3 +304,21 @@
       .fail (failArgs...) ->
         allReady.rejectWith(this, failArgs...)
   allReady.promise()
+
+# Install Deferred to Zepto automatically.
+do ->
+  destination = window.Zepto
+  return if not destination or destination.Deferred
+  destination.Deferred = ->
+    new Deferred()
+  origAjax = destination.ajax
+  destination.ajax = (options) ->
+    deferred = new Deferred()
+    createWrapper = (wrapped, finisher) ->
+      (args...) ->
+        wrapped?(args...)
+        finisher(args...)
+    options.success = createWrapper options.success, deferred.resolve
+    options.error = createWrapper options.error, deferred.reject
+    origAjax(options)
+    deferred.promise()


Minor code lint

Minor code lint

--- client/app/models/scheduleitem.coffee
+++ client/app/models/scheduleitem.coffee
@@ -47,7 +47,7 @@
     #
     # @see https://tools.ietf.org/html/rfc5545#section-3.3.9
     isSameDay: ->
-        endDate = if @isAllDay() then @getEndDateObject().add -1, 'd' else
+        endDate = if @isAllDay() then @getEndDateObject().add -1, 'd' else \
                                       @getEndDateObject()
         endDate.isSame @getStartDateObject(), 'day'

@@ -289,8 +289,8 @@
             return callback false

         # Kind of changes which doesn't need mails.
-        if method in ['update', 'patch'] and not (
-                @startDateChanged or @attendeesChanged)
+        if method in ['update', 'patch'] and
+           not ( @startDateChanged or @attendeesChanged)
             return callback false

         # else: look state of each guest.
@@ -300,7 +300,7 @@
                 return true

             else if method is 'delete'
-                 return guest.status in ['ACCEPTED', 'NEEDS-ACTION']
+                return guest.status in ['ACCEPTED', 'NEEDS-ACTION']

             else if method in ['update', 'patch']
                 return guest.status is 'INVITATION-NOT-SENT' or (


remove unused exclusion warning

remove unused exclusion warning

It lives in question/library/exercise-display.  This wasn't removed when
that was extracted

--- src/components/exercises/cards.cjsx
+++ src/components/exercises/cards.cjsx
@@ -22,20 +22,6 @@
     onExerciseToggle:       React.PropTypes.func.isRequired
     getExerciseIsSelected:  React.PropTypes.func.isRequired
     getExerciseActions:     React.PropTypes.func.isRequired
-
-  renderMinimumExclusionWarning: ->
-    [
-      <Icon key="icon" type="exclamation" />
-

-

-          Tutor needs at least 5 questions for this topic to be
-          included in spaced practice and personalized learning.
-

-

-          If you exclude too many, your students will not get to practice on this topic.
-

-

-    ]

   render: ->
     title = TocStore.getSectionLabel(@props.chapter_section)?.title


Added attribute readonly for state readonly

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -343,6 +343,7 @@

           if !@is_section_break
             @$el.addClass('readonly') if @model.get("field_options").state is "readonly"
+            $('.readonly').find('input, textarea, select').attr('readonly', 'readonly');
             @$el.addClass('response-field-'+ @field_type + ' '+ @model.getCid())
               .data('cid', cid)
               .html(Formbuilder.templates["view/base#{base_templ_suff}"]({


Rename ExpectsMethod.check_expects_usage() to check_usage()

--- src/TinyMock.coffee
+++ src/TinyMock.coffee
@@ -96,13 +96,13 @@
 new_ExpectsMethod = (expects_method_name, mock_methods)->

   expects_method = (method_name) ->
-    check_expects_usage(@, method_name, arguments.length)
+    check_usage(@, method_name, arguments.length)
     if not is_mock_method(@[method_name])
       @[method_name] = new_MockMethod(@, method_name)
       mock_methods.add(@[method_name])
     @[method_name].create_expectation()

-  check_expects_usage = (self, method_name, arg_count) ->
+  check_usage = (self, method_name, arg_count) ->
     fail(messages.ExpectsUsage) unless method_name?
     fail(messages.ExpectsUsage) unless arg_count == 1
     fail(messages.NotAnExistingMethod, method_name) unless is_mock_object(self) or has_method(self, method_name)


Fix typo in utils.coffee and remove old arguments

--- src/readmill/utils.coffee
+++ src/readmill/utils.coffee
@@ -136,7 +136,7 @@
   #
   # Returns a highlight object.
   highlightFromAnnotation: (annotation, root) ->
-    locator = @locationFromAnnotation(annotation, root)
+    locator = @locatorsFromAnnotation(annotation, root)
     # See: https://github.com/Readmill/API/wiki/Readings
     {
       id: annotation.id


Promoting middleware response logging to INFO, since it is helpful info

--- src/scripts/access.coffee
+++ src/scripts/access.coffee
@@ -31,7 +31,7 @@
         Logger.addReaction 'x', message
         done()
       else
-        Logger.debug "Responding to '#{message}' (#{command}) from #{username}"
+        Logger.log "Responding to '#{message}' (#{command}) from #{username}"
         if context.listener.options.adminOnly
           if not isAdminUser username
             # Admin command, but user isn't in whitelist


Fixed possible undefined property on popup reference during login

--- src/api/auth.coffee
+++ src/api/auth.coffee
@@ -86,8 +86,7 @@
       authHelper: (path)->
         win = window.open(path, "_auth", 'location=0,status=0,width=990,height=600')
         _popupInterval = setInterval ->
-          if win.closed
-            onCompleteAuthentication()
+          onCompleteAuthentication() if win?.closed
         , 200

       onCompleteAuth: onCompleteAuthentication


Minor fixes.

Minor fixes.

--- src/api/user.coffee
+++ src/api/user.coffee
@@ -35,7 +35,7 @@
 # Changes the password of the current user.
 exports.changePassword = (session, request) ->
   {old, new: newP} = request.params
-  @throw 'INVALID_PARAMS' unless p_old? and newP?
+  @throw 'INVALID_PARAMS' unless old? and newP?

   # Current user must be signed in.
   @checkPermission session
@@ -61,10 +61,10 @@
   @throw 'INVALID_PARAMS' unless id?

   # Only an administrator can see another user.
-  @checkPermission session, 'admin' unless session.get 'user_id' is p_id
+  @checkPermission session, 'admin' unless session.get 'user_id' is id

   # Retrieves the user.
-  user = $waitPromise @xo.users.first p_id
+  user = $waitPromise @xo.users.first id

   # Throws an error if it did not exist.
   @throw 'NO_SUCH_OBJECT' unless user


Forgot to refresh gitx

Forgot to refresh gitx

--- src/app/pane.coffee
+++ src/app/pane.coffee
@@ -1,5 +1,7 @@
+{View} = require 'space-pen'
+
 module.exports =
-class Pane
-  @content: (view) ->
+class Pane extends View
+  @content: (content) ->
     @div class: 'pane', =>
-      @subview 'view', view
+      @subview 'content', content


Add rectangle and transcription tools

Add rectangle and transcription tools

--- src/subject-viewer.coffee
+++ src/subject-viewer.coffee
@@ -6,7 +6,8 @@
   point: require 'marking-surface/lib/tools/point'
   circle: null
   ellipse: require 'marking-surface/lib/tools/ellipse'
-  rect: null
+  rect: require 'marking-surface/lib/tools/rectangle'
+  text: require 'marking-surface/lib/tools/transcription'
   polygon: null

 loadImage = (src, callback) ->


Fix vote from from email

Fix vote from from email

--- lineman/app/components/thread_page/thread_page_controller.coffee
+++ lineman/app/components/thread_page/thread_page_controller.coffee
@@ -9,11 +9,16 @@

   @performScroll = ->
     ScrollService.scrollTo @elementToFocus(), 150
+    $rootScope.$broadcast 'triggerVoteForm', $location.search().position if @openVoteModal()
+
+  @openVoteModal = ->
+    $location.search().position and
+    @discussion.hasActiveProposal() and
+    @discussion.activeProposal().key == $location.search().proposal and
+    AbilityService.canVoteOn(@discussion.activeProposal())

   @elementToFocus = ->
     if @proposalToFocus
-      if (position = $location.search().position) and AbilityService.canVoteOn(proposal)
-        $rootScope.$broadcast 'triggerVoteForm', position
       "#proposal-#{@proposalToFocus.key}"
     else if @commentToFocus
       "#comment-#{@commentToFocus.id}"
@@ -30,6 +35,7 @@
       @discussion = discussion
       @group      = @discussion.group()
       @comment    = Records.comments.build(discussion_id: @discussion.id)
+      @openVoteModal()

       @sequenceIdToFocus = @discussion.reader().lastReadSequenceId # or location hash when we put it back in.


Fix path to domain-browser

Fix path to domain-browser

--- src/builtins.coffee
+++ src/builtins.coffee
@@ -14,7 +14,7 @@
   crypto:               'crypto-browserify/index.js'
   dgram:                null
   dns:                  null
-  domain:               'domain-browser/index.js'
+  domain:               'domain-browser/source/index.js'
   events:               'events/events.js'
   fs:                   null
   http:                 'http-browserify/index.js'


Fix bug when caching orderForm.

Fix bug when caching orderForm.

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -65,11 +65,12 @@
 				'giftRegistryData'
 				'ratesAndBenefitsData'
 			]
+
 		@version = 'VERSION'

 	cacheOrderForm: (data) =>
 		@orderFormId = data.orderFormId
-		@orderForm = data.orderForm
+		@orderForm = data

 	orderFormHasExpectedSections = (orderForm, sections) ->
 		if not orderForm or not orderForm instanceof Object


Defaulting cart count to zero if undefined

--- app/assets/javascripts/darkswarm/services/current_order.js.coffee
+++ app/assets/javascripts/darkswarm/services/current_order.js.coffee
@@ -2,5 +2,7 @@
   new class CurrentOrder
     constructor: ->
         @[k] = v for k, v of currentOrder
+        @cart_count ?= 0
+
     empty: =>
       @line_items.length == 0


Extend list of supported file extensions

Some manpages use a combination of their page numbers and group names in
their file extensions (e.g., "config.5ssl"). We obviously can't cater to
every possible alphanumeric permutation, so we've settled for using only
the most common *roff file-extensions.

Source: http://man7.org/linux/man-pages/man7/groff_filenames.7.html


removed destructive restful routes

removed destructive restful routes

--- app.coffee
+++ app.coffee
@@ -42,14 +42,10 @@
 app.locals site_name: "Node Love"

 #
-# Pages API routes
+# Pages API get routes
 #
 app.get "/pages", page.findAll
 app.get "/pages/:id", page.findByAttribute
-
-#app.post "/pages", page.addPage
-#app.put "/pages/:id", page.updatePage
-#app.delete "/pages/:id", page.deletePage

 #
 # Else, render layout


start background flush after http server has started

--- app.coffee
+++ app.coffee
@@ -143,11 +143,12 @@
 if !module.parent # Called directly
 	app.listen port, host, ->
 		logger.info "Document-updater starting up, listening on #{host}:#{port}"
+		if Settings.continuousBackgroundFlush
+			logger.info "Starting continuous background flush"
+			DeleteQueueManager.startBackgroundFlush()
+
 module.exports = app

 for signal in ['SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGABRT']
 	process.on signal, shutdownCleanly(signal)

-if Settings.continuousBackgroundFlush
-	logger.info "Starting continuous background flush"
-	DeleteQueueManager.startBackgroundFlush()


Check saved main window position before create it

--- app.coffee
+++ app.coffee
@@ -86,12 +86,22 @@
 app.on 'ready', ->
   shortcut.register()
   {screen} = require 'electron'
-  screenSize = screen.getPrimaryDisplay().workAreaSize
+  {workArea} = screen.getPrimaryDisplay()
+  {x, y, width, height} = config.get 'poi.window', workArea
+  validate = (n, min, range) ->
+    n? and n >= min and n < min + range
+  withinDisplay = (d) ->
+    wa = d.workArea
+    validate(x, wa.x, wa.width) and validate(y, wa.y, wa.height)
+  if not screen.getAllDisplays().some withinDisplay
+    {x, y} = workArea
+  width ?= workArea.width
+  height ?= workArea.height
   global.mainWindow = mainWindow = new BrowserWindow
-    x: config.get 'poi.window.x', 0
-    y: config.get 'poi.window.y', 0
-    width: config.get 'poi.window.width', screenSize.width
-    height: config.get 'poi.window.height', screenSize.height
+    x: x
+    y: y
+    width: width
+    height: height
     resizable: config.get 'poi.content.resizeable', true
     alwaysOnTop: config.get 'poi.content.alwaysOnTop', false
     'title-bar-style': 'hidden'


add remaining lodash functions

add remaining lodash functions

--- grunt/build.coffee
+++ grunt/build.coffee
@@ -88,7 +88,12 @@
     options:
       modifier: 'modern'
       include: [
-        'each', 'map', 'reduce'
+        'difference', 'flatten', 'intersection', 'last'
+        'all', 'each', 'map', 'pluck', 'reduce'
+        'bind', 'defer'
+        'clone', 'defaults', 'has', 'keys', 'omit', 'values'
+        'isArray', 'isElement', 'isEqual', 'isNumber', 'isObject', 'isString'
+        'uniqueId'
       ]
       flags: ['debug']
     target:


enable compiler to better serve as a stand-alone library module

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -8,9 +8,12 @@
 # which implements the version 1.0 of the YANG language specifications.
 ###

-synth = require 'data-synth'
-
-class YangCompiler
+synth  = require 'data-synth'
+parser = require 'yang-parser'
+#YANG_V1_SPEC = require '../yang-v1-spec.json'
+YANG_V1_SPEC = {}
+
+class Compiler

   define: (type, key, value) ->
     _define = (to, type, key, value) ->
@@ -80,6 +83,8 @@
     res.context = context
     return res

+  normalize = (obj) -> ([ obj.prf, obj.kw ].filter (e) -> e? and !!e).join ':'
+
   ###
   # The `parse` function performs recursive parsing of passed in statement
   # and sub-statements and usually invoked in the context of the
@@ -88,10 +93,7 @@
   # currently does NOT perform semantic validations but rather simply
   # ensures syntax correctness and building the JS object tree structure.
   ###
-
-  normalize = (obj) -> ([ obj.prf, obj.kw ].filter (e) -> e? and !!e).join ':'
-
-  parse: (input, parser=(require 'yang-parser')) ->
+  parse: (input, parser=parser) ->
     try
       input = (parser.parse input) if typeof input is 'string'
     catch e
@@ -114,6 +116,10 @@
       when not !!input.arg then params
       else "#{input.arg}": params

+  extractKeys = (x) -> if x instanceof Object then (Object.keys x) else [x].filter (e) -> e? and !!e
+
+  fork: (f, args...) -> f?.apply? (new @constructor), args
+
   ###
   # The `preprocess` function is the intermediary method of the compiler
   # which prepares a parsed output to be ready for the `compile`
@@ -121,11 +127,6 @@
   # found in the parsed output in order to prepare the context for the
   # `compile` operation to proceed smoothly.
   ###
-
-  extractKeys = (x) -> if x instanceof Object then (Object.keys x) else [x].filter (e) -> e? and !!e
-
-  fork: (f, args...) -> f?.apply? (new @constructor), args
-
   preprocess: (schema, source={}, scope) ->
     unless scope?
       # first merge source extension using parent extensions if available
@@ -209,12 +210,13 @@
   # return synthesized object hierarchy.
   ###

-  compile: (schema, source={}, scope) ->
+  compile: (schema, source=YANG_V1_SPEC, scope) ->
     return @fork arguments.callee, schema, source, true unless scope?
     @source = source

     schema = (schema.call this) if schema instanceof Function
-    schema = (@preprocess schema, source) unless source.extension?
+    #schema = (@preprocess schema, source) unless source.extension?
+    schema = @preprocess schema, source unless schema instanceof Object
     unless schema instanceof Object
       throw @error "must pass in proper 'schema' to compile"

@@ -248,4 +250,12 @@
             throw @error "[compile:#{source.name}] failed to compile '#{key} #{arg}'", schema
     return output

-module.exports = YangCompiler
+#
+# declare exports
+#
+exports = module.exports = new Compiler
+exports.Compiler = Compiler
+exports.Module = class extends synth.Model
+  @schema = (s) ->
+    obj = (exports.compile s, YANG_V1_SPEC)
+    (@extend obj).merge schema: obj


incomplete: touch support

incomplete: touch support

--- src/ui/application.coffee
+++ src/ui/application.coffee
@@ -582,30 +582,48 @@
   else
     dragHandler = new MouseToolCombo application, x, y

-doSetPanMode = (mode) ->
-  isPanMode = mode
-
-  bpan = E('btn-mode-pan')
-  bedit = E('btn-mode-edit')
-  removeClass bpan, 'button-active'
-  removeClass bedit, 'button-active'
-
-  addClass (if isPanMode then bpan else bedit), 'button-active'
-
-doCanvasMouseMove = (e) ->
-
-  isPanAction = (e.shiftKey) ^ (isPanMode)
-  E('canvas-container').style.cursor = if isPanAction then 'move' else 'default'
-
-  if dragHandler isnt null
-    e.preventDefault()
-    dragHandler.mouseMoved e
-
 doCanvasMouseUp = (e) ->
   if dragHandler isnt null
     e.preventDefault()
     dragHandler?.mouseUp e
     dragHandler = null
+
+doCanvastouchStart = (e)->
+  if e.touches.length is 1
+    doCanvasMouseDown(e)
+    e.preventDefault()
+
+doCanvastouchLeave = (e)->
+  doCanvasMouseOut(e)
+
+doCanvastouchEnd = (e)->
+  e.preventDefault()
+  doCanvasMouseUp(e)
+
+doCanvastouchMove = (e)->
+  doCanvasMouseMove(e)
+
+
+
+doSetPanMode = (mode) ->
+  isPanMode = mode
+
+  bpan = E('btn-mode-pan')
+  bedit = E('btn-mode-edit')
+  removeClass bpan, 'button-active'
+  removeClass bedit, 'button-active'
+
+  addClass (if isPanMode then bpan else bedit), 'button-active'
+
+doCanvasMouseMove = (e) ->
+
+  isPanAction = (e.shiftKey) ^ (isPanMode)
+  E('canvas-container').style.cursor = if isPanAction then 'move' else 'default'
+
+  if dragHandler isnt null
+    e.preventDefault()
+    dragHandler.mouseMoved e
+

 doOpenEditor = ->
   E('generic-tf-code').value = application.transitionFunc.code
@@ -768,6 +786,12 @@
 mouseMoveReceiver.addEventListener "mouseup", doCanvasMouseUp
 mouseMoveReceiver.addEventListener "mousemove", doCanvasMouseMove
 mouseMoveReceiver.addEventListener "mousedrag", doCanvasMouseMove
+
+mouseMoveReceiver.addEventListener "touchstart", doCanvasTouchStart
+mouseMoveReceiver.addEventListener "touchend", doCanvasTouchEnd
+mouseMoveReceiver.addEventListener "touchmove", doCanvasTouchMove
+mouseMoveReceiver.addEventListener "touchleave", doCanvasTouchLeave
+

 E("btn-set-rule").addEventListener "click", (e)->application.doSetRule()
 E("btn-set-rule-generic").addEventListener "click", (e)->


Proposed fix for #1234.

Proposed fix for #1234.

See https://github.com/yakyak/yakyak/issues/1234#issuecomment-697462483

--- src/ui/models/conv.coffee
+++ src/ui/models/conv.coffee
@@ -11,7 +11,7 @@
 add = (conv) ->
     # rejig the structure since it's insane
     if conv?.conversation?.conversation_id?.id
-        {conversation, event} = conv
+        {conversation, event = []} = conv
         conv = conversation
         # remove observed events
         conv.event = (e for e in event when !e.event_id.match(/observed_/))


webpack: exposing the full MATHJAX_URL in the app

--- src/webpack.config.coffee
+++ src/webpack.config.coffee
@@ -48,6 +48,9 @@
 MATHJAX_DIR = 'smc-webapp/node_modules/mathjax'
 MATHJAX_VERS = JSON.parse(fs.readFileSync("#{MATHJAX_DIR}/package.json", 'utf8')).version
 MATHJAX_ROOT = path.join(OUTPUT, "mathjax-#{MATHJAX_VERS}")
+MATHJAX_URL  = path.join(BASE_URL, MATHJAX_ROOT, 'MathJax.js')
+console.log "MATHJAX_ROOT='#{MATHJAX_ROOT}'"
+console.log "MATHJAX_URL='#{MATHJAX_URL}'"

 # webpack plugin to do the linking after it's "done"
 class MathjaxVersionedSymlink
@@ -95,7 +98,7 @@
 jade2html = new HtmlWebpackPlugin
                         date     : dateISO
                         title    : 'SageMathCloud'
-                        mathjax  : "#{MATHJAX_ROOT}/MathJax.js"
+                        mathjax  : MATHJAX_URL
                         filename : 'index.html'
                         chunksSortMode: smcChunkSorter
                         hash: false
@@ -134,7 +137,7 @@

 setNODE_ENV          = new webpack.DefinePlugin
                                 'MATHJAX_VERS': MATHJAX_VERS
-                                'MATHJAX_ROOT': MATHJAX_ROOT
+                                'MATHJAX_URL' : MATHJAX_URL
                                 'VERSION'     : VERSION
                                 'process.env' :
                                     'NODE_ENV'     : JSON.stringify(NODE_ENV)


webapp/webpack2 migration wip

webapp/webpack2 migration wip

--- src/webpack.config.coffee
+++ src/webpack.config.coffee
@@ -104,7 +104,7 @@
 SMC_LICENSE   = 'AGPLv3'
 WEBAPP_LIB    = misc_node.WEBAPP_LIB
 INPUT         = path.resolve(__dirname, WEBAPP_LIB)
-OUTPUT        = misc_node.OUTPUT_DIR
+OUTPUT        = path.resolve(__dirname, misc_node.OUTPUT_DIR)
 DEVEL         = "development"
 NODE_ENV      = process.env.NODE_ENV || DEVEL
 PRODMODE      = NODE_ENV != DEVEL
@@ -420,13 +420,27 @@
                         includes: c.modules.map (m) ->  m.request
                 )

+# https://webpack.js.org/guides/migrating/#uglifyjsplugin-minimize-loaders
+loaderOptions = new webpack.LoaderOptionsPlugin(
+    minimize: true
+    options:
+        'html-minify-loader':
+            empty                : true   # KEEP empty attributes
+            cdata                : true   # KEEP CDATA from scripts
+            comments             : false
+            removeComments       : true
+            minifyJS             : true
+            minifyCSS            : true
+            collapseWhitespace   : true
+            conservativeCollapse : true   # absolutely necessary, also see above in module.loaders/.html
+)

 plugins = [
     cleanWebpackPlugin,
     #provideGlobals,
     setNODE_ENV,
     banner,
-    new webpack.LoaderOptionsPlugin({minimize: true})  # https://webpack.js.org/guides/migrating/#uglifyjsplugin-minimize-loaders
+    loaderOptions
 ]

 if STATICPAGES
@@ -566,8 +580,8 @@

     resolve:
         # So we can require('file') instead of require('file.coffee')
-        extensions : ['', '.js', '.json', '.coffee', '.cjsx', '.scss', '.sass']
-        root       : [path.resolve(__dirname),
+        extensions : ['.js', '.json', '.coffee', '.cjsx', '.scss', '.sass']
+        modules    : [path.resolve(__dirname),
                       path.resolve(__dirname, WEBAPP_LIB),
                       path.resolve(__dirname, 'smc-util'),
                       path.resolve(__dirname, 'smc-util/node_modules'),
@@ -578,14 +592,3 @@
         #    modules: path.join(__dirname, "node_modules") # bind to modules;

     plugins: plugins
-
-    'html-minify-loader':
-        empty                : true   # KEEP empty attributes
-        cdata                : true   # KEEP CDATA from scripts
-        comments             : false
-        removeComments       : true
-        minifyJS             : true
-        minifyCSS            : true
-        collapseWhitespace   : true
-        conservativeCollapse : true   # absolutely necessary, also see above in module.loaders/.html
-


Use a single command to install apm and atom

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -109,11 +109,7 @@
     @command 'application:open-your-keymap', -> ipc.sendChannel('command', 'application:open-your-keymap')
     @command 'application:open-your-stylesheet', -> ipc.sendChannel('command', 'application:open-your-stylesheet')

-    @command 'window:install-atom-cli', =>
-      CommandInstaller.installApmCommand (error) => @commandInstalationDialog(error, 'atom')
-
-    @command 'window:install-apm-cli', =>
-      CommandInstaller.installApmCommand (error) => @commandInstalationDialog(error, 'apm')
+    @command 'window:install-shell-commands', => @installShellCommands()

     @command 'window:run-package-specs', => ipc.sendChannel('run-package-specs', path.join(atom.project.getPath(), 'spec'))
     @command 'window:increase-font-size', =>
@@ -138,16 +134,22 @@
     @command 'core:save', => @saveActivePaneItem()
     @command 'core:save-as', => @saveActivePaneItemAs()

-  commandInstalationDialog: (error, commandName) ->
-    if error?
-      installDirectory = CommandInstaller.getInstallDirectory()
-      atom.confirm
-        message: error.message
-        detailedMessage: "Make sure #{CommandInstaller.getInstallDirectory()} is writable. Run 'sudo chmod o+w #{installDirectory}' to fix this problem."
-    else
-      atom.confirm
-        message: "Command installed."
-        detailedMessage: "You can now use `#{commandName}` from the terminal."
+  installShellCommands: ->
+    showDialog = (error, commandName)->
+      if error?
+        installDirectory = CommandInstaller.getInstallDirectory()
+        atom.confirm
+          message: error.message
+          detailedMessage: "Make sure #{CommandInstaller.getInstallDirectory()} is writable. Run 'sudo chmod o+w #{installDirectory}' to fix this problem."
+      else
+        atom.confirm
+          message: "Command installed."
+          detailedMessage: "The shell command `#{commandName}` is installed."
+
+    CommandInstaller.installApmCommand (error) =>
+      showDialog(error, 'atom')
+      unless error?
+        CommandInstaller.installApmCommand (error) => showDialog(error, 'apm')

   # Private:
   handleFocus: (e) ->


added options for local-loading and remote loading

--- source/09__vocab.coffee
+++ source/09__vocab.coffee
@@ -50,10 +50,12 @@

 tagg.addBank(basic_vocab)

-autoload = document.querySelectorAll('[data-autoload="false"]')
-if autoload.length is 0
+load_local = document.querySelectorAll('[data-local-loading="false"]')
+if load_local.length is 0
     tagg.addBank(new tagg.FamilyBank({path:"."}))

+load_remote = document.querySelectorAll('[data-remote-loading="false"]')
+if load_remote.length is 0
     tagg.addBank(new tagg.FamilyBank({
         protocol: "https",
         hostname: "storage.googleapis.com",


Fixed lookup for resulting AST in generation script.

--- scripts/generate-samples-ast.coffee
+++ scripts/generate-samples-ast.coffee
@@ -27,7 +27,7 @@
         #{mson}
   """
   protagonist.parse blueprint, (err, result) ->
-    ast = result?.ast?.resourceGroups?[0]?.resources?[0]?.attributes
+    ast = result?.ast?.resourceGroups?[0]?.resources?[0]?.attributes?.source
     cb err, ast


いや、まさかそんな

いや、まさかそんな

--- scripts/line-beacon-listener.coffee
+++ scripts/line-beacon-listener.coffee
@@ -1,16 +1,12 @@
-config =
-  baseUrl: process.env.HUBOT_HEROKU_KEEPALIVE_URL ? 'http://localhost:8080'

 module.exports = (robot) ->

   robot.router.post "/line-becon", (req, res) ->
     if not req.body
       res.end ""
-      robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊ボディなんてねぇよ！！"
       return

     data = if req.body.payload? then JSON.parse req.body.payload else req.body
-    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{req.body}"
-    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{data}"
-    robot.send {room: "C4LEAQHPW"}, "ビーコンに反応あり"
+    robot.logger.error data
+    robot.send {room: "C4LEAQHPW"}, data
     res.end ""


Suggest slug, once again.

Suggest slug, once again.

--- client/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/app/Applications/Groups.kdapplication/AppController.coffee
@@ -398,14 +398,21 @@
   showGroupSubmissionView:->

     verifySlug = ->
+      titleInput = modal.modalTabs.forms["General Settings"].inputs.Title
       slugInput = modal.modalTabs.forms["General Settings"].inputs.Slug
       KD.remote.api.JName.one
         name: slugInput.getValue()
       , (err, name)->
         if name
           slugInput.setValidationResult 'slug', "Slug is already being used.", yes
+          KD.remote.api.JGroup.suggestUniqueSlug titleInput.getValue(), 0, (err, newSlug)->
+            if newSlug
+              slugInput.setTooltip
+                title     : "Next available slug: #{newSlug}"
+                placement : "right"
         else
           slugInput.setValidationResult 'slug', null
+          delete slugInput.tooltip

     makeSlug = =>
       titleInput = modal.modalTabs.forms["General Settings"].inputs.Title
@@ -563,7 +570,7 @@
     modal = new KDModalViewWithForms modalOptions
     form = modal.modalTabs.forms["General Settings"]
     form.on "FormValidationFailed", ->
-    form.buttons.Save.hideLoader()
+      form.buttons.Save.hideLoader()

   handleError =(err, buttons)->
     unless buttons


Let Viewer app works with new folder structure.

--- client/app/Applications/Viewer.kdapplication/AppController.coffee
+++ client/app/Applications/Viewer.kdapplication/AppController.coffee
@@ -13,8 +13,7 @@
         return cb true  unless path
         path = FSHelper.plainPath path
         publicPath = path.replace \
-          ///.*\/(.*\.#{KD.config.userSitesDomain})\/(.*)///, 'http://$1/$2'
-
+          ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"
         cb publicPath isnt path, {path: publicPath}

       failure    : (options, cb)->


client: home: billing: changed markup

client: home: billing: changed markup

--- client/home/lib/billing/components/paymentinformation/view.coffee
+++ client/home/lib/billing/components/paymentinformation/view.coffee
@@ -20,20 +20,20 @@

       <fieldset>
-        Full Name:
+        Full Name
         <input
           className={inputClass 'full-name'}
           value={@pickValue 'fullName'}
           onChange={@sendValue 'fullName'}
-          placeholder='Enter your name and surname' />
+          placeholder='Enter your name and surname...' />
       </fieldset>
       <fieldset className='email'>
-        E-mail Address:
+        E-mail Address
         <input
           className={inputClass 'email'}
           value={@pickValue 'email'}
           onChange={@sendValue 'email'}
-          placeholder='Enter your e-mail' />
+          placeholder='Enter your e-mail...' />
       </fieldset>
       <ActionBar
         onRemoveCard={@props.onRemoveCard}
@@ -53,8 +53,8 @@

       PAYMENT HISTORY

-
-      SAVE
-
+    <button className="GenericButton medium fr" href="#" onClick={onSave}>
+      SAVE
+    </button>
   </fieldset>


split conditional into two statements

split conditional into two statements

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -54,7 +54,8 @@

     skipOverExistingClosingBracket = false
     if @isClosingBracket(text) and nextCharacter is text and not hasEscapeCharacterBeforeCursor
-      if (bracketMarker = _.find(@bracketMarkers, (marker) -> marker.isValid() and marker.getBufferRange().end.isEqual(cursorBufferPosition))) or @getScopedSetting("bracket-matcher.alwaysSkipClosingPairs")
+      bracketMarker = _.find(@bracketMarkers, (marker) -> marker.isValid() and marker.getBufferRange().end.isEqual(cursorBufferPosition))
+      if bracketMarker or @getScopedSetting("bracket-matcher.alwaysSkipClosingPairs")
         skipOverExistingClosingBracket = true

     if skipOverExistingClosingBracket


Emit activated

Emit activated

--- lib/coffee-refactor.coffee
+++ lib/coffee-refactor.coffee
@@ -1,10 +1,12 @@
 Ripper = require './ripper'
 NotificationView = require './notification-view'
+{ packages: packageManager } = atom

-{ packages: packageManager } = atom

 module.exports =
   activate: ->
+    console.log 'coffee-refactor:activate'
+    atom.workspace.emit 'coffee-refactor-became-active'
     return if 'refactor' in packageManager.getAvailablePackageNames() and
               !packageManager.isPackageDisabled 'refactor'
     new NotificationView


fixes

fixes

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -6,7 +6,6 @@
 module.exports =
 class Console
   @activate: ->
-    # TODO: eval only in last editor
     @evalCmd = atom.commands.add '.ink-console atom-text-editor',
       'console:evaluate': ->
         ed = @getModel()
@@ -19,8 +18,7 @@
         @getModel().inkConsole.next()

   @deactivate: ->
-    @openCmd.dispose()
-    @clearCmd.dispose()
+    @evalCmd.dispose()

   constructor: ->
     @view.getModel = -> c


Don't set globals

Don't set globals

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -8,6 +8,7 @@
   process: null
   subscriptions: null
   numInstances: 0
+  editorMap: null

   config:
     checkOnSave:
@@ -27,6 +28,7 @@
     @subscriptions = new CompositeDisposable
     @process=null
     @numInstances=0
+    @editorMap = new WeakMap

     atom.views.addViewProvider HaskellGhcModMessage, (message)->
       el=new HaskellGhcModMessageElement
@@ -34,22 +36,23 @@
       el

     @subscriptions.add atom.commands.add 'atom-text-editor',
-      'haskell-ghc-mod:type': ->
-        @getModel().haskellGhcModController?.getType()
-      'haskell-ghc-mod:info': ->
-        @getModel().haskellGhcModController?.getInfo()
-      'haskell-ghc-mod:insert-type': ->
-        @getModel().haskellGhcModController?.insertType()
-      'haskell-ghc-mod:check': ->
-        @getModel().haskellGhcModController?.doCheck()
+      'haskell-ghc-mod:type': ({target}) =>
+        @editorMap.get(target.getModel())?.getType()
+      'haskell-ghc-mod:info': ({target}) =>
+        @editorMap.get(target.getModel())?.getInfo()
+      'haskell-ghc-mod:insert-type': ({target}) =>
+        @editorMap.get(target.getModel())?.insertType()
+      'haskell-ghc-mod:check': ({target}) =>
+        @editorMap.get(target.getModel())?.doCheck()

     @subscriptions.add atom.workspace.observeTextEditors (editor) =>
       return unless editor.getGrammar().scopeName=="source.haskell"
       @numInstances += 1
       @process = new GhcModiProcess unless @process
-      editor.haskellGhcModController = new EditorController(@process,editor)
+      @editorMap.set(editor,new EditorController(@process,editor))
       editor.onDidDestroy =>
-        editor.haskellGhcModController?.destroy()
+        @editorMap.get(editor)?.destroy()
+        @editorMap.delete(editor)
         @numInstances -= 1
         if @numInstances==0
           @process?.destroy()
@@ -57,6 +60,7 @@

   deactivate: ->
     for editor in atom.workspace.getEditors()
-      editor.haskellGhcModController?.destroy()
+      @editorMap.get(editor)?.desrtoy()
+      @editorMap.delete(editor)
     @subscriptions.dispose()
     @process?.destroy()


Prevent using enter key to submit

Prevent using enter key to submit

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -5,10 +5,8 @@
 #= require_tree .

 jQuery ->
-
   triggerAutosave = (e) ->
     window.autosave_timer ||= setTimeout( autosave, 15000 )
-

   # This is a very primitive way of testing.
   # Should be refactored once forms stabilize.
@@ -330,3 +328,12 @@
   # Remove alerts from registration page as soon as user starts typing
   $(".page-devise input").on 'keypress keydown keyup change', () ->
     $(this).closest(".question-body").find(".error").remove()
+
+  # Disable using enter key to submit on the form
+  $("form").on 'keypress', (e) ->
+    if e.keyCode == 13
+      target = $(e.target)
+      if !target.is("textarea") && !target.is(":button,:submit")
+        $(this).find(":input:visible:not([disabled],[readonly]), a").each () ->
+          return false;
+        return false;


fixes #762

fixes #762

--- app/assets/javascripts/invitations.js.coffee
+++ app/assets/javascripts/invitations.js.coffee
@@ -18,10 +18,20 @@
 Application.validateEmailsAndConfirm = (field) ->
   if $(field).is(":visible")
     emailList = parseEmails($(field).val())
-    if(emailList.length > 0)
-      return false unless confirm("#{emailList.length} invitations will be sent")
-      $(".recipients").val(emailList.toString())
+    if(emailList.length == 0)
+      addValidateEmailErrorMessageFor($(field))
+      return false
+    else
+      if confirm("#{emailList.length} invitations will be sent")
+        $(".recipients").val(emailList.toString())
+      else
+        event.preventDefault()
   true
+
+
+addValidateEmailErrorMessageFor = (field) ->
+  $(field).parent().addClass("error")
+  $(field).parent().find(".email-validation-help").show()

 hideValidateEmailErrorMessageFor = (field) ->
   $(field).parent().removeClass("error")


Removed todo

Removed todo

--- app/assets/javascripts/landingpage.js.coffee
+++ app/assets/javascripts/landingpage.js.coffee
@@ -14,7 +14,6 @@
       raw_points = $.parseJSON(data)
       setTimeout(insertNextPoint,100)

-  #TODO: Free Memory. Circles are only loaded. They arent removed jet
   insertNextPoint = ->
     if raw_points.length
       point = raw_points.pop()


fix for allowany middleware

fix for allowany middleware

--- lib/middle/allowany.coffee
+++ lib/middle/allowany.coffee
@@ -1,6 +1,8 @@
 module.exports = (req, res, next, config) ->
   if req.method is 'OPTIONS'
-    res.setHeader 'Access-Control-Allow-Origin', '*'
-    res.setHeader 'Access-Control-Allow-Headers', '*'
-    res.setHeader 'Access-Control-Allow-Methods', '*'
-  next()
+    res.writeHead 200,
+      'Access-Control-Allow-Origin': req.headers.origin,
+      'Access-Control-Allow-Headers': req.headers['access-control-request-headers'],
+      'Access-Control-Allow-Methods': req.headers['access-control-request-method']
+    return res.end()
+  next()


Fixes #408

Fixes #408

Open diff in a pending tab and replace it with other selected commits

--- lib/models/git-show.coffee
+++ lib/models/git-show.coffee
@@ -29,17 +29,22 @@
     if err then notifier.addError err else showFile objectHash

 showFile = (objectHash) ->
+  filePath = showCommitFilePath(objectHash)
   disposables = new CompositeDisposable
-  if atom.config.get('git-plus.openInPane')
-    splitDirection = atom.config.get('git-plus.splitPane')
-    atom.workspace.getActivePane()["split#{splitDirection}"]()
-  atom.workspace
-    .open(showCommitFilePath(objectHash), activatePane: true)
-    .then (textBuffer) ->
-      if textBuffer?
-        disposables.add textBuffer.onDidDestroy ->
-          disposables.dispose()
-          try fs.unlinkSync showCommitFilePath(objectHash)
+  editorForDiffs = atom.workspace.getPaneItems().filter((item) -> item.getURI().includes('.diff'))[0]
+  if editorForDiffs
+    editorForDiffs.setText fs.readFileSync(filePath, encoding: 'utf-8')
+  else
+    if atom.config.get('git-plus.openInPane')
+      splitDirection = atom.config.get('git-plus.splitPane')
+      atom.workspace.getActivePane()["split#{splitDirection}"]()
+    atom.workspace
+      .open(filePath, pending: true, activatePane: true)
+      .then (textBuffer) ->
+        if textBuffer?
+          disposables.add textBuffer.onDidDestroy ->
+            disposables.dispose()
+            try fs.unlinkSync filePath

 class InputView extends View
   @content: ->


Hide the commitsArea initially

Hide the commitsArea initially

--- lib/status-bar-view.coffee
+++ lib/status-bar-view.coffee
@@ -28,6 +28,7 @@
         @a href: '#', class: 'grammar-name inline-block', outlet: 'grammarName'

   initialize: (rootView, @pane) ->
+    @commitsArea.hide()
     @updatePathText()
     @subscribe @pane, 'pane:active-item-changed', =>
       @subscribeToBuffer()


Update appending to workspace view

Update appending to workspace view

--- lib/status-bar-view.coffee
+++ lib/status-bar-view.coffee
@@ -22,7 +22,7 @@

   # Private:
   attach: ->
-    atom.workspaceView.vertical.append(this) unless @hasParent()
+    atom.workspaceView.appendToBottom(this) unless @hasParent()

   # Public:
   appendLeft: (item) ->


Update oft panel of status bar to use new styles

--- lib/status-bar-view.coffee
+++ lib/status-bar-view.coffee
@@ -14,18 +14,18 @@
         @div class: 'git-branch inline-block', outlet: 'branchArea', =>
           @span class: 'icon icon-git-branch'
           @span class: 'branch-label', outlet: 'branchLabel'
-        @div class: 'git-commits inline-block', outlet: 'branchArea', =>
+        @div class: 'git-commits inline-block', outlet: 'commitsArea', =>
           @span class: 'icon icon-arrow-up commits-ahead-label', outlet: 'commitsAhead'
           @span class: 'icon icon-arrow-down commits-behind-label', outlet: 'commitsBehind'
         @div class: 'git-status inline-block', outlet: 'gitStatus', =>
           @span outlet: 'gitStatusIcon'

       @div class: 'status-bar-left', =>
-        @span class: 'file-info', =>
+        @div class: 'file-info inline-block', =>
           @span class: 'current-path', outlet: 'currentPath'
           @span class: 'buffer-modified', outlet: 'bufferModified'
-        @span class: 'cursor-position', outlet: 'cursorPosition'
-        @span class: 'grammar-name', outlet: 'grammarName'
+        @div class: 'cursor-position inline-block', outlet: 'cursorPosition'
+        @a href: '#', class: 'grammar-name inline-block', outlet: 'grammarName'

   initialize: (rootView, @pane) ->
     @updatePathText()
@@ -36,7 +36,7 @@
       @updatePathText()

     @subscribe @pane, 'cursor:moved', => @updateCursorPositionText()
-    @subscribe @grammarName, 'click', => @pane.activeView.trigger 'grammar-selector:show'
+    @subscribe @grammarName, 'click', => @pane.activeView.trigger('grammar-selector:show'); false
     @subscribe @pane, 'editor:grammar-changed', => @updateGrammarText()

     repo = project.getRepo()


More readable

More readable

--- core/app/backbone/views/sub_comments/sub_comments_add_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_add_view.coffee
@@ -59,11 +59,13 @@
     @submitting = true
     @ui.submit.prop('disabled',true ).text('Posting...')

+
 class window.SubCommentsAddView extends BaseSubCommentsAddView
   className: 'evidence-sub-comments-form'
   template: 'sub_comments/add_view'

   templateHelpers: => current_user: currentUser.toJSON()
+

 class window.NDPSubCommentsAddView extends BaseSubCommentsAddView
   className: 'ndp-evidenceish-content'


Better fix for missing array part type.

Better fix for missing array part type.

--- packages/landsofillusions/character/part/properties/array.coffee
+++ packages/landsofillusions/character/part/properties/array.coffee
@@ -47,8 +47,12 @@
         partData = fields[fieldKey]
         part = @_partsByOrder[dotFieldKey]

+        unless partData.type
+          console.warn "Array field #{dotFieldKey} is missing type."
+          continue
+
         # If we have the part already, make sure it's of the correct type.
-        unless part?.options.type is partData.type or not partData.type
+        unless part?.options.type is partData.type
           Tracker.nonreactive =>
             partDataLocation = @options.dataLocation.child fieldKey


Fix label helper for env vars form

Fix label helper for env vars form

--- assets/scripts/app/helpers/handlebars.coffee
+++ assets/scripts/app/helpers/handlebars.coffee
@@ -48,6 +48,8 @@
     options.hash.for = id
     options.hashTypes.for = 'STRING'
     options.hashContexts.for = this
+    if options.hash.content
+      options.fn = Ember.Handlebars.compile("{{view.content}}")

   Ember.Handlebars.helpers.view.call(this, view, options)
 )


use singles except when ruby requires doubles

--- app/assets/javascripts/components/group-browser.cjsx
+++ app/assets/javascripts/components/group-browser.cjsx
@@ -10,7 +10,7 @@
     groups:[]

   componentDidMount:->
-    API.type("groups").get(project_id: @props.project.id).then (groups)=>
+    API.type('groups').get(project_id: @props.project.id).then (groups)=>
       group.showButtons = false for group in groups  # hide buttons by default
       @setState groups: groups

@@ -36,21 +36,21 @@
       buttonContainerClasses = []
       groupNameClasses = []
       if group.showButtons
-        buttonContainerClasses.push "active"
+        buttonContainerClasses.push 'active'
       else
-        groupNameClasses.push "active"
+        groupNameClasses.push 'active'

       display_groups.push(

-

-
+

+

-

-

+

+

                   {group.meta_data.start_year} ‒ {group.meta_data.end_year} ‧ {group.name}

-

+

@@ -62,11 +62,11 @@
     return null if @state.groups.length <= 1
     groups = @renderGroups()

-

-
Browse 90 years of programs to see what's available to mark or transcribe:

+

+
Browse 90 years of programs to see what's available to mark or transcribe:

       {groups}

-
+

 module.exports = GroupBrowser


fix javascript tips error

fix javascript tips error

--- app/assets/javascripts/views/activity_view.js.coffee
+++ app/assets/javascripts/views/activity_view.js.coffee
@@ -10,9 +10,9 @@
   initialize: (options)->
     @listenTo(@model, 'change', @render)
     @subjectId = options.subjectId
-    @subviews = {
-      '.js-tips': new TipsView(tips: @model.get('subject').tips)
-    }
+    @subviews = {}
+    if subject = @model.get('subject')
+      @subviews['.js-tips'] = new TipsView(tips: subject.tips) if subject.tips

   render: =>
     @$el.html(JST[@model.get('type')].render(@templateData()))


popover html true

popover html true

--- app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
+++ app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
@@ -3,7 +3,7 @@

 window.initPopover = ->
   $('.popover').hide()
-  $('a[rel=popover]').popover({trigger: 'hover'})
+  $('a[rel=popover]').popover({trigger: 'hover', html: true})

 window.initAcFileds = ->
   for el in $('.ac_field')


Removed unnecessary comments

Removed unnecessary comments

--- lib/table-formatter.coffee
+++ lib/table-formatter.coffee
@@ -3,7 +3,6 @@
 module.exports =
 class TableFormatter
     subscriptions: []
-    #regex: /((.+?)\|)+?(.+)?\r?\n(([:\-\|]+?)\|)+?([:\-\|]+)?[ ]*(\r?\n((.+?)\|)+?(.+)?)+/mg

     constructor: ->
       @subscriptions = new CompositeDisposable
@@ -34,29 +33,18 @@
         @subscriptions.dispose()

     format: (editor,force) ->
-        # console.log(editor.getGrammar().scopeName)
         if editor.getGrammar().scopeName != 'source.gfm'
-            # console.log('exiting')
             return

         selectionsRanges = editor.getSelectedBufferRanges()
-        # console.log(selectionsRanges[0].isEmpty())
-        # console.log(@autoSelectEntireDocument)

         if force or (selectionsRanges[0].isEmpty() and @autoSelectEntireDocument)
-            # console.log('all selected')
             selectionsRanges = [editor.getBuffer().getRange()]

-        # console.log('myIterator')
         myIterator = (obj) =>
-            # console.log('iterating')
             obj.replace(@formatTable(obj.match))

         for range in selectionsRanges
-            # console.log(@regex)
-            # console.log(range)
-            # console.log(myIterator)
-
             editor.backwardsScanInBufferRange(@regex, range, myIterator)

     formatTable: (text) ->
@@ -138,7 +126,6 @@
         for i in [0..columns-1]
            newtext = justify[i][0] + '-'.repeat((widths[i]-2)) + justify[i][justify[i].length-1]
            formattedformatline.push(newtext)
-        #formatline = '|' + formattedformatline.join('|') + '|'

         if @keepFirstAndLastPipes
             formatline = '|' + formattedformatline.join('|') + '|'


Hack around unset document in Box's layout computations (#6352)

--- bokehjs/src/coffee/models/layouts/box.coffee
+++ bokehjs/src/coffee/models/layouts/box.coffee
@@ -312,7 +312,12 @@
   # once with horizontal=true and once with horizontal=false)
   _align_inner_cell_edges_constraints: () ->
     constraints = []
-    if @ in @document.roots()
+
+    # XXX: checking for `@document?` is a temporary hack, because document isn't always
+    # attached properly. However, if document is not attached then we know it can't be
+    # a root, because otherwise add_root() would attach it. All this layout logic should
+    # be part of views instead of models and use is_root, etc.
+    if @document? and @ in @document.roots()
       flattened = @_flatten_cell_edge_variables(@_horizontal)
       for key, variables of flattened
         if variables.length > 1


updated arc.coffee to use new vectorized ops

--- bokehjs/src/coffee/renderer/glyph/arc.coffee
+++ bokehjs/src/coffee/renderer/glyph/arc.coffee
@@ -39,6 +39,36 @@
         else if dir == 'anticlock' then @direction[i] = true
         else @direction[i] = NaN

+
+    set_data: (request_render=true) ->
+      source = @mget_obj('data_source')
+      if source.type == 'ColumnDataSource'
+        @x = @glyph_props.source_v_select('x', source)
+        @y = @glyph_props.source_v_select('y', source)
+        start_angles = @glyph_props.source_v_select('start_angle', source)
+        @start_angle = (-angle for angle in start_angles)
+        end_angles = @glyph_props.source_v_select('end_angle', source)
+        @end_angle = (-angle for angle in end_angles)
+
+        @direction = new Uint8Array(source.get_length())
+        for i in [0..@direction.length-1]
+          #FIXME
+          dir = @glyph_props.select('direction', data[i])
+          if dir == 'clock' then @direction[i] = false
+          else if dir == 'anticlock' then @direction[i] = true
+          else @direction[i] = NaN
+
+
+        @mask = new Uint8Array(@x.length)
+        @selected_mask = new Uint8Array(@x.length)
+        for i in [0..@mask.length-1]
+          @mask[i] = true
+          @selected_mask[i] = false
+        @have_new_data = true
+
+      if request_render
+        @request_render()
+
     _render: () ->
       [@sx, @sy] = @plot_view.map_to_screen(@x, @glyph_props.x.units, @y, @glyph_props.y.units)
       @radius = @distance(@data, 'x', 'radius', 'edge')
@@ -46,33 +76,22 @@
       ctx = @plot_view.ctx

       ctx.save()
-      if @glyph_props.fast_path
-        @_fast_path(ctx)
-      else
-        @_full_path(ctx)
+      @_full_path(ctx)
       ctx.restore()

-    _fast_path: (ctx) ->
+    _full_path: (ctx) ->
       if @do_stroke
-        @glyph_props.line_properties.set(ctx, @glyph_props)
+        source = @mget_obj('data_source')
+        glyph_props.line_properties.set_prop_cache(source)
+        ctx.beginPath()
         for i in [0..@sx.length-1]
           if isNaN(@sx[i] + @sy[i] + @radius[i] + @start_angle[i] + @end_angle[i] + @direction[i])
             continue
-          ctx.beginPath()
+          if glyph_props.line_properties.set_vectorize(ctx, i)
+            ctx.stroke()
+            ctx.beginPath()
           ctx.arc(@sx[i], @sy[i], @radius[i], @start_angle[i], @end_angle[i], @direction[i])
-          ctx.stroke()
-
-    _full_path: (ctx) ->
-      if @do_stroke
-        for i in [0..@sx.length-1]
-          if isNaN(@sx[i] + @sy[i] + @radius[i] + @start_angle[i] + @end_angle[i] + @direction[i])
-            continue
-
-          ctx.beginPath()
-          ctx.arc(@sx[i], @sy[i], @radius[i], @start_angle[i], @end_angle[i], @direction[i])
-
-          @glyph_props.line_properties.set(ctx, @data[i])
-          ctx.stroke()
+        ctx.stroke()

     draw_legend: (ctx, x1, x2, y1, y2) ->
       glyph_props = @glyph_props


fix download cog item in new files

fix download cog item in new files

test plan:
 - enable "better file browsing"
 - confirm that the "download" item at the top of the cog menu
   for files and folders is operational

fixes CNVS-16287

Change-Id: Iac3e394bcbdfec8a7905d435f7c4f9b0e5787402
Reviewed-on: https://gerrit.instructure.com/43216
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/react_files/components/ItemCog.coffee
+++ app/coffeescripts/react_files/components/ItemCog.coffee
@@ -44,9 +44,10 @@
             a (if @props.model instanceof Folder
               href: '#'
               onClick: wrap(downloadStuffAsAZip)
+              ref: 'download'
             else
               href: @props.model.get('url')
-            ref: 'download'
+              ref: 'download'
             ),
               I18n.t('download', 'Download')
           if @props.userCanManageFilesForContext


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -6,6 +6,8 @@
 App.height = 480

 test "shouldn't remove object inside activeBounds", ->
+  log App
+
   obj = GameObject
     x: 50
     y: 50


Passes me to login event

Passes me to login event

--- src/hull.api.coffee
+++ src/hull.api.coffee
@@ -28,7 +28,7 @@
               console.info "Authentication is in progress. Use `Hull.on('hull.auth.login', fn)` to call `fn` when done."
               return
             api.auth.login(args...).then ()->
-              api.api('me').then ()-> _emitter.emit('hull.auth.login')
+              api.api('me').then (me)-> _emitter.emit('hull.auth.login', me)
             , (err)->
               _emitter.emit 'hull.auth.fail', err
           logout: api.auth.logout


Fix anchor seeking on non-hash URLs (#348)

--- app/assets/javascripts/hub.coffee
+++ app/assets/javascripts/hub.coffee
@@ -17,9 +17,11 @@
   #-----------------------
   if window.location.hash
     hash = window.location.hash.substring(1)
-    $('html, body').animate {
-      scrollTop: ($('a[name="'+hash+'"]').offset().top) + 15
-    }, 0
+    target = $('a[name="'+hash+'"]')
+    if target[0]
+      $('html, body').animate {
+        scrollTop: (target.offset().top) + 15
+      }, 0

   #-----------------------
@@ -50,7 +52,7 @@

   if $('.search__input')[0]
     if gon.algoliatoken
-      #Doc Search
+#Doc Search
       docsearch
         apiKey: gon.algoliatoken
         indexName: 'minio'
@@ -68,8 +70,8 @@
     e.preventDefault()
     $(this).parent().append '
'
     $(this)
-      .closest('.dropdown')
-      .addClass 'dropdown--open'
+    .closest('.dropdown')
+    .addClass 'dropdown--open'
     return

   # Close
@@ -97,10 +99,10 @@
   $body.on 'click', '.content *:not("a") > img:not(".img-preview__img")', ->
     $imgSrc = $(this).attr('src')
     $imgWrap =  '
' +
-                  '

' +
-                  '' +
-                  '' +
-                '
'
+      '

' +
+      '' +
+      '' +
+      '
'
     $body.append $imgWrap
     return


set player's pin image to marker

set player's pin image to marker

--- app/assets/javascripts/map.coffee
+++ app/assets/javascripts/map.coffee
@@ -37,7 +37,7 @@
               position: LL(s.lat, s.lon)
               map: that.map
               title: s.nickname
-              icon: s.image
+              icon: location.origin + "/users/#{s.id}/picture"
               zIndex: 10000 - i
               animation: google.maps.Animation.DROP


Ensure we sent groupings correctly, and don't overload in case of slow LOAD info

--- src/components/Throttle.coffee
+++ src/components/Throttle.coffee
@@ -12,6 +12,7 @@
       out: new noflo.Port()

     @q = []
+    @groups = []
     @load = 0
     @max = 10

@@ -24,25 +25,34 @@
       @process()

     @inPorts.in.on "begingroup", (group) =>
-      @push "begingroup", group
+      @groups.push group
     @inPorts.in.on "data", (data) =>
       @push "data", data
     @inPorts.in.on "endgroup", =>
-      @push "endgroup"
+      @groups.pop()
     @inPorts.in.on "disconnect", =>
       @push "disconnect"

   push: (eventname, data) ->
-    @q.push { name: eventname, data: data }
+    @q.push
+      name: eventname
+      data: data
+      groups: @groups.slice 0
     @process()

   process: ->
+    sent = 0
     while @q.length > 0 and @load < @max
+      return if sent > @max
       event = @q.shift()
       switch event.name
-        when "begingroup" then @outPorts.out.beginGroup event.data
-        when "data" then @outPorts.out.send event.data
-        when "endgroup" then @outPorts.out.endGroup()
+        when "data"
+          for group in event.groups
+            @outPorts.out.beginGroup group
+          @outPorts.out.send event.data
+          for group in event.groups
+            @outPorts.out.endGroup()
         when "disconnect" then @outPorts.out.disconnect()
+      sent++

 exports.getComponent = -> new Throttle()


add_sp_ra_delete_tips


Fixed: email fixtures are now sorted

Fixed: email fixtures are now sorted

--- client/app/stores/EmailStore.coffee
+++ client/app/stores/EmailStore.coffee
@@ -8,6 +8,7 @@

 # Used in production instead of real data during development early stage
 fixtures = require '../../../tests/fixtures/emails.json'
+fixtures = fixtures.concat require '../../../tests/fixtures/emails_generated.json'
 _idGenerator = 0

 class EmailStore extends Store
@@ -20,6 +21,15 @@
     # Loads from fixtures if necessary
     if not window.mailboxes? or window.mailboxes.length is 0
         emails = fixtures
+        # emails are sorted server-side so we manually sort them
+        # if we use fixtures
+        emails.sort (e1, e2) ->
+            if e1.createdAt < e2.createdAt
+                return 1
+            else if e1.createdAt > e2.createdAt
+                return -1
+            else
+                return 0
     else
         emails = []


Dev: prevent local New Relic error.

Dev: prevent local New Relic error.

/cc @kenperkins -- okay with you, at least for now?

--- app._coffee
+++ app._coffee
@@ -1,7 +1,8 @@
 # DO NOT REMOVE
-#
-# require 'newrelic' is needed to bootstrap instrumentation into zoomhub
-require 'newrelic'
+# This is needed to bootstrap instrumentation into zoomhub.
+# But it causes an error if not configured (e.g. locally), so check.
+if process.env['NEW_RELIC_APP_NAME']
+    require 'newrelic'

 config = require './config'
 express = require 'express-streamline'


Updated: Atom config:  atom-beautify _analyticsUserId

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -1,6 +1,7 @@
 "*":
   "atom-beautify":
-    _analyticsUserId: "c8ae3b6d-a781-4233-9780-a96292c2afa8"
+    general:
+      _analyticsUserId: "c8ae3b6d-a781-4233-9780-a96292c2afa8"
   core:
     customFileTypes:
       "source.ini": [


Reach the outputView from DefineStackView

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -120,7 +120,7 @@

   handleSave: ->

-    @outputView.clear().raise()
+    @stackTemplateView.outputView.clear().raise()

     @cancelButton.setTitle 'Cancel'
     @setAsDefaultButton.hide()
@@ -128,28 +128,28 @@
     @checkAndBootstrapCredentials (err, credentials) =>
       return  @saveButton.hideLoader()  if err

-      @outputView
+      @stackTemplateView.outputView
         .add 'Credentials are ready!'
         .add 'Saving current template content...'

       @saveTemplate (err, stackTemplate) =>

-        if @outputView.handleError err
+        if @stackTemplateView.outputView.handleError err
           @saveButton.hideLoader()
           return

-        @outputView.add 'Template content saved now processing the template...'
+        @stackTemplateView.outputView.add 'Template content saved now processing the template...'

         @handleCheckTemplate { stackTemplate }, (err, machines) =>

           @saveButton.hideLoader()

           if err
-            @outputView.add "Parsing failed, please check your
+            @stackTemplateView.outputView.add "Parsing failed, please check your
                              template and try again"
             return

-          @outputView.add "You can now close this window, or set this
+          @stackTemplateView.outputView.add "You can now close this window, or set this
                            template as default for your team members."

           @cancelButton.setTitle 'Close'
@@ -162,33 +162,33 @@
     [credential]      = credentialsData

     failed = (err) =>
-      @outputView.handleError err
+      @stackTemplateView.outputView.handleError err
       callback err

     showCredentialContent = (credential) =>
       credential.fetchData (err, data) =>
         return failed err  if err
-        @outputView.add JSON.stringify data.meta, null, 2
+        @stackTemplateView.outputView.add JSON.stringify data.meta, null, 2
         callback null, [credential]

-    @outputView
+    @stackTemplateView.outputView
       .add 'Verifying credentials...'
       .add 'Bootstrap check initiated for credentials...'

     credential.isBootstrapped (err, state) =>
+
       return failed err  if err

       if state

-        @outputView.add 'Already bootstrapped, fetching data...'
+        @stackTemplateView.outputView.add 'Already bootstrapped, fetching data...'
         showCredentialContent credential

       else

-        @outputView.add 'Bootstrap required, initiating to bootstrap...'
-
-        identifiers = [credential.identifier]
-
+        @stackTemplateView.outputView.add 'Bootstrap required, initiating to bootstrap...'
+
+        identifiers           = [credential.identifier]
         { computeController } = kd.singletons

         computeController.getKloud()
@@ -198,10 +198,10 @@
           .then (response) =>

             if response
-              @outputView.add 'Bootstrap completed successfully'
+              @stackTemplateView.outputView.add 'Bootstrap completed successfully'
               showCredentialContent credential
             else
-              @outputView.add 'Bootstrapping completed but something went wrong.'
+              @stackTemplateView.outputView.add 'Bootstrapping completed but something went wrong.'
               callback null

             console.log '[KLOUD:Bootstrap]', response
@@ -224,7 +224,7 @@
         console.log '[KLOUD:checkTemplate]', err, response

         if err or not response
-          @outputView
+          @stackTemplateView.outputView
             .add 'Something went wrong with the template:'
             .add err?.message or 'No response from Kloud'

@@ -234,7 +234,7 @@

           machines = parseTerraformOutput response

-          @outputView
+          @stackTemplateView.outputView
             .add 'Template check complete succesfully'
             .add 'Following machines will be created:'
             .add JSON.stringify machines, null, 2
@@ -256,20 +256,20 @@
     # and call them in here ~ GG

     # Parsing credential requirements
-    @outputView.add 'Parsing template for credential requirements...'
+    @stackTemplateView.outputView.add 'Parsing template for credential requirements...'

     requiredProviders = providersParser templateContent

-    @outputView
+    @stackTemplateView.outputView
       .add 'Following credentials are required:'
       .add '-', requiredProviders

     # Parsing additional requirements, like user/group authentications
-    @outputView.add 'Parsing template for additional requirements...'
+    @stackTemplateView.outputView.add 'Parsing template for additional requirements...'

     requiredData = requirementsParser templateContent

-    @outputView
+    @stackTemplateView.outputView
       .add 'Following extra information will be requested from members:'
       .add requiredData

@@ -323,7 +323,7 @@

   handlePreview: ->

-    template      = @editorView.getValue()
+    template      = @stackTemplateView.editorView.getValue()

     group         = kd.singletons.groupsController.getCurrentGroup()
     account       = whoami()
@@ -399,10 +399,10 @@
       return new kd.NotificationView
         title: 'Setting stack template for koding is disabled'

-    @outputView.add 'Setting this as default group stack template...'
+    @stackTemplateView.outputView.add 'Setting this as default group stack template...'

     currentGroup.modify stackTemplates: [ stackTemplate._id ], (err) =>
-      return if @outputView.handleError err
+      return if @stackTemplateView.outputView.handleError err

       new kd.NotificationView
         title : "Group (#{slug}) stack has been saved!"


Referral System: fixed a big issue on referral system

--- client/landing/Intro/views/registerinlineform.coffee
+++ client/landing/Intro/views/registerinlineform.coffee
@@ -112,7 +112,7 @@
   doRegister:(formData)->
     (KD.getSingleton 'mainController').isLoggingIn on
     formData.agree = 'on'
-    formData.referrer = $.cookie 'refererer'
+    formData.referrer = $.cookie 'referrer'
     # we need to close the group channel so we don't receive the cycleChannel event.
     # getting the cycleChannel even for our own MemberAdded can cause a race condition
     # that'll leak a guest account.


landing: move isLoggedIn check to utils

landing: move isLoggedIn check to utils

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -20,6 +20,8 @@

 module.exports = utils = {
+
+  isLoggedIn: -> kd.userAccount

   clearKiteCaches: ->


Wrap and set finder options

Wrap and set finder options

--- client/skr/screens/customer-maint/CustomerMaint.cjsx
+++ client/skr/screens/customer-maint/CustomerMaint.cjsx
@@ -10,11 +10,12 @@
     modelForAccess: 'customer'

     render: ->
-        <div className="customer-maint">
+        <LC.ScreenWrapper identifier="customer-maint">
             <Lanes.Screens.CommonComponents
                 activity={@state} commands={@state.commands} model={@customer} />
             <BS.Row>
-                <Skr.Components.CustomerFinder sm=4 editOnly ref="finder"
+                <SC.CustomerFinder sm=4 editOnly ref="finder"
+                    syncOptions={include: ['billing_address', 'shipping_address']}
                     commands={@state.commands} customer={@customer} />
                 <LC.Input sm=8 name="name" model={@customer} />
             </BS.Row>
@@ -25,24 +26,17 @@
                     model={@customer} />
             </BS.Row>
             <BS.Row>
-                <LC.SelectField sm=6
-                    label="Receivables Account"
-                    name="gl_receivables_account"
-                    labelField="combined_name"
-                    model={@customer} />
-                <LC.SelectField sm=6
-                    label="Payment Terms"
-                    name="terms"
-                    labelField="code"
-                    model={@customer} />
+                <SC.GlAccountChooser sm=6 model={@customer}
+                    label="Receivables Account" name="gl_receivables_account"/>
+                <SC.TermsChooser model={@customer} />
             </BS.Row>
             <BS.Row>
                 <LC.FieldSet sm=12 title="Address">
-                    <Skr.Components.Address lg=6 title="Billing"
+                    <SC.Address lg=6 title="Billing"
                         model={@customer.billing_address}  />
-                    <Skr.Components.Address lg=6 title="Shipping"
+                    <SC.Address lg=6 title="Shipping"
                         model={@customer.shipping_address} />
                 </LC.FieldSet>
             </BS.Row>

-        </div>
+        </LC.ScreenWrapper>


Fix advanced menu

Fix advanced menu

--- src/js/menus.coffee
+++ src/js/menus.coffee
@@ -479,7 +479,6 @@
               @myShowsMenu.show()

         when 'advanced'
-          @advancedMenu = new Advanced()
           @advancedMenu.show()

   show: ->
@@ -498,7 +497,7 @@
             action: -> trakttv.fetchToWatchList()
           }, {
             title: 'Reset local data'
-            action: ->
+            action: =>
               localStorage.clear()
               @initSettings()


modified

modified

--- src/jsonsina.coffee
+++ src/jsonsina.coffee
@@ -36,7 +36,11 @@

   host = "http://money.finance.sina.com.cn/quotes_service/api/json_v2.php"
   #host = "http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php"
-  c = recode param.symbol, 1
+  try
+    c = recode param.symbol, 1
+  catch error
+    callback "jsonsina >> history: #{error}", null
+
   t = scale[param.type]
   l = param.len ? 0
   url = "#{host}/CN_MarketData.getKLineData?symbol=#{c}&scale=#{t}&datalen=#{l}"


address code review comments

address code review comments

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -92,8 +92,6 @@
     @subjectViewer.setClassification @classification
     @animalSelector.setClassification @classification

-    AnalyticsLogger.logEvent 'view'
-
     if !!subject.metadata.tutorial
       @tutorial.start()
     else
@@ -114,6 +112,7 @@

   activate: ->
     super
+    AnalyticsLogger.logEvent 'view'
     setTimeout => @tutorial.dialog.attach()

 module.exports = Classifier


Fix QD and Talk tutorial text

Fix QD and Talk tutorial text

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -334,7 +334,6 @@
         firstStep: 'dashboard'
         steps: TutorialDashboard
         parent: @el[0]
-      tutorial.start()

     # Prompt Talk message
     if nClassified is 7
@@ -343,6 +342,11 @@
         firstStep: 'talk'
         steps: TutorialTalk
         parent: @el[0]
+
+    if tutorial?
+      tutorial.el.on 'start-tutorial enter-tutorial-step', =>
+        translate.refresh tutorial.el.get 0
+
       tutorial.start()

   onNoMoreSubjects: ->


display comment date

display comment date

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -726,7 +726,7 @@
       commentsContainer.append """

#{comment.body}

-
by #{comment.user_name} X minutes ago

+
by #{comment.user_name} #{comment.created_at}

       """


adding nsfw support

adding nsfw support

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -9,6 +9,7 @@
 #
 # Commands:
 #   hubot offshore team
+#   hubot sachin nsfw
 #
 # Author:
 #   Mateusz Kopij <sagasu>
@@ -16,12 +17,16 @@

 offshorepics = [
   "https://github.com/sagasu/offshore-team/raw/master/img/OffshoreTeam2.jpg",
-  "https://github.com/sagasu/offshore-team/raw/master/img/vicTeam.png"
+  "https://github.com/sagasu/offshore-team/raw/master/img/vicTeam.png",
+  "https://github.com/sagasu/offshore-team/raw/master/img/offshore_gone_wrong.png"
 ]

 module.exports = (robot) ->

   robot.respond /offshore team/i, (msg)->
     msg.send msg.random offshorepics
+
+  robot.respond /sachin nsfw/i, (msg)->
+    msg.send "https://github.com/sagasu/offshore-team/raw/master/img/offshore_gone_wrong.png"


made options explicit, removed comments

made options explicit, removed comments

--- core/app/backbone/factlink/make_tooltip_for_view.coffee
+++ core/app/backbone/factlink/make_tooltip_for_view.coffee
@@ -1,30 +1,18 @@
 Backbone.Factlink ||= {}

-#This function registers a tooltip handler for a marionette
-#view.  Whenever a user hover(Intent)s over an element with
-#the given selector, a new tooltip is filled with a view
-#created by the given callback.
+Backbone.Factlink.makeTooltipForView = (options) ->
+  _.defaults options, $offsetParent: options.parentView.$el
+  _.defaults options.positioning, fadeTime: 100

-#options:
-#  parentView: the marionette view owning this tooltip
-#  tooltipViewFactory: -> called to created a view on hover
-#  selector: selector identifying what can be hovered over.
-#  $offsetParent: a dom node within which to position the
-#    tooltip with respect to the target (hovered) node.
-#    If unspecified, uses parentView.$el.
-
-Backbone.Factlink.makeTooltipForView = (options) ->
   positionedRegion =
-    new Backbone.Factlink.PositionedRegion _.defaults options.positioning,
-      fadeTime: 100
+    new Backbone.Factlink.PositionedRegion options.positioning

   makeTooltip = ($el, $target) ->
     popoverOptions = _.defaults
       contentView: options.tooltipViewFactory(),
       options.positioning

-    positionedRegion.bindToElement $target,
-      options.$offsetParent || $el
+    positionedRegion.bindToElement $target, options.$offsetParent
     positionedRegion.show new PopoverView popoverOptions
     positionedRegion.updatePosition()
     positionedRegion.$el


Removed unused prop

Removed unused prop

--- core/app/backbone/factlink/react_text_input_view.coffee
+++ core/app/backbone/factlink/react_text_input_view.coffee
@@ -2,7 +2,7 @@
 Backbone.Factlink.ReactTextInputView = React.createBackboneClass

   getInitialState: ->
-    text: @props.defaultValue
+    text: ''

   focusInput: ->
     @refs.input.getDOMNode().focus()


Add avatar to scores page

Add avatar to scores page

Directly list all user scores when there is only 1 user for a team.

--- client.coffee
+++ client.coffee
@@ -275,12 +275,17 @@
 					Dom.style Flex: 1, fontSize: '100%', paddingLeft: '80px'
 					Dom.text "Team " + teamName + " scored " + teamScore + " points"
 					# To Do expand voor scores
-					if expanded.get()
+					if expanded.get() || team.count('users').get() <= 1
 						team.iterate 'users', (user) !->
 							Dom.div !->
 								Dom.style clear: 'both'
+								Ui.avatar Plugin.userAvatar(user.key()),
+									style: margin: '6px 10px 0 0', float: 'left'
+									size: 40
+									onTap: (!-> Plugin.userInfo(user.key()))
 								Dom.div !->
-									Dom.style fontSize: '75%', marginTop: '6px', marginRight: '6px', display: 'block', float: 'left'
+									Dom.br()
+									Dom.style fontSize: '75%', marginTop: '6px', marginRight: '6px', display: 'block', float: 'left', minWidth: '75px'
 									Dom.text Plugin.userName(user.n) + " has: "
 								Dom.div !->
 									Dom.style fontSize: '75%', marginTop: '6px', display: 'block', float: 'left'
@@ -294,8 +299,9 @@
 						Dom.div !->
 							Dom.style fontSize: '75%', marginTop: '6px'
 							Dom.text "Tap for details"
-				Dom.onTap !->
-					expanded.set(!expanded.get())
+				if team.count('users').get() > 1
+					Dom.onTap !->
+						expanded.set(!expanded.get())
 		, (team) -> [(-team.get('teamScore')), team.get('name')]

 logContent = ->


Removed FactlinkConfig

Removed FactlinkConfig

--- app/js/publisher_poc/nu.nl.coffee
+++ app/js/publisher_poc/nu.nl.coffee
@@ -6,23 +6,9 @@
   */
 (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("$script",this,function(){function v(e,t){for(var n=0,r=e.length;n<r;++n)if(!t(e[n]))return f;return 1}function m(e,t){v(e,function(e){return!t(e)})}function g(e,t,a){function d(e){return e.call?e():r[e]}function b(){if(!--p){r[h]=1,c&&c();for(var e in s)v(e.split("|"),d)&&!m(s[e],d)&&(s[e]=[])}}e=e[l]?e:[e];var f=t&&t.call,c=f?t:a,h=f?e.join(""):t,p=e.length;return setTimeout(function(){m(e,function(e){if(e===null)return b();if(u[e])return h&&(i[h]=1),u[e]==2&&b();u[e]=1,h&&(i[h]=1),y(!n.test(e)&&o?o+e+".js":e,b)})},0),g}function y(n,r){var i=e.createElement("script"),s=f;i.onload=i.onerror=i[d]=function(){if(i[h]&&!/^c|loade/.test(i[h])||s)return;i.onload=i[d]=null,s=1,u[n]=2,r()},i.async=1,i.src=n,t.insertBefore(i,t.firstChild)}var e=document,t=e.getElementsByTagName("head")[0],n=/^https?:\/\//,r={},i={},s={},o,u={},a="string",f=!1,l="push",c="DOMContentLoaded",h="readyState",p="addEventListener",d="onreadystatechange";return!e[h]&&e[p]&&(e[p](c,function b(){e.removeEventListener(c,b,f),e[h]="complete"},f),e[h]="loading"),g.get=y,g.order=function(e,t,n){(function r(i){i=e.shift(),e.length?g(i,r):g(i,t,n)})()},g.path=function(e){o=e},g.ready=function(e,t,n){e=e[l]?e:[e];var i=[];return!m(e,function(e){r[e]||i[l](e)})&&v(e,function(e){return r[e]})?t():!function(e){s[e]=s[e]||[],s[e][l](t),n&&n(i)}(e.join("|")),g},g.done=function(e){g([null],e)},g})
 `
-localConfig =
-  api: "http://localhost:3000"
-  lib: "http://localhost:8000/lib/dist"
-  srcPath: "/factlink.core.js"
-  url: window.location.href
-
-remoteConfig =
-  api: "https://factlink-testserver.inverselink.com:443"
-  lib: "https://factlink-static-testserver.inverselink.com:443/lib/dist"
-  srcPath: "/factlink.core.min.js"
-  url: window.location.href
-
-window.FactlinkConfig = localConfig
-
 $script(['//code.jquery.com/jquery-1.10.2.min.js',
          '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js',
-         "#{window.FactlinkConfig.lib}/factlink_loader_basic.js"
+         "#{window.FactlinkConfig.lib}/factlink_loader_basic.#{window.FactlinkConfig.minified}js"
          ], 'myBundle')

 $script.ready 'myBundle', ->


Invalidate Auth token on HTTP 401 erro

Invalidate Auth token on HTTP 401 erro

--- app/src/api/authentication.coffee
+++ app/src/api/authentication.coffee
@@ -31,7 +31,7 @@
   # Tries to recover from an usafe call (i.e. a call performed without being sure that the current auth token is valid)
   _recoverUnsafeCallFailure: (request, deferred, error) ->
     [jqXHR, textStatus, errorThrown] = error
-    if jqXHR.statusCode() is 403
+    if jqXHR.statusCode() is 403 or jqXHR.statusCode() is 401
       @_authenticateAndPerfomSafeCall(request, deferred)
     else
       @_reportFailure(request, deferred, error)


fix screenshot reporter

fix screenshot reporter

--- angular/test/protractor.coffee
+++ angular/test/protractor.coffee
@@ -1,7 +1,8 @@
 HtmlScreenshotReporter = require('protractor-jasmine2-screenshot-reporter');

 reporter = new HtmlScreenshotReporter
-  dest: 'screenshots',
+  dest: 'screenshots'
+  captureOnlyFailedSpecs: true
   filename: 'my-report.html'

 paths         = require('../tasks/paths')
@@ -24,7 +25,7 @@
       reporter.beforeLaunch(resolve);
   #
   onPrepare: ->
-    # jasmine.getEnv().addReporter(reporter);
+    jasmine.getEnv().addReporter(reporter);
     browser.driver.manage().window().setSize(1680, 1024)
   #
   afterLaunch: (exitCode) ->


Deploy state constants

Deploy state constants

--- SingularityUI/app/constants.coffee
+++ SingularityUI/app/constants.coffee
@@ -51,4 +51,12 @@
         DRIVER_RUNNING: 'running'
         DRIVER_STOPPED: 'stopped'

+    deployStates:
+        SUCCEEDED: 'Succeeded'
+        FAILED_INTERNAL_STATE: 'Failed internal state'
+        WAITING: 'Waiting'
+        OVERDUE: 'Overdue'
+        FAILED: 'Failed'
+        CANCELED: 'Canceled'
+
 module.exports = constants


Module specified id and unique documents

Use id specified by modules for events and groups. Specify id when
indexing events and groups to prevent duplicates.

--- app/db.coffee
+++ app/db.coffee
@@ -1,5 +1,7 @@
 ###
-Elasticsearch package and client object
+db is a database module that handles storage and retrieval of events and groups.
+It uses ElasticSearch as a database backend and the elasticsearch JavaScript
+client.

 @author Snorre Davøen
 ###
@@ -9,7 +11,28 @@
 	log: 'trace'
 	apiVersion: '1.5'

-createIndex = (name) ->
+
+# Add ID mapping to index
+# @param [string] name of index
+# @private
+_putIdMapping = (name) ->
+	client.indices.putMapping
+
+
+# Create Elastic Search index with given name.
+# @param [string] name of the index
+# @private
+_createIndex = (name) ->
+	params =
+		'index': name
+		'mappings': {}
+
+
+	if name is 'events'
+		params.mappings.event = '_id': 'path': 'id'
+	else
+		params.mappings.group = '_id': 'path': 'id'
+
 	client.indices.exists
 		'index': name
 	.then(
@@ -17,17 +40,21 @@
 			if not exists
 				client.indices.create
 					'index': name
+					'_id':
+						'path': 'id'
 				.then(
 					(body) ->
 						console.log "Created #{name} index successfully"
 					(error) ->
 						console.error 'Failed to create event index')
 			else
+
 				console.log "Index already exists"

 		(error) ->
 			console.error error)
-
+
+

 # Make sure elastic search is running
 client.ping
@@ -35,17 +62,12 @@
 	hello: 'elasticsearch!'
 .then(
 	(body) ->
-		createIndex 'events'
-		createIndex 'groups'
+		_createIndex 'events'
+#		_createIndex 'groups'
 	(error) ->
 		console.error 'Elasticsearch cluster is down'
 		throw new Error 'Elastic search cluster is down')

-
-# Normalize url by removing http, www, and trailing slash
-normalizeUrl = (url) ->
-	return url
-

 # Add events to ElasticSearch event index
 # @param [object] query
@@ -60,7 +82,7 @@
 			'index':
 				'_index': 'events'
 				'_type': 'event'
-		event.id = event.source
+				'_id': event.id
 		bulkEvents.push event

 	client.bulk
@@ -72,10 +94,16 @@
 			query.onError error)

+# Add events to ElasticSearch event index
+# @param [object] query
+# @option query [object] group
+# @option query [function] onSuccess
+# @option query [function] onError
 setGroup = (query) ->
-	client.create
+	client.index
 		'index': 'groups',
 		'type': 'group'
+		'id': query.group.id
 		'body': query.group
 	.then(
 		(result) ->
@@ -84,6 +112,7 @@
 			query.onError error)

+# Export set and setGroup functions
 exports = module.exports =
 	'set': set
 	'setGroup': setGroup


Restore dev console access

Restore dev console access

--- app/main.cjsx
+++ app/main.cjsx
@@ -27,3 +27,6 @@

 logDeployedCommit = require './lib/log-deployed-commit'
 logDeployedCommit()
+
+# For console access:
+window?.zooAPI = require 'panoptes-client/lib/api-client'


allow 'restarting' as service state. expose functions to reconcile service state

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -1,4 +1,4 @@
-SERVICE_STATES = ['starting', 'running', 'stopping', 'stopped', 'removed']
+SERVICE_STATES = ['starting', 'restarting', 'running', 'stopping', 'stopped', 'removed']

 atLeastOneWithState = (services, states...) ->
   _.reduce services, ((memo, service) -> memo or service.state in states), false
@@ -40,6 +40,7 @@
     if f then Instances.update doc._id, $set:
       state: 'created'
       desiredState: 'running'
+      status: 'Created'

 Instances.find({state: 'removed', desiredState: 'stopped'}, fields: {_id: 1}).observe
   added: (doc, olddoc) -> Instances.remove doc._id
@@ -56,7 +57,18 @@

 f = true

+setServiceField = (fieldName, fieldValue, labels) ->
+  if (name = labels['bigboat/instance/name']) and (service = labels['bigboat/service/name'])
+    Instances.upsert {name: name}, $set: {"services.#{service}.#{fieldName}": fieldValue}
+
 module.exports =
+  updateServiceFQDN: (fqdn, labels) -> setServiceField 'fqdn', fqdn, labels
+
+  updateServicePorts: (ports, labels) -> setServiceField 'ports', ports, labels
+
+  updateContainerName: (name, labels) -> setServiceField 'container.name', name, labels
+
+
   updateServiceState: (mappedState, labels) ->
     unless mappedState in SERVICE_STATES
       throw "Service state '#{mappedState}' is not supported."
@@ -83,3 +95,6 @@
   #
   imagePull: (image) ->
     updated = Instances.update {'steps.image': image}, {$set: {'steps.$.completed': true}}
+
+  imagePulling: (image, instance) ->
+    Instances.upsert {name: instance}, $set: status: "Pulling #{image}"


add storageBucket

add storageBucket

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -80,6 +80,7 @@
           updateDoc.app = name: appName, version: appVersion

       updateDoc.startedBy = startedBy if startedBy = labels['bigboat/startedBy']
+      updateDoc.storageBucket = storageBucket if storageBucket = labels['bigboat/storage/bucket']

       updateDoc['agent.url'] = val if val = labels['bigboat/agent/url']


white space

white space


Remove unused tasks & functions

Remove unused tasks & functions

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -25,13 +25,6 @@
   singleRun: true

 DIST_DIR = './dist'
-
-handleErrors = (title) => (args...) =>
-  # TODO: Send error to notification center with gulp-notify
-  console.error(title, args...)
-  # Keep gulp from hanging on this task
-  @emit('end')
-

 # -----------------------------------------------------------------------
 #  Build Javascript and Styles using webpack
@@ -75,11 +68,6 @@
 # -----------------------------------------------------------------------
 #  Development
 # -----------------------------------------------------------------------
-#
-gulp.task '_karma', ->
-  server = new karma.Server(KARMA_CONFIG)
-  server.start()
-
 gulp.task '_webserver', ->
   config = _.extend( {}, webpackConfig)
   config.entry.tutor.unshift(


KDView emits paste event now

KDView emits paste event now

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -39,6 +39,7 @@
     dragenter   : "dragEnter"
     dragleave   : "dragLeave"
     dragover    : "dragOver"
+    paste       : "paste"

   overrideAndMergeObjects = (objects)->
@@ -468,7 +469,7 @@
   bindEvents:($elm)->
     $elm or= @getDomElement()
     # defaultEvents = "mousedown mouseup click dblclick dragstart dragenter dragleave dragover drop resize"
-    defaultEvents = "mousedown mouseup click dblclick"
+    defaultEvents = "mousedown mouseup click dblclick paste"
     instanceEvents = @getOptions().bind

     eventsToBeBound = if instanceEvents
@@ -528,6 +529,8 @@
   mouseLeave:(event)-> yes

   mouseUp:(event)->    yes
+
+  paste:(event)->      yes

   mouseDown:(event)->
     (@getSingleton "windowController").setKeyView null


fix transition to cc dashboard when typing in just the course url

--- src/components/task-plan/teacher-task-plans-listing.cjsx
+++ src/components/task-plan/teacher-task-plans-listing.cjsx
@@ -68,7 +68,12 @@

   statics:
     willTransitionTo: (transition, params, query, callback) ->
-      {date, planId} = params
+      {date, planId, courseId} = params
+      course = CourseStore.get(courseId)
+      if course.is_concept_coach
+        transition.redirect('cc-dashboard', {courseId})
+        return callback()
+
       unless date? and moment(date, DATE_FORMAT).isValid()
         date = moment(TimeStore.getNow())
         params.date = date.format(DATE_FORMAT)


a little more coffee-like.

a little more coffee-like.

--- public/init.coffee
+++ public/init.coffee
@@ -1,8 +1,8 @@
 $.ajaxSettings.accepts.json = null # required to work with the epages API
+
 # TODO remove this before going live
-$.ajaxSettings.headers = {
+$.ajaxSettings.headers =
   "Authorization": "Bearer M0mPgTiGPtw5LkdCGwhel3gcGc5PqIPF",
   "Accept": "application/vnd.epages.v1+json"
-}

 App.start()


Fixes for IE support

Fixes for IE support

--- app/classes/WebGLCanvas.coffee
+++ app/classes/WebGLCanvas.coffee
@@ -492,12 +492,12 @@

       for line in error.split('\n')

-        parts = /ERROR:\s*(\d+)\s*:\s*(\d+)\s*:\s*(.*)/.exec(line)
+        parts = /^ERROR:\s*\d+\s*:\s*(\d+)\s*:\s*(.*)/.exec(line) or /^\((\d+),\s*\d+\):\s*(.*)/.exec(line)

         if parts
-          line = parseInt(parts[2])
+          line = parseInt(parts[1])
           @errors.push(
-            message: parts[3]
+            message: parts[2]
             line: line - headerSize
           )


result -> item

result -> item

--- modules/player/search.coffee
+++ modules/player/search.coffee
@@ -30,7 +30,7 @@
         name: if results.partial then l.player.hud.searching else l.player.hud.results
         icon: if results.partial then 'https://d.thebitlink.com/wheel.gif'
       description: ''
-    results.items.forEach (result, i)=>
+    results.items.forEach (item, i)=>
       embed.description += "#{options[i]} [#{item.title.replace(/\]/, '\\]')}]"
       embed.description += "(#{item.webpage_url.replace(/\]/, '\\]')})"
       embed.description += " (#{@util.displayTime item.duration})\n"


rename internal method uploadFile to _uploadFile

--- services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee
@@ -140,7 +140,7 @@
 				return callback(error) if error?
 				callback null, doc, folder_id

-	uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)->
+	_uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)->
 		if not SafePath.isCleanFilename fileName
 			return callback new Errors.InvalidNameError("invalid element name")
 		fileRef = new File(
@@ -165,7 +165,7 @@
 	addFile: wrapWithLock
 		beforeLock: (next) ->
 			(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
-				ProjectEntityUpdateHandler.uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
+				ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
 					return callback(error) if error?
 					next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
 		withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)->
@@ -229,7 +229,7 @@
 		# the history unless you are making sure it is updated in some other way.
 		beforeLock: (next) ->
 			(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
-				ProjectEntityUpdateHandler.uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
+				ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
 					return callback(error) if error?
 					next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
 		withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->


strip out debugging info

strip out debugging info


Disable DASH for YouTube videos (currently broken)

This is a temporary workaround.

--- modules/player/util.coffee
+++ modules/player/util.coffee
@@ -110,6 +110,7 @@

   # Uses youtube-dl to get information of an URL or search term
   getInfo: (query)-> await ytdl(query, [
+      '--youtube-skip-dash-manifest'
       '--default-search=ytsearch'
       '--ignore-errors'
       '--force-ipv4',


linkShell: acorn.withLink -> acorn.shellWithLink

--- coffee/src/shells/link.shell.coffee
+++ coffee/src/shells/link.shell.coffee
@@ -111,7 +111,7 @@
     return new BestShell.Model link: link

-acorn.withLink = LinkShell.Model.withLink
+acorn.shellWithLink = LinkShell.Model.withLink


Include React/Image and React/Text by default

Every app needs these. Also define REACT_NATIVE_PATH for convenience.

--- src/PodfileFragmentGenerator.coffee
+++ src/PodfileFragmentGenerator.coffee
@@ -67,8 +67,6 @@
   for np in nativePackages
     if np.nativePackage.podspec?
       podspecs.push [np.name, path.join np.path, np.nativePackage.podspec]
-    else if np.name is 'react-native'
-      podspecs.push [np.name, path.join np.path, 'React.podspec']
     else
       defaultPodspec = path.join np.path, "#{ np.name }.podspec"
       if fs.existsSync defaultPodspec
@@ -80,6 +78,10 @@

   """
   # Generated by rem for #{ pkg.name }
+  REACT_NATIVE_PATH = #{ rubyEscapeString reactNativePath }
+  pod 'React', :path => REACT_NATIVE_PATH
+  pod 'React/RCTImage', :path => REACT_NATIVE_PATH
+  pod 'React/RCTText', :path => REACT_NATIVE_PATH
   #{("pod #{ rubyEscapeString name }, :path => #{ rubyEscapeString podspecPath }\n" for [name, podspecPath] in podspecs).join ''}
   """


More patience for forward/backward button spec

--- spec_app/spec/javascripts/up/link_spec.js.coffee
+++ spec_app/spec/javascripts/up/link_spec.js.coffee
@@ -44,6 +44,8 @@

         it 'adds history entries and allows the user to use the back- and forward-buttons', (done) ->

+          waitForBrowser = 70
+
           # By default, up.history will replace the <body> tag when
           # the user presses the back-button. We reconfigure this
           # so we don't lose the Jasmine runner interface.
@@ -83,21 +85,21 @@
                 expect(document.title).toEqual('title from three')

                 history.back()
-                u.setTimer 50, ->
+                u.setTimer waitForBrowser, ->
                   respondWith('restored text from two', 'restored title from two')
                   expect($('.target')).toHaveText('restored text from two')
                   expect(location.pathname).toEqual('/two')
                   expect(document.title).toEqual('restored title from two')

                   history.back()
-                  u.setTimer 50, ->
+                  u.setTimer waitForBrowser, ->
                     respondWith('restored text from one', 'restored title from one')
                     expect($('.target')).toHaveText('restored text from one')
                     expect(location.pathname).toEqual('/one')
                     expect(document.title).toEqual('restored title from one')

                     history.forward()
-                    u.setTimer 50, ->
+                    u.setTimer waitForBrowser, ->
                       # Since the response is cached, we don't have to respond
                       expect($('.target')).toHaveText('restored text from two', 'restored title from two')
                       expect(location.pathname).toEqual('/two')


Fix polymorphic has many async test

Fix polymorphic has many async test

--- tests/batman/model/associations/polymorphic_has_many_test.coffee
+++ tests/batman/model/associations/polymorphic_has_many_test.coffee
@@ -87,7 +87,7 @@
     metafield.save (err) ->
       throw err if err
       equal metafields.get('length'), 2
-    QUnit.start()
+      QUnit.start()

 asyncTest "hasMany associations should take record type into consideration when adding items to their set", 5, ->
   @Product.find 4, (err, product) =>


Use AppFollowBucketItemView for JApp

Use AppFollowBucketItemView for JApp

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
@@ -21,6 +21,7 @@
   getBucketMap =->
     JAccount  : AccountFollowBucketItemView
     JTag      : TagFollowBucketItemView
+    JApp      : AppFollowBucketItemView

   constructor:(options = {},data)->


Don't clear loaded packages between specs

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -37,7 +37,6 @@

   window.resetTimeouts()
   atom.packageStates = {}
-  atom.loadedPackages = []
   spyOn(atom, 'saveWindowState')
   spyOn(atom, 'getSavedWindowState').andReturn(null)
   $native.setWindowState('')


raspberrypi.coffee: Mention Pi Zero W in the name

Changelog-entry: Mention Pi Zero W in the raspberrypi.coffee board name
Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- raspberrypi.coffee
+++ raspberrypi.coffee
@@ -5,7 +5,7 @@
 	version: 1
 	slug: 'raspberry-pi'
 	aliases: [ 'raspberrypi' ]
-	name: 'Raspberry Pi (v1 and Zero)'
+	name: 'Raspberry Pi (v1 / Zero / Zero W)'
 	arch: 'rpi'
 	state: 'released'


Add chart leftMargin property.

Add chart leftMargin property.

--- app/javascripts/correlation.coffee
+++ app/javascripts/correlation.coffee
@@ -197,9 +197,10 @@
     CHART_LAYOUT_PARAMS =
       height: 280
       width: 600
-      ticks: 4
       axisPadding: .2
       symbolSize: 8
+      ticks: 4
+      leftMargin: 6

     # The default chart data types to be displayed, by collection.
     DEFAULT_CHARTS:
@@ -444,7 +445,7 @@
           .group(group)
         chart.xAxis().ticks(CHART_LAYOUT_PARAMS.ticks)
         chart.yAxis().ticks(CHART_LAYOUT_PARAMS.ticks)
-        chart.margins().left += 6;
+        chart.margins().left += CHART_LAYOUT_PARAMS.leftMargin;

       # Render all the charts.
       dc.renderAll()
@@ -453,4 +454,4 @@
       #   they can be bound to mouse events.
       d3.selectAll('.chart-body').moveToFront()

-  ]+  ]


Update mini-course-content.coffee

Update mini-course-content.coffee

--- app/lib/mini-course-content.coffee
+++ app/lib/mini-course-content.coffee
@@ -307,10 +307,10 @@
     material:
       title: "Congratulations!"
       text: """
-
You have successfully completed the Planet Hunters mini-course on Exoplanetary Astrophysics! We hope you enjoyed it, and that you learned some new and interesting things about this fantastic area of research. Please continue your great work on Planet Hunters, so that together we can continue to discover more about our Universe.

-
Remember, if you want to review the slides from the course, you can browse them form your profile

-        """
-      figure: "./images/mini-course/slide-25.png"
+
You've successfully completed the Planet Hunters mini-course on Exoplanetary Astrophysics! We hope you enjoyed it, and that you learned some new and interesting things about this fantastic area of research. Please continue your great work on Planet Hunters, so that together we can continue to discover more about our Universe.

+
Remember, if you want to review the slides from the course, you can access them from your profile

+        """
+      figure: "./images/mini-course/slide-25.jpg"
       figure_credits: ""

   }


Fix wrong conditional

Fix wrong conditional

The `unless process.env.HUBOT_GITHUB_API` conditional was wrong and the script was not working on non-enterprise APIs.

--- src/scripts/github-commiters.coffee
+++ src/scripts/github-commiters.coffee
@@ -47,9 +47,7 @@
         else if commits.length == 0
           msg.send "Achievement unlocked: [LIKE A BOSS] no commits found!"
         else
-          unless process.env.HUBOT_GITHUB_API
-            msg.send "https://github.com/#{repo}"
-          else
-            ghe_url = base_url.replace /\/api\/v3/, ''
-            msg.send "#{ghe_url}/#{repo}"
-            response_handler commits
+          if process.env.HUBOT_GITHUB_API
+            base_url = base_url.replace /\/api\/v3/, ''
+          msg.send "#{base_url}/#{repo}"
+          response_handler commits