When closing link dialog reselect editor back.

--- client/lib/scribe/links.coffee
+++ client/lib/scribe/links.coffee
@@ -92,8 +92,13 @@
         position: position
         width: 360
         close: (event, ui) =>
-          $dialog.remove()
+          range.selectNode parentAnchor if parentAnchor
+          selection.selection.removeAllRanges()
+          selection.selection.addRange range
+
+          $dialog.dialog('destroy')
           template._$dialog = null
+
           return # Make sure CoffeeScript does not return anything
         buttons: buttons


Add local cache to fetchImage() logic

Add local cache to fetchImage() logic

--- coffee/controller/cabin.coffee
+++ coffee/controller/cabin.coffee
@@ -2,6 +2,8 @@

 site = require '../model/config'
 cabin = require '../model/cabin'
+request = require 'request'
+fs = require 'fs'

 exports.getList = (request, reply) ->
   reply.view 'list',
@@ -19,11 +21,14 @@
       cabin: data
       title: 'Endre hytte'

-exports.getImage = (request, reply) ->
-  cabin.getCabin request.params.id, (err, data) ->
+exports.fetchImage = (id, reply) ->
+  cabin.getCabin id, (err, data) ->
+    # @TODO(starefossen) code is undefined
     return reply().code(404) if err or not data?.bilder?[0]
     cabin.getImage data.bilder[0], (err, data) ->
+      # @TODO(starefossen) code is undefined
       return reply().code(404) if err or not data?.img?[1]
-      stream = require('request')(data.img[1].url)
+      stream = request data.img[1].url
       stream.once 'response', reply
+      stream.pipe fs.createWriteStream('static/images/cabin/' + id)


Add a spec for screen line changes

Add a spec for screen line changes

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -233,12 +233,17 @@
         expect(editor.pixelPositionForScreenPosition([0, Infinity]).left).toEqual 2 * charWidth

   describe "gutter rendering", ->
-    [lineNumberHasClass, gutter] = []
+    [gutter] = []
+
+    lineNumberHasClass = (screenRow, klass) ->
+      component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
+
+    bufferRowHasClass = (bufferRow, klass) ->
+      screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
+      component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)

     beforeEach ->
       {gutter} = component.refs
-      lineNumberHasClass = (screenRow, klass) ->
-        component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)

     it "renders the currently-visible line numbers", ->
       node.style.height = 4.5 * lineHeightInPixels + 'px'
@@ -449,6 +454,20 @@
           expect(lineNumberHasClass(9, 'fancy-class')).toBe true
           expect(lineNumberHasClass(9, 'nope-class')).toBe false

+        it "initially renders off screen lines with line number classes based on the decorations on their buffer row", ->
+          marker = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]], invalidate: 'inside')
+          editor.addDecorationForMarker(marker, decoration)
+
+          waitsFor -> not component.decorationChangedImmediate?
+          runs ->
+            expect(bufferRowHasClass(9, 'someclass')).toBe true
+            editor.foldBufferRow(5)
+            editor.removeDecorationForMarker(marker, decoration)
+
+          waitsFor -> not component.decorationChangedImmediate?
+          runs ->
+            expect(bufferRowHasClass(9, 'someclass')).toBe false
+
         it "updates line number classes when the marker moves", ->
           expect(lineNumberHasClass(1, 'someclass')).toBe false
           expect(lineNumberHasClass(2, 'someclass')).toBe true


Fix error with asset upload in Firefox

Fix error with asset upload in Firefox

--- packages/rocketchat-ui-admin/admin/admin.coffee
+++ packages/rocketchat-ui-admin/admin/admin.coffee
@@ -233,8 +233,8 @@
 	"click .delete-asset": ->
 		Meteor.call 'unsetAsset', @asset

-	"change input[type=file]": ->
-		e = event.originalEvent or event
+	"change input[type=file]": (ev) ->
+		e = ev.originalEvent or ev
 		files = e.target.files
 		if not files or files.length is 0
 			files = e.dataTransfer?.files or []


Set text on the markdown editor rather than the active editor in spec

--- spec/markdown-preview-spec.coffee
+++ spec/markdown-preview-spec.coffee
@@ -161,13 +161,14 @@
     describe "when the editor is modified", ->
       describe "when the preview is in the active pane but is not the active item", ->
         it "re-renders the preview but does not make it active", ->
+          markdownEditor = atom.workspace.getActiveEditor()
           previewPane.focus()

           waitsForPromise ->
             atom.workspace.open()

           runs ->
-            atom.workspace.getActiveEditor().setText("Hey!")
+            markdownEditor.setText("Hey!")

           waitsFor ->
             MarkdownPreviewView::renderMarkdown.callCount > 0


Bind keys h/l to cal.fullCalendar prev/next

--- orgviz/static/orgviz.coffee
+++ orgviz/static/orgviz.coffee
@@ -177,6 +177,8 @@
     # navigation
     .bind("keydown", "left", -> cal.fullCalendar "prev")
     .bind("keydown", "right", -> cal.fullCalendar "next")
+    .bind("keydown", "h", -> cal.fullCalendar "prev")
+    .bind("keydown", "l", -> cal.fullCalendar "next")
     .bind("keydown", "j", getCalScrollUpDown(cal, 100))
     .bind("keydown", "k", getCalScrollUpDown(cal, -100))
     # change views


Might as well may dusya case insenstive as well

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -55,7 +55,7 @@

 # Make any changes required to the name
 filterName = (name) ->
-  if /dusya/.test(name)
+  if /dusya/i.test(name)
     # She likes to change her name A LOT. We can assume if it
     # looks like her, it's her.
     name = 'dusya'


Pick a random background from the disk

Pick a random background from the disk

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -75,20 +75,34 @@
 buildComic = (lines, cb) ->
   buildPanels lines, (err, panels) ->
     return cb(err, null) if err
-
-    # TODO: Note that you have mixed extension backgrounds, so you'll have
-    # to vary the loader to accommodate.
-    GD.openPng path.resolve(BG_BASE, "b29.png"), (err, bg) ->
-      return cb(err, bg) if err
-      totalPadding = 12
-      left = 0
-      top = totalPadding / 2
-      for panel in panels
-        do (panel) ->
-          compositeImage bg, panel, (left += (totalPadding / 2)), top
-          left += panel.width # Panel width
-          left += (totalPadding / 2)
-      cb(false, bg)
+    loaders =
+      png: GD.openPng
+      jpg: GD.openJpeg
+      jpeg: GD.openJpeg
+
+
+    fs.readdir BG_BASE, (err, files) ->
+      cb(err, null) if err
+      files = files.filter (f) -> f[0] != '.'
+      selected = files[Math.round(Math.random() * files.length)]
+      ext = selected[-3..].toLowerCase()
+      loader = loaders[ext]
+
+      # If we pick a BG we can't load, panic
+      # TODO: Or try again a few times?
+      cb("Can't find loader for #{selected}", null) unless loader
+
+      loader path.resolve(BG_BASE, selected), (err, bg) ->
+        return cb(err, bg) if err
+        totalPadding = 12
+        left = 0
+        top = totalPadding / 2
+        for panel in panels
+          do (panel) ->
+            compositeImage bg, panel, (left += (totalPadding / 2)), top
+            left += panel.width # Panel width
+            left += (totalPadding / 2)
+        cb(false, bg)

 # Turn a set of 6 `lines` into 3 panels
 # using two lines per panel, then invokes `cb`.


Try this

Try this

--- scripts/gif.coffee
+++ scripts/gif.coffee
@@ -23,9 +23,8 @@
         'beard slap': 'http://i.imgur.com/6qLjt.gif'

     robot.respond /gif (.*)/i, (msg) ->
-        name = gifs[msg.match[0]]
+        name = gifs[msg.match[1]]
         msg.send name if name?

     robot.respond /gif bomb/i, ->
-            for name, gif of gifs
-                msg.send gif
+        msg.send gif for name, gif of gifs


hide repeat checkbox for matrix

hide repeat checkbox for matrix

--- jsapp/xlform/src/view.row.coffee
+++ jsapp/xlform/src/view.row.coffee
@@ -218,6 +218,9 @@
           @mandatorySetting = new $viewMandatorySetting.MandatorySettingView({
             model: @model.get('required')
           }).render().insertInDOM(@)
+        else if key is '_isRepeat' and @model.getValue('type') is 'kobomatrix'
+          # don't display repeat checkbox for matrix groups
+          return false
         else
           new $viewRowDetail.DetailView(model: val, rowView: @).render().insertInDOM(@)


(- fix tests) Only listen for flushed once

--- Paws/Test/reactor.tests.coffee
+++ Paws/Test/reactor.tests.coffee
@@ -536,7 +536,7 @@
          here.with(immediate: no).stage new Native ->

          listener = sinon.spy()
-         here.on 'flushed', listener
+         here.once 'flushed', listener

          expect(here.realize()).to.be.ok()
          expect(listener).was.notCalled()
@@ -545,7 +545,7 @@
          here.with(immediate: no).stage new Native ->

          listener = sinon.spy()
-         here.on 'flushed', listener
+         here.once 'flushed', listener

          expect(here.realize()).to.be.ok()

@@ -560,7 +560,7 @@
          here.with(immediate: no).stage requestor, undefined, new Mask mutex

          listener = sinon.spy()
-         here.on 'flushed', listener
+         here.once 'flushed', listener

          expect(here.realize()).to.be.ok()
          expect(listener).was.calledWith 1


Update fr.coffee

Update fr.coffee

Correction de fautes et oublis

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -62,9 +62,9 @@
     'konnector description withings': "Téléchargez toutes les mesures de vos appareils Withings."
     'konnector description twitter': """
     Téléchargez tous vos tweets publiés sur Twitter. Ce connecteur requiert
-    deux identifiants and deux clés secrètes. Vous pouvez les générer via le
+    deux identifiants et deux clés secrètes. Vous pouvez les générer via le
     tableau Twitter de gestion
-    d'applications. Vous pourrez y créez une application. Twitter vous
+    d'applications. Vous pourrez y créer une application. Twitter vous
     fournira des identifiants pour cette application. Avec ces identifiants
     ce connecteur pourra récupérer vos données.
     """


Remove Table Xtract flash from now unused home page

--- client/code/view/home.coffee
+++ client/code/view/home.coffee
@@ -8,19 +8,5 @@

   render: ->
     @el.innerHTML = JST['home']()
-    @tableXtractFlash()
     @

-  tableXtractFlash: ->
-    html = """

-      Automatically and accurately extract tables from PDFs
-      with our new Table Xtract tool
-      Learn more
-
"""
-    $('
').attr('id', 'table-xtract-flash').addClass('alert alert-warning')
-      .hide().html(html).insertAfter('body > header').slideDown()
-      .find('a').on 'click', ->
-        _gaq.push ['_trackEvent', 'table-xtract', 'homepage-flash-click']
-
-  close: ->
-    $('#table-xtract-flash').hide()


state-ify bubble sort

state-ify bubble sort

--- main.coffee
+++ main.coffee
@@ -138,14 +138,33 @@
         else
                 ssort()

+# Good old bubble sort
 bsort = ->
-        swapped = false
-        for i in [1...colours.length]
-                if colours[i - 1].val > colours[i].val
-                        swapRects(i - 1, i)
-                        swapped = true
-
-        defer(bsort) if swapped
+        sort_context ?=
+                swapped: false
+                i: 1
+                count: 0
+
+        { swapped, i, count } = sort_context
+
+        if i == colours.length
+                sort_context.i = 1
+                sort_context.swapped = false
+
+                bsort() if swapped
+                return
+
+        if colours[i - 1].val > colours[i].val
+                swapRects(i - 1, i)
+                sort_context.swapped = true
+
+        sort_context.i++
+        sort_context.count++
+
+        if (sort_context.count % UPDATE_INTERVAL) == 0
+                defer(bsort)
+        else
+                bsort()

 qsort = (tukey) ->
         # Put the median of colours.val's in colours[a].


return 0% when values are null and can't be computed

--- tutor/src/helpers/task.coffee
+++ tutor/src/helpers/task.coffee
@@ -20,18 +20,21 @@
     moment(due_at).isBefore(TimeStore.getNow())

   getHumanUnacceptedScore: (task) ->
+    return '0%' unless task.correct_on_time_exercise_count and task.exercise_count
     score = Math.round((
       task.correct_on_time_exercise_count / task.exercise_count
       ) * 100 )
     "#{score}%"

   getHumanScoreWithLateWork: (task) ->
+    return '0%' unless task.correct_exercise_count and task.exercise_count
     score = Math.round((
       task.correct_exercise_count / task.exercise_count
       ) * 100 )
     "#{score}%"

   getHumanUnacceptedProgress: (task) ->
+    return '0%' unless task.completed_on_time_exercise_count and task.exercise_count
     progress = Math.round((
       task.completed_on_time_exercise_count / task.exercise_count
       ) * 100 )
@@ -43,6 +46,7 @@
         task.completed_exercise_count / task.exercise_count
       else
         task.completed_step_count / task.step_count
+    return '0%' if isNaN(percent )
     progress = Math.round(percent * 100)
     "#{progress}%"


fixed error in inspect()

fixed error in inspect()

--- src/Toolbox.coffee
+++ src/Toolbox.coffee
@@ -61,7 +61,7 @@
     for k,v of obj
       switch typeof v
         when 'function' then ret.functions.push key: k, value: v.toString()
-        when 'object' then ret.objects.push key: k, (if long then value: v else value: "Object")
+        when 'object' then ret.objects.push key: k, value: (if long then v else "Object")
         else ret.vars.push key: k, value: v
     if print
       for t in ['functions','objects','vars']


fix(adapter): should ignore all messages unless v2messageType is 'V2Message'

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -89,7 +89,7 @@
         .then (response) =>
           if response?
             @robot.logger.debug "Received #{response.length ? 0} datafeed messages"
-            @_receiveMessage msg for msg in response when msg.v2messageType = 'V2Message'
+            @_receiveMessage msg for msg in response when msg.v2messageType is 'V2Message'
           @emit 'poll', id
         .fail (err) =>
           @robot.emit 'error', new Error("Unable to read datafeed #{id}: #{err}")


Disable portable tests, mess with env

Disable portable tests, mess with env

--- spec/atom-portable-spec.coffee
+++ spec/atom-portable-spec.coffee
@@ -8,6 +8,8 @@
       expect(AtomPortable.isPortableInstall(platform, "C:\\some\\path")).toBe false

   describe "without ATOM_HOME environment variable", ->
+    return # Disabled - interferes with user home directory
+
     environmentAtomHome = undefined
     portableAtomHomePath = path.join(path.dirname(process.execPath), "..", ".atom")
     portableAtomHomeNaturallyExists = fs.existsSync(portableAtomHomePath)
@@ -50,6 +52,8 @@
     fs.removeSync(portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomeBackupPath)

 describe "Check for Portable Mode", ->
+  return # Disabled - interferes with user home directory
+
   describe "Windows", ->
     portableModeCommonPlatformBehavior "win32"


Expose Tour to window

Expose Tour to window

--- shepherd.coffee
+++ shepherd.coffee
@@ -350,6 +350,8 @@
   start: ->
     @currentStep = null
     @next()
+
+window.Tour = Tour

 tour = new Tour
   defaults:


No need to send event data on Socket connection when pages already contain it. Only send new stuff

--- nodejs/palsu.coffee
+++ nodejs/palsu.coffee
@@ -60,6 +60,14 @@
                 posts = event.get 'sioc:container_of'
                 posts.predicate = "sioc:has_container"
                 posts.object = event.id
+                posts.comparator = (item) ->
+                    itemDate = new Date item.get "dc:created"
+                    itemIndex = 0
+                    posts.pluck("dc:created").forEach (date, index) ->
+                        if itemDate.getTime() > new Date(date).getTime()
+                            itemIndex = index + 1
+                    return itemIndex
+
                 return posts.fetch
                     success: (postCollection) ->
                         console.log "Got #{postCollection.length} posts"
@@ -82,27 +90,6 @@

 # Handle a new connected client
 socket.on 'connection', (client) ->
-    # TODO: Send new data
-    meeting = VIE.EntityManager.getByJSONLD
-        "@": "<#meeting>"
-        a: "<mgd:event>"
-    meeting.fetch
-        success: (item) ->
-            client.send item.toJSONLD()
-
-    meetingComments = new VIE.RDFEntityCollection
-    meetingComments.predicate = "sioc:has_container"
-    meetingComments.object = "#meeting-comments"
-    meetingComments.comparator = (item) ->
-        itemDate = new Date item.get "dc:created"
-        itemIndex = 0
-        meetingComments.pluck("dc:created").forEach (date, index) ->
-            if itemDate.getTime() > new Date(date).getTime()
-                itemIndex = index + 1
-        return itemIndex
-    meetingComments.bind "add", (item) ->
-        client.send item.toJSONLD()
-    meetingComments.fetch()

     client.on 'message', (data) ->
         if typeof data isnt 'object'


Fix spec

Fix spec

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -33,7 +33,7 @@
       notification = notificationContainer.querySelector('atom-notification.success')
       expect(notificationContainer.childNodes.length).toBe 1
       expect(notification).toHaveClass 'success'
-      expect(notification.querySelector('.message').textContent).toBe 'A message'
+      expect(notification.querySelector('.message').textContent.trim()).toBe 'A message'

       atom.notifications.addInfo('A message')
       expect(notificationContainer.childNodes.length).toBe 2


not all constructors have names

not all constructors have names

--- src/stdlib/key-binder.coffee
+++ src/stdlib/key-binder.coffee
@@ -68,7 +68,7 @@
   triggerBinding: (scope, method) ->
     responder = _.detect @responders(), (responder) =>
       (scope is 'window' and responder is window) or
-        responder.constructor.name.toLowerCase() is scope or
+        responder.constructor.name?.toLowerCase() is scope or
         @inheritedKeymap responder, scope
     if responder
       if _.isFunction method


:white_check_mark: Added specs

:white_check_mark: Added specs

--- spec/sync-settings-spec.coffee
+++ spec/sync-settings-spec.coffee
@@ -135,4 +135,5 @@
           , ->
             for file in atom.config.get 'sync-settings.extraFiles'
               expect(fs.existsSync("#{atom.config.configDirPath}/#{file}")).toBe(true)
+              expect(SyncSettings.fileContent("#{atom.config.configDirPath}/#{file}")).toBe("# #{file} (not found) ")
               fs.unlink "#{atom.config.configDirPath}/#{file}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -39,7 +39,7 @@
     width: 8
     height: 8
     collisionMargin: Point(0, 0)
-  I.
+
   ###*
   Distance between two objects. Proxies to Point.distance.
   In order for this to work, `otherObj` must have a


Docco for command

Docco for command

--- src/command.coffee
+++ src/command.coffee
@@ -1,12 +1,23 @@
-process.title = "pow"
+# The `command` module is loaded when the `pow` binary runs. It parses
+# any command-line arguments and determines whether to install Pow's
+# configuration files or start the daemon itself.

 {Daemon, Configuration, Installer} = require ".."
 sys = require "sys"

+# Set the process's title to `pow` so it's easier to find in `ps`,
+# `top`, Activity Monitor, and so on.
+process.title = "pow"
+
+# Print valid command-line arguments and exit with a non-zero exit
+# code if invalid arguments are passed to the `pow` binary.
 usage = ->
-  console.error "usage: pow [[--install-local | --install-system] [--dry-run]]"
+  console.error "usage: pow [--install-local | --install-system [--dry-run]]"
   process.exit -1

+# Start by loading the user configuration from `~/.powconfig`, if it
+# exists. The user configuration affects both the installer and the
+# daemon.
 Configuration.getUserConfiguration (err, configuration) ->
   throw err if err

@@ -14,28 +25,40 @@
   dryRun = false

   for arg in process.argv.slice(2)
+    # Cache the factory method for creating a local or system
+    # installer if necessary.
     if arg is "--install-local"
       createInstaller = Installer.getLocalInstaller
     else if arg is "--install-system"
       createInstaller = Installer.getSystemInstaller
+    # Set a flag if a dry run is requested.
     else if arg is "--dry-run"
       dryRun = true
+    # Abort if we encounter an unknown argument.
     else
       usage()

+  # Abort if a dry run is requested without installing anything.
   if dryRun and not createInstaller
     usage()
   else if createInstaller
+    # Create the installer, passing in our loaded configuration.
     installer = createInstaller configuration
+    # If a dry run was requested, check to see whether any files need
+    # to be installed with root privileges. If yes, exit with a status
+    # of 1. If no, exit with a status of 0.
     if dryRun
       installer.needsRootPrivileges (needsRoot) ->
         exitCode = if needsRoot then 1 else 0
         installer.getStaleFiles (files) ->
           sys.puts file.path for file in files
           process.exit exitCode
+    # Otherwise, install all the requested files, printing the full
+    # path of each installed file to stdout.
     else
       installer.install (err) ->
         throw err if err
   else
+    # Start up the Pow daemon if no arguments were passed.
     daemon = new Daemon configuration
     daemon.start()


fix some styling issues in command

fix some styling issues in command

--- src/command.coffee
+++ src/command.coffee
@@ -79,14 +79,19 @@
   write = (string, filename) ->
     if typeof string is 'object'
       string = JSON.stringify string, null, (if argv.pretty then '  ' else '')
-    unless argv.out then process.stdout.write string
+    unless argv.out
+      process.stdout.write string
     else
-      if argv['append-ext'] then newName = path.basename filename
-      else newName = path.basename filename, path.extname filename
+      if argv['append-ext']
+        newName = path.basename filename
+      else
+        newName = path.basename filename, path.extname filename
       newName = path.join argv.out, newName + '.svg'
       fs.writeFile newName, string, (error) ->
-        unless error then print "#{filename} converted to #{newName}"
-        else warn "Error writing to #{newName}: #{error.code}"
+        unless error
+          print "#{filename} converted to #{newName}"
+        else
+          "Error writing to #{newName}: #{error.code}"

   # add drill file if it was included
   if argv.drill? and argv.drill not in fileList then fileList.push argv.drill


add $ as a valid separator for R. Closes #56.

--- lib/autocomplete-provider.coffee
+++ lib/autocomplete-provider.coffee
@@ -39,7 +39,7 @@

         getPrefix: (editor, bufferPosition) ->
             # Whatever your prefix regex might be
-            regex = /([\w0-9_-][\.:]?)+$/
+            regex = /([\w0-9_-][\.:\$]?)+$/

             # Get the text for the line up to the triggered buffer position
             line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])


check a property react won't notice

check a property react won't notice

--- src/context.coffee
+++ src/context.coffee
@@ -132,7 +132,8 @@
     React.renderComponent component, $wrapper.get 0
     $wrapper

-  is_component = (o) -> o?._lifeCycleState?
+  # FIXME figure out a real check for a react component
+  is_component = (o) -> o?.__realComponentInstance?

   is_renderable = (o) ->
     o? and (is_component(o) or o._lead_render?)


Bug fix %%

Bug fix %%

--- src/convert.coffee
+++ src/convert.coffee
@@ -35,8 +35,8 @@
   for v, neighbors of fold.vertices_vertices
     v = parseInt v
     for u, i in neighbors
-      next["#{u},#{v}"] = neighbors[(i-1) % neighbors.length]
-      #console.log u, v, neighbors[(i-1) % neighbors.length]
+      next["#{u},#{v}"] = neighbors[(i-1) %% neighbors.length]
+      #console.log u, v, neighbors[(i-1) %% neighbors.length]
   fold.faces_vertices = []
   #for uv, w of next
   for uv in (key for key of next)


Improved logging.

Improved logging.

--- public/assets/chat/chat.coffee
+++ public/assets/chat/chat.coffee
@@ -21,7 +21,7 @@
       buttonClass: 'open-zammad-chat'
       inactiveClass: 'is-inactive'
       title: 'Chat with us!'
-      idleTimeout: 8
+      idleTimeout: 4
       inactiveTimeout: 20

     _messageCount: 0
@@ -613,28 +613,32 @@
     inactiveTimeoutStart: =>
       @inactiveTimeoutStop()
       delay = =>
-        @log 'debug', "Inactive timeout of #{@options.inactiveTimeout} minutes, show timeout screen."
+        @log 'debug', "Inactive timeout of #{@options.inactiveTimeout} minutes, show timeout screen.", new Date
         @state = 'off'
         @setAgentOnlineState 'offline'
         @showTimeout()
         @wsClose()
+      @log 'debug', "Start inactive timeout in #{@options.inactiveTimeout} minutes", new Date
       @inactiveTimeoutStopDelayId = setTimeout(delay, @options.inactiveTimeout * 1000 * 60)

     inactiveTimeoutStop: =>
       return if !@inactiveTimeoutStopDelayId
+      @log 'debug', "Stop inactive timeout of #{@options.inactiveTimeout} minutes at", new Date
       clearTimeout(@inactiveTimeoutStopDelayId)

     idleTimeoutStart: =>
       @idleTimeoutStop()
       delay = =>
-        @log 'debug', "Idle timeout of #{@options.idleTimeout} minutes, hide widget"
+        @log 'debug', "Idle timeout of #{@options.idleTimeout} minutes, hide widget", new Date
         @state = 'off'
         @hide()
         @wsClose()
+      @log 'debug', "Start idle timeout in #{@options.idleTimeout} minutes", new Date
       @idleTimeoutStopDelayId = setTimeout(delay, @options.idleTimeout * 1000 * 60)

     idleTimeoutStop: =>
       return if !@idleTimeoutStopDelayId
+      @log 'debug', "Stop idle timeout of #{@options.idleTimeout} minutes at", new Date
       clearTimeout(@idleTimeoutStopDelayId)

   window.ZammadChat = ZammadChat


Relaxed another test.

Relaxed another test.

When the expected values are very low (or very high), then using proportions as ranges doesn't really work terribly well and I need quite large proportions to keep the false negatives under control.

--- src/test/nesting_spec.coffee
+++ src/test/nesting_spec.coffee
@@ -504,7 +504,7 @@
                   early nesters", ->
             earlyNesters = newBirds.filter((b) -> b.isEarly())
             earlyNesters.length.should.be.approximately(expectedEarlyNesters,
-              expectedEarlyNesters * 0.5)
+              expectedEarlyNesters)
           And "approximately #{expectedLateNesters} should be late nesters", ->
             lateNesters = newBirds.filter((b) -> b.isLate())
             lateNesters.length.should.be.approximately(expectedLateNesters,


Fixed lazy loading to use React state and eliminate race condition

--- public/scripts/MarkatoApp.cjsx
+++ public/scripts/MarkatoApp.cjsx
@@ -12,14 +12,6 @@
 ShareButton = require './ShareButton'
 ChordAltModal = require './ChordAltModal'
 TransposeModal = require './TransposeModal'
-
-# Lazily load play function
-play = null
-lazilyLoadPlay = ->
-  unless play
-    audio.init (err, fns) ->
-      if err then alert err
-      {play} = fns

 # TODO(jsrmath): Fix sharp11 issue #3 and then use plain chord.transpose
 transpose = (origKey, newKey, chordStr) ->
@@ -43,6 +35,12 @@
     chordReplacements: {} # Maps chord to index of alternate
     altsModalChord: null
     altsModalAlts: []
+    play: null # Lazily load the play function before the component mounts
+
+  componentWillMount: ->
+    audio.init (err, fns) =>
+      if err then alert err
+      @setState play: fns.play

   key: ->
     possibleKeys = [
@@ -115,7 +113,6 @@
     @setState showChordAltModal: true, altsModalChord: chord

   render: ->
-    lazilyLoadPlay()
     <Grid>
       <Row>
         <Col md={if @state.isEditing then 6 else 12}>
@@ -130,7 +127,7 @@
                          chordReplacements={@state.chordReplacements}
                          formatChordWithAlts={@formatChordWithAlts}
                          playback={not @state.isEditing}
-                         play={play}
+                         play={@state.play}
                          showTransposeModal={=> @toggleState 'showTransposeModal'}
                          setDisplayKey={@setDisplayKey}
                          fontSize={@props.displaySettings.fontSize} />


cleanup on if

cleanup on if

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -279,11 +279,10 @@
                         ifpass(f.strikethrough, s) ->
                             pass if cont.proxied
                                 stripProxiedColon seg.text
+                            else if seg.type == 'LINE_BREAK'
+                                    '\n'
                             else
-                                if seg.type == 'LINE_BREAK'
-                                    '\n'
-                                else
-                                    seg.text
+                                seg.text
     # image formatter
     (seg) ->
         href = seg?.link_data?.link_target


less horrible arcs intersection alg.

less horrible arcs intersection alg.

--- src/digraph.coffee
+++ src/digraph.coffee
@@ -18,10 +18,6 @@
     find_arc: (val) ->
       for arc in @_arcs
         return arc if arc.test(val)
-
-    intersect: (other) ->
-      # warning: Cartesian product
-      arc for arc in @arcs() when other.find_arc(arc.value)

   class @Arc
     constructor: (@vertex, @id, @value, @next_vertex) ->
@@ -107,13 +103,13 @@

     product_vertex: (v1, v2) ->
       key = @vkey(v1, v2)
-      @product_vertices[key]
-
-    product_next: (v1, v2) ->
-      unless (result = @product_vertex(v1, v2))
-        result = @intersection.create_vertex()
-        @add_unvisited(v1, v2, result)
-      result
+      unless (vertex = @product_vertices[key])
+        # Implement sink/final checking for FSMs by
+        # overriding this method and messing with the
+        # vertex we create.
+        vertex = @intersection.create_vertex()
+        @add_unvisited(v1, v2, vertex)
+      vertex

     add_unvisited: (v1, v2, product_vertex) ->
       key = @vkey(v1, v2)
@@ -130,8 +126,20 @@
         delete @unvisited[key]
         [v1, v2, @product_vertices[key]]

+    # this only works if arc equivalence is assumed to mean
+    # that the arcs have the same @value
+    intersecting_arcs: (v1, v2, callback) ->
+      vals = {}
+      for arc in v1.arcs()
+        vals[arc.value] = arc
+      for other_arc in v2.arcs()
+        if (this_arc = vals[other_arc.value])
+          callback(this_arc, other_arc)
+
+

   # return a new digraph that is the intersection of the two graphs.
+  # NOTE: FSM subclasses will end up with sinks that aren't final.
   intersect: (other) ->
     digraph = @
     product = new @constructor()
@@ -140,21 +148,24 @@
     while (next_vertices = helper.get_next_vertices())
       [this_vertex, other_vertex, product_vertex] = next_vertices

-      # for the two current vertices, iterate over the
-      # transitions with same values
-      for this_arc in this_vertex.arcs()
-        for other_arc in other_vertex.arcs() when this_arc.test(other_arc.value)
-          this_next = this_arc.next_vertex
-          other_next = other_arc.next_vertex
-
-          # if the new graph does not already have the intersection state,
-          # create an intersection state and index it
-          product_next = helper.product_next(this_next, other_next)
-          product.add_arc(product_vertex, product_next, this_arc.value)
+      # for the two current vertices, iterate over the arcs with same values.
+      # TODO: check the arc intersection algorithm for stupids.
+      helper.intersecting_arcs this_vertex, other_vertex, (this_arc, other_arc) ->
+        this_next = this_arc.next_vertex
+        other_next = other_arc.next_vertex
+
+        # if the new graph does not already have the intersection state,
+        # create an intersection state and index it
+        product_next = helper.product_vertex(this_next, other_next)
+        product.add_arc(product_vertex, product_next, this_arc.value)
     product

-
+  traverse: (callback) ->
+    _traverse = @stepper()
+    next = _traverse([@source], callback)
+    while next.length > 0
+      next = _traverse(next, callback)

   stepper: ->
     visited_arcs = {}
@@ -169,13 +180,6 @@
           next.push(arc.next_vertex)
       return next

-
-  traverse: (callback) ->
-    _traverse = @stepper()
-    next = _traverse([@source], callback)
-    while next.length > 0
-      next = _traverse(next, callback)
-
   write_graph: (filename) ->
     fs = require("fs")
     string = Graphviz.digraph_preamble("Djinn")


Allow both iframe and component launcher to pass through

--- src/compile.coffee
+++ src/compile.coffee
@@ -15,8 +15,9 @@
     manifestLoader.readManifest dir, (err, manifest) ->
       if err then return callback(err)

-      # No need to do anything for iframe gadgets
-      if manifest.launcher == 'iframe' || manifest.defaultConfig?.__launcher == 'iframe'
+      # No need to do anything for iframe or component gadgets
+      doNothingLaunchers = ['iframe', 'component']
+      if manifest.launcher in doNothingLaunchers || manifest.defaultConfig?.__launcher in doNothingLaunchers
         return compile.simplyCopyFilesToDist src, callback

       options = _.extend _.clone(options), { src, dest, manifest }


Don't update DOM unless it actually needs to change

This fixes usage of arrow keys within bound text fields.
I couldn't come up with a sensible way of testing this.

--- src/compile.coffee
+++ src/compile.coffee
@@ -47,7 +47,7 @@
       else
         val = get(model, ast.value)
         val = "" if val == undefined
-        element.value = val
+        element.value = val unless element.value is val

     modelUpdated()
     model.bind?("change:#{ast.value}", modelUpdated)


Links within hint content also open in a new tab.

--- services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
+++ services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
@@ -8,7 +8,7 @@
 		regexToMatch: /Extra alignment tab has been changed to \\cr/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Extra_alignment_tab_has_been_changed_to_%5Ccr"
 		humanReadableHint: """
-			You have written too many alignment tabs in a table, causing one of them to be turned into a line break. Make sure you have specified the correct number of columns in your table.
+			You have written too many alignment tabs in a table, causing one of them to be turned into a line break. Make sure you have specified the correct number of columns in your table.
 		"""
 	,
 		regexToMatch: /Display math should end with \$\$/
@@ -20,7 +20,7 @@
 		regexToMatch: /Missing [{$] inserted./
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Missing_$_inserted"
 		humanReadableHint: """
-			Check that your $'s match around math expressions. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Symbols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list here.
+			Check that your $'s match around math expressions. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Symbols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list here.
 		"""
 	,
 		regexToMatch: /(undefined )?[rR]eference(s)?.+(undefined)?/
@@ -44,13 +44,13 @@
 		regexToMatch: /`!?h' float specifier changed to `!?ht'/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:%60!h%27_float_specifier_changed_to_%60!ht%27."
 		humanReadableHint: """
-			The float specifier 'h' is too strict of a demand for LaTeX to place your float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try keep the float here anyway, check out the float package.
+			The float specifier 'h' is too strict of a demand for LaTeX to place your float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try keep the float here anyway, check out the float package.
 		"""
 	,
 		regexToMatch: /No positions in optional float specifier/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:No_positions_in_optional_float_specifier."
 		humanReadableHint: """
-			You have forgotten to include a float specifier, which tells LaTeX where to position your figure. Find out more about float specifiers here.
+			You have forgotten to include a float specifier, which tells LaTeX where to position your figure. Find out more about float specifiers here.
 		"""
 	,
 		regexToMatch: /Undefined control sequence/
@@ -62,13 +62,13 @@
 		regexToMatch: /File .+ not found/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:File_XXX_not_found_on_input_line_XXX."
 		humanReadableHint: """
-			The compiler cannot find the file you want to include. Make sure that you have uploaded the file and specified the file location correctly.
+			The compiler cannot find the file you want to include. Make sure that you have uploaded the file and specified the file location correctly.
 		"""
 	,
 		regexToMatch: /LaTeX Error: Unknown graphics extension: \..+/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_graphics_extension:_.gif."
 		humanReadableHint: """
-			The compiler does not recognise the file type of one of your images. Make sure you are using a supported image format for your choice of compiler, and check that there are no periods (.) in the name of your image.
+			The compiler does not recognise the file type of one of your images. Make sure you are using a supported image format for your choice of compiler, and check that there are no periods (.) in the name of your image.
 		"""
 	,
 		regexToMatch: /LaTeX Error: Unknown float option `H'/
@@ -80,7 +80,7 @@
 		regexToMatch: /LaTeX Error: Unknown float option `.+'/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_float_option_%60H%27."
 		humanReadableHint: """
-			You have used a float specifier which the compiler does not understand. You can learn more about the different float options available for placing figures here.
+			You have used a float specifier which the compiler does not understand. You can learn more about the different float options available for placing figures here.
 		"""
 	,
 		regexToMatch: /LaTeX Error: \\math.+ allowed only in math mode/


Unit tests pass again.

Unit tests pass again.

--- src/control.coffee
+++ src/control.coffee
@@ -416,11 +416,10 @@
         if result isnt `undefined`
           iteratorResult = result
           false
-
-  # Getter
-
-  # Method or setter
-        (if (iteratorResult isnt `undefined`) then iteratorResult else this)
+      if iteratorResult != `undefined`
+        iteratorResult # Getter
+      else
+        this # Method or setter

   ###
   Generic factory for a property getter/setter.
@@ -455,13 +454,12 @@
         newClass.extend
           className: json.className
           classHierarchy: json.className + " " + superclass.classHierarchy
-      if json.genericSupport
-        newClass.genericIfClassIs = json.className
-        ###
-             Create a copy of the JSON that doesn't include the reserved keys
-             (which go on the class). We use the remaining JSON values as
-             "inherited" on the class' prototype for use by render().
-        ###
+      newClass.genericIfClassIs = json.className  if json.genericSupport
+      ###
+           Create a copy of the JSON that doesn't include the reserved keys
+           (which go on the class). We use the remaining JSON values as
+           "inherited" on the class' prototype for use by render().
+      ###
       inherited = copyExcludingKeys(json,
         className: true
         genericSupport: true


catbug() returns Module instance

catbug() returns Module instance

--- src/core.js.coffee
+++ src/core.js.coffee
@@ -6,7 +6,7 @@

     constructor: (@name, @rootSelector, @elements, @builder) ->

-    buildElements: (context) ->
+    _buildElements: (context) ->
       result = {}
       for info in @elements
         result[info.name] = top.element.create(info.selector, context)
@@ -16,7 +16,7 @@
       el = $ el
       dataKey = "catbug-#{@name}"
       unless el.data dataKey
-        context = top.builderContext.create(el, @buildElements el)
+        context = top.builderContext.create(el, @_buildElements el)
         el.data dataKey, @builder.call(context, context)
       el.data dataKey

@@ -41,6 +41,8 @@

     $ module.initAll

+    module
+
   top.init = (names) ->
     if _.isString names
       names = names.split ' '


comment comment out

comment comment out


ADD: show/hide event for popover

ADD: show/hide event for popover

--- lib/assets/javascripts/tao/ui/popover/element.coffee
+++ lib/assets/javascripts/tao/ui/popover/element.coffee
@@ -51,8 +51,10 @@
     if @active
       @refresh()
       @_enableAutoHide() if @autoHide
+      @trigger 'show'
     else
       @_disableAutoHide() if @autoHide
+      @trigger 'hide'

   _enableAutoHide: ->
     $(document).on "mousedown.tao-popover-#{@taoId}", (e) =>


fix a bug 在上传模块时，将文件都复制到临时目录中

fix a bug
在上传模块时，将文件都复制到临时目录中

--- lib/gitCreatePackage/gitCreatePackage-manager.coffee
+++ lib/gitCreatePackage/gitCreatePackage-manager.coffee
@@ -38,12 +38,14 @@
       gitCreatePackageWizardView.destroy()
       pv.attach()
       selectPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath
+
+      tmpfile = path.resolve os.tmpdir(), uuid.v1()
+
       if require('fs').statSync(selectPath).isFile()
-        tmpfile = path.resolve os.tmpdir(), uuid.v1()
-      else
-        tmpfile = os.tmpdir()
-
-      tmpDir = path.resolve tmpfile, path.basename(selectPath)
+        tmpDir = path.resolve tmpfile, path.basename(selectPath)
+      else
+        tmpDir = tmpfile
+
       if fs.existsSync tmpDir
         fs.removeSync tmpDir
       fs.copySync selectPath, tmpDir


fix logic error in "any image" preview scraper caused by strange coffeescript semantics

--- scripts/preview/any_image.coffee
+++ scripts/preview/any_image.coffee
@@ -13,7 +13,8 @@
     uri.path.match(PATTERN)?

   loadPreview: ->
-    $.ajax @uri, type: 'HEAD',
+    $.ajax @uri.toString(),
+      type: 'HEAD'
       success: (_, status, response) =>
         contentType = response.getResponseHeader 'Content-Type'
         if contentType in IMAGE_TYPES


better notification

better notification

--- src/validatorSRV.coffee
+++ src/validatorSRV.coffee
@@ -32,11 +32,11 @@
         thrownError = if xhr.status == 0 then "Cannot reach server" else thrownError
         n =
           type: "Server"
-          info: xhr.status
+          info: "Status: " + xhr.status
           message: thrownError
           location:
-            row: 0
-            column: 0
+            row: -1
+            column: -1
         @dispatcher.trigger 'notification:push', n

   # Override this, or provide your own validation handler if your validator returns a different response


subselect

subselect

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -74,6 +74,7 @@
     o 'Literal AS Literal',                               -> new Table($1, $3)
     o 'LEFT_PAREN List RIGHT_PAREN',                      -> $2
     o 'LEFT_PAREN Query RIGHT_PAREN',                     -> new SubSelect($2)
+    o 'LEFT_PAREN Query RIGHT_PAREN AS Literal',             -> new SubSelect($2, $5)
     o 'LEFT_PAREN Query RIGHT_PAREN Literal',             -> new SubSelect($2, $4)
     o 'Literal WINDOW WINDOW_FUNCTION LEFT_PAREN Number RIGHT_PAREN',
                                                           -> new Table($1, null, $2, $3, $5)


Fixed: the index/clear-all route couldn't be called

--- server/controllers/routes.coffee
+++ server/controllers/routes.coffee
@@ -146,6 +146,12 @@
     ]

     # Indexer management
+    'data/index/clear-all/':
+        'delete': [
+            utils.checkPermissionsFactory('all')
+            indexer.removeAll
+        ]
+
     'data/index/:id':
         post: [
             utils.lockRequest
@@ -154,7 +160,7 @@
             indexer.index
             utils.unlockRequest
         ]
-        delete: [
+        'delete': [
             utils.lockRequest
             utils.getDoc
             utils.checkPermissionsByDoc
@@ -163,10 +169,6 @@
         ]
     'data/search/:type': post: [utils.checkPermissionsByType, indexer.search]
     'data/search/': post: [utils.checkPermissionsFactory('all'), indexer.search]
-    'data/index/clear-all/': delete: [
-        utils.checkPermissionsFactory('all')
-        indexer.removeAll
-    ]

     # Mail management
     'mail/': post: [utils.checkPermissionsFactory('send mail'), mails.send]


Small fixes

Small fixes

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -332,11 +332,11 @@

   app.get ///system/factories.json///, (req, res) ->
     glob argv.c + '/**/factory.json', (e, files) ->
-      if e then return cb(e)
+      if e then return res.e(e)
       files = files.map (file) ->
         return fs.createReadStream(file).on('error', res.e).pipe(
           JSONStream.parse([false]).on 'root', (el) ->
-            @.emit 'data', el
+            @emit 'data', el
         )

       es.concat.apply(null, files)


In-line type signatures

In-line type signatures

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -371,6 +371,17 @@
     ,
       include: '#function_type_declaration'
     ,
+      match: '\\((?<paren>(?:[^()]|\\(\\g<paren>\\))*)(::|∷)(?<paren2>(?:[^()]|\\(\\g<paren2>\\))*)\\)'
+      captures:
+        1: patterns: [include: 'source.haskell']
+        2: name: 'keyword.other.double-colon.haskell'
+        3: patterns: [include: '#type_signature']
+    ,
+      match: '(::|∷)(.*)'
+      captures:
+        1: name: 'keyword.other.double-colon.haskell'
+        2: patterns: [include: '#type_signature']
+    ,
       match: /\b(Just|Left|Right|Nothing|True|False|LT|EQ|GT)(?!')\b/
       name: 'support.tag.haskell'
     ,


More accurate inline type signature match

Closes #64

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -366,7 +366,7 @@
         2: name: 'keyword.other.double-colon.haskell'
         3: {name: 'meta.type-signature.haskell', patterns: [include: '#type_signature']}
     ,
-      match: '(::|∷)(.*)'
+      match: '(::|∷)((?:{className}|{functionName}|\\->|=>|→|⇒|\\s)*)'
       captures:
         1: name: 'keyword.other.double-colon.haskell'
         2: {name: 'meta.type-signature.haskell', patterns: [include: '#type_signature']}


FIX: no wheel for TextInputAreas

FIX: no wheel for TextInputAreas

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -100,7 +100,7 @@
         textAlign:        options.align || "left"
         verticalAlign:    "bottom"

-      on: logEventErrors
+      on: merge
         cut:      (keyboardEvent) => @delayedCheckIfValueChanged()
         paste:    (keyboardEvent) => @delayedCheckIfValueChanged()
         drop:     (keyboardEvent) => @delayedCheckIfValueChanged()
@@ -137,7 +137,7 @@
               catch error
                 log TextInputElement: blurHandler: {error}

-        wheel: !isTextarea && (domEvent) =>
+        wheel: unless isTextarea then (domEvent) =>
           @canvasElement._handleDomWheelEvent domEvent

     super


:lipstick:

:lipstick:

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -167,7 +167,7 @@
         keyIdentifier = key[0].toUpperCase() + key[1..]

   location ?= KeyboardEvent.DOM_KEY_LOCATION_STANDARD
-  event.initKeyboardEvent('keydown', bubbles, cancelable, view,  keyIdentifier, location, ctrl, alt, shift, cmd)
+  event.initKeyboardEvent('keydown', bubbles, cancelable, view, keyIdentifier, location, ctrl, alt, shift, cmd)
   Object.defineProperty(event, 'target', get: -> target) if target?

   which ?= keyCode


Do os.platform() check only once.

Do os.platform() check only once.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -59,14 +59,16 @@
       return callback log
     callback null, stdout

+isWindows = os.platform() is 'win32'
+
 exports.replaceSlashes = replaceSlashes = (_) ->
-  if os.platform() is 'win32'
+  if isWindows
     _.replace(/\//g, '\\')
   else
     _

 exports.replaceBackSlashes = replaceBackSlashes = (_) ->
-  if os.platform() is 'win32'
+  if isWindows
     _.replace(/\\/g, '\/')
   else
     _


Added some example doc tags

Added some example doc tags

--- src/detectr.coffee
+++ src/detectr.coffee
@@ -11,6 +11,8 @@

   ###
     Default configuration for detectr
+
+    @mixin Default tests
   ###
   defaultTests =
     tests:
@@ -125,6 +127,8 @@

   ###
     detectr constructor
+
+    @method detectr
   ###
   detectr = (config, options) ->
     # Make sure the config object exists and it has some tests in it


removing slugs from schema

removing slugs from schema

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -22,7 +22,6 @@
   author_id: @objectId().required()
   tier: @number().default(2)
   slug: @string().allow('', null)
-  slugs: @array().items(@string()).allow('',null,[])
   thumbnail_title: @string().allow('', null)
   thumbnail_teaser: @string().allow('', null)
   thumbnail_image: @string().allow('', null)


Fix stats counter

Fix stats counter

--- src/coffee/import/products.coffee
+++ src/coffee/import/products.coffee
@@ -57,12 +57,13 @@
             oldProduct = fetchedProductResult?.body?.results?[0]
             throw new Error "Could not find product to update by attribute name: '#{productIdMapping}' and value: '#{attr.value}' in SPHERE.IO" if not oldProduct
             @logger.info "[Products] About to update product with id: '#{attr.value}', counter: '#{@productsUpdated}'"
-            @productsUpdated++
             update = @productSync.buildActions(p[0], oldProduct).get()
             Q()
             # TODO: activate once SPHERE fixes delete and add variant with unique constraint attribute in one update action
             # TODO: make sure variants are not dropped and created from scratch but updated only.
             #@client.products.byId(oldProduct.id).update(update)
+            #.then ->
+            #  @productsUpdated++
     .then (updateProductsResult) =>
       productCreates = @products.creates
       if _.size(productCreates) > 0
@@ -76,9 +77,10 @@
               oldProduct = fetchedProductResult?.body?.results[0]
               if not oldProduct
                 @logger.info "[Products] About to create product with id: '#{attr.value}', counter: '#{@productsCreated}'"
-                @productsCreated++
                 # product does not exist yet, create it
                 @client.products.save(p[0])
+                .then ->
+                  @productsCreated++
               else
                 @productsCreateSkipped++
                 Q()


Changed testpie colors

Changed testpie colors


Minor fixes in the networking protocol

Minor fixes in the networking protocol

--- library/visualize/monitor.coffee
+++ library/visualize/monitor.coffee
@@ -72,7 +72,8 @@
             missing = "Cannot resolve element #{origin}"
             throw new Error missing unless resolved?
             wrapped.foreign yes if wrapped.foreign?
-            @element.emit event, resolved, wrapped
+            @element().emit event, resolved, wrapped
+            @transport().emit "acknowledge", context

     # Attach one of the ends of the monitor to the previously bound
     # element instance. This method will attach the specific event
@@ -88,7 +89,7 @@
             throw new Error wrapping unless wrapped
             return undefined if context.foreign()
             unwrapped = context.unwrap() or undefined
-            @transport.emit event, tagging, unwrapped
+            @transport().emit event, tagging, unwrapped

     # Set the transport channel of this session. If the transport is
     # not supplied, the method will return the transport instead of


mock gaprov shadow email hack

mock gaprov shadow email hack

--- mock.coffee
+++ mock.coffee
@@ -72,13 +72,17 @@
     resource = @constructor.name.toLowerCase() + 's'
     resource_singular = _(resource).rtrim('s')
     id = @_properties.id
-    return cb(new Error("404")) unless _(clever.db[resource]).findWhere({id:id})
+    clever_resource = _(clever.db[resource]).findWhere({id:id})
+    return cb(new Error("404")) unless clever_resource?
     prop_obj = _(clever.db["#{resource_singular}properties"]).findWhere(_.object [[resource_singular, id]])
     if not prop_obj
       prop_obj = _.object [[resource_singular, id], ['data', {}]]
       clever.db["#{resource_singular}properties"].push prop_obj
     if obj
       dotty.put prop_obj.data, k, v for k, v of obj
+      if obj.google_apps?.username?
+        clever_resource._shadow ?= {}
+        clever_resource._shadow.email = obj.google_apps.username
     return cb null, prop_obj.data

   clever


course: delete assignment; show deleted assignments

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -24,8 +24,6 @@
 ###
 TODO:

-- [ ] (0:45?) (0:37) delete student; show deleted students
-- [ ] (0:45?) delete assignment; show deleted assignments
 - [ ] (1:00?) help page -- integrate info
 - [ ] (1:00?) changing title/description needs to change it for all projects
 - [ ] (1:00?) clean up after flux/react when closing the editor
@@ -40,6 +38,8 @@
 - [ ] (1:00?) (0:19+) fix bugs in opening directories in different projects using actions -- completely busted right now due to refactor of directory listing stuff....

 DONE:
+- [x] (0:45?) (0:07) delete assignment; show deleted assignments
+- [x] (0:45?) (0:37) delete student; show deleted students
 - [x] (0:30?) (0:18) when searching, show how many things are not being shown.
 - [x] (0:30?) (0:15) when adding assignments filter out folders contained in existing assignment folders
 - [x] (1:00?) (0:12) assignment collect; don't allow until after assigned, etc. -- FLOW
@@ -1146,15 +1146,26 @@
     delete_assignment: ->
         @props.flux.getActions(@props.name).delete_assignment(@props.assignment)

+    undelete_assignment: ->
+        @props.flux.getActions(@props.name).undelete_assignment(@props.assignment)
+
     render_delete_button: ->
-        <Button onClick={@delete_assignment}>
-            <Icon name="trash" /> Delete
-        </Button>
+        if @props.assignment.get('deleted')
+            <Button onClick={@undelete_assignment}>
+                <Icon name="trash-o" /> Undelete
+            </Button>
+        else
+            <Button onClick={@delete_assignment}>
+                <Icon name="trash" /> Delete
+            </Button>

     render_summary_line: ->
         <Row style={entry_style}>
-            <Col md=3>
-
e.preventDefault();@setState(more:true)}>{@props.assignment.get('path')}

+            <Col md=4>
+

+                    e.preventDefault();@setState(more:true)}>{@props.assignment.get('path')}
+                    { (deleted) if @props.assignment.get('deleted')}
+

             </Col>
         </Row>

@@ -1182,6 +1193,7 @@
         add_searching : false      # whether or not it is asking the backend for the result of a search
         add_select    : undefined  # contents to put in the selection box after getting search result back
         add_selected  : ''         # specific path name in selection box that was selected
+        show_deleted  : false      # whether or not to show deleted assignments on the bottom

     clear_and_focus_assignment_search_input: ->
         @setState(search : '')
@@ -1321,7 +1333,15 @@
             v = (x for x in v when matches(x))
         v.sort (a,b) ->
             return misc.cmp(a.path.toLowerCase(), b.path.toLowerCase())
-        return {assignments:v, num_omitted:num_omitted}
+
+        # Deleted assignments
+        w = (x for x in v when x.deleted)
+        num_deleted = w.length
+        v = (x for x in v when not x.deleted)
+        if @state.show_deleted  # but show at the end...
+            v = v.concat(w)
+
+        return {assignments:v, num_omitted:num_omitted, num_deleted:num_deleted}

     render_assignments: (assignments) ->
         for x in assignments
@@ -1331,10 +1351,17 @@
                     name={@props.name}
                     />

+    render_show_deleted: (num_deleted) ->
+        if @state.show_deleted
+            <Button onClick={=>@setState(show_deleted:false)}>Hide {num_deleted} deleted assignments</Button>
+        else
+            <Button onClick={=>@setState(show_deleted:true)}>Show {num_deleted} deleted assignments</Button>
+
     render :->
-        {assignments, num_omitted} = @compute_assignment_list()
+        {assignments, num_omitted, num_deleted} = @compute_assignment_list()
         <Panel header={@render_header(num_omitted)}>
             {@render_assignments(assignments)}
+            {@render_show_deleted(num_deleted) if num_deleted}
         </Panel>

 Settings = rclass


adding cancel button when copying publicly

--- salvus/page/project_files.cjsx
+++ salvus/page/project_files.cjsx
@@ -1055,7 +1055,21 @@
         size = @props.checked_files.size
         signed_in = @props.flux.getStore('account').get_user_type() == 'signed_in'
         if @props.public_view and not signed_in
-            <LoginLink />
+

+                <LoginLink />
+                <Row>
+                    <Col sm=12>
+                        <ButtonToolbar>
+                            <Button bsStyle='primary' disabled={true}>
+                                <Icon name='files-o' /> Copy {size} {misc.plural(size, 'item')}
+                            </Button>
+                            <Button onClick={@cancel_action}>
+                                Cancel
+                            </Button>
+                        </ButtonToolbar>
+                    </Col>
+                </Row>
+

         else

                 <Row>


Dumbest bug ever. Fix latex autocomplete.

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -81,7 +81,6 @@
 									callback null, result

 			@editor.completers = [@suggestionManager, SnippetCompleter, ReferencesCompleter]
-			@editor.completers = [SnippetCompleter, ReferencesCompleter]

 		disable: () ->
 			@editor.setOptions({


CLI better 'list' command output

CLI better 'list' command output

--- cli/commands/list.coffee
+++ cli/commands/list.coffee
@@ -18,7 +18,9 @@
         unless version is 'current'
           versions.push(version)

-      print " #{name.ljust(16)} #{versions.sort().join(' ').grey}"
+      versions = versions.sort().reverse().slice(1)
+
+      print " #{name.ljust(16)} #{versions.join(', ').grey}"

 exports.help = (args) ->


fix non-existent module injection doubfire.users

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -59,7 +59,6 @@
   "doubtfire.units"
   "doubtfire.tasks"
   "doubtfire.projects"
-  "doubtfire.users"
 ])
 .config(($urlRouterProvider, $httpProvider) ->


Utils: Increase timeout for getLocationInfo

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -879,7 +879,7 @@
       $.ajax
         url      : '//freegeoip.net/json/?callback=?'
         error    : fail
-        timeout  : 1500
+        timeout  : 5000
         dataType : 'json'
         success  : (data)->


Fixed: search only route couldn't be loaded directly

--- client/app/router.coffee
+++ client/app/router.coffee
@@ -26,6 +26,9 @@

     mainSearch: (query) ->
         TaskActionCreator.setSearchQuery query
+        TaskActionCreator.setArchivedMode false
+        TagActionCreator.selectTags null
+        React.renderComponent App(), $('body')[0]

     archived: ->
         TaskActionCreator.setArchivedMode true


part button text can update during save

part button text can update during save

--- src/components/exercise/part.cjsx
+++ src/components/exercise/part.cjsx
@@ -113,7 +113,7 @@
   # add get props methods for different panel types as needed here

   render: ->
-    {id, step, waitingText} = @props
+    {id, step, getWaitingText, waitingText} = @props
     {currentPanel} = @state

     # panel is one of ['review', 'multiple-choice', 'free-response', 'teacher-read-only']
@@ -124,7 +124,7 @@
       {...cardProps}
       step={step}
       panel={currentPanel}
-      waitingText={waitingText}
+      waitingText={getWaitingText?(id) or waitingText}
     />

 module.exports = Exercise


removed console.log

removed console.log

--- app/_src/pjaxnavigation.coffee
+++ app/_src/pjaxnavigation.coffee
@@ -67,7 +67,6 @@
         ease: Circ.easeInOut
         clearProps: 'all'
         onComplete: =>
-          console.log('hide hero')
           @$hero.addClass('hero--hidden')
       })


Add null check for outputFiles array

Add null check for outputFiles array

--- app/coffee/BuildManager.coffee
+++ app/coffee/BuildManager.coffee
@@ -51,7 +51,7 @@
 			return callback(error) if error?

 			jobs = []
-			for file in outputFiles
+			for file in outputFiles or []
 				do (file) ->
 					jobs.push (callback) ->
 						BuildManager._saveOutputFileToS3 repo, sha, file.url, callback


split pack handling code into separate parts for docs and projects need to debug projects

--- app/coffee/MongoManager.coffee
+++ app/coffee/MongoManager.coffee
@@ -84,16 +84,15 @@
 	#  are those of the first entry in the pack D1 (this makes it
 	#  possible to treat packs and single updates in the same way).

-	_findResults: (param, query, limit, callback) ->
-		# param - the field used to select and sort ops within a range,
-		# either 'v' or 'meta.end_ts'
+
+	_findDocResults: (query, limit, callback) ->
 		# query - the mongo query selector, includes both the doc_id/project_id and
-		# the range on v or meta.end_ts
+		# the range on v
 		# limit - the mongo limit, we need to apply it after unpacking any
 		# packs

 		sort = {}
-		sort[param] = -1;
+		sort['v'] = -1;
 		cursor = db.docHistory
 			.find( query )
 			.sort( sort )
@@ -102,15 +101,15 @@
 			cursor.limit(limit)

 		# take the part of the query which selects the range over the parameter
-		rangeQuery = query[param]
+		rangeQuery = query['v']

 		# helper function to check if an item from a pack is inside the
 		# desired range
 		filterFn = (item) ->
-			return false if rangeQuery?['$gte']? && item[param] < rangeQuery['$gte']
-			return false if rangeQuery?['$lte']? && item[param] > rangeQuery['$lte']
-			return false if rangeQuery?['$lt']? && item[param] >= rangeQuery['$lt']
-			return false if rangeQuery?['$gt']? && item[param] <= rangeQuery['$gt']
+			return false if rangeQuery?['$gte']? && item['v'] < rangeQuery['$gte']
+			return false if rangeQuery?['$lte']? && item['v'] > rangeQuery['$lte']
+			return false if rangeQuery?['$lt']? && item['v'] >= rangeQuery['$lt']
+			return false if rangeQuery?['$gt']? && item['v'] <= rangeQuery['$gt']
 			return true

 		# create a query which can be used to select the entries BEFORE
@@ -122,25 +121,25 @@
 		# $gt and $gte (i.e. we need to find packs which start before our
 		# range but end in it)
 		if rangeQuery?['$gte']?
-			extraQuery[param] = {'$lt' : rangeQuery['$gte']}
+			extraQuery['v'] = {'$lt' : rangeQuery['$gte']}
 		else if rangeQuery?['$gt']
-			extraQuery[param] = {'$lte' : rangeQuery['$gt']}
+			extraQuery['v'] = {'$lte' : rangeQuery['$gt']}
 		else
-			delete extraQuery[param]
+			delete extraQuery['v']

 		needMore = false  # keep track of whether we need to load more data
 		updates = [] # used to accumulate the set of results
 		cursor.toArray (err, result) ->
 			unpackedSet = MongoManager._unpackResults(result)
-			MongoManager._filterAndLimit(updates, unpackedSet, filterFn, limit)
+			updates = MongoManager._filterAndLimit(updates, unpackedSet, filterFn, limit)
 			# check if we need to retrieve more data, because there is a
 			# pack that crosses into our range
 			last = if unpackedSet.length then unpackedSet[unpackedSet.length-1] else null
 			if limit? && updates.length == limit
 				needMore = false
-			else if extraQuery[param]? && last? && filterFn(last)
+			else if extraQuery['v']? && last? && filterFn(last)
 				needMore = true
-			else if extraQuery[param]? && updates.length == 0
+			else if extraQuery['v']? && updates.length == 0
 				needMore = true
 			if needMore
 				# we do need an extra result set
@@ -153,13 +152,94 @@
 						return callback err, updates
 					else
 						extraSet = MongoManager._unpackResults(result2)
-						MongoManager._filterAndLimit(updates, extraSet, filterFn, limit)
+						updates = MongoManager._filterAndLimit(updates, extraSet, filterFn, limit)
 						callback err, updates
 				return
 			if err?
 				callback err, result
 			else
 				callback err, updates
+
+	_findProjectResults: (query, limit, callback) ->
+		# query - the mongo query selector, includes both the doc_id/project_id and
+		# the range on v or meta.end_ts
+		# limit - the mongo limit, we need to apply it after unpacking any
+		# packs
+
+		sort = {}
+		sort['meta.end_ts'] = -1;
+		cursor = db.docHistory
+			.find( query, {"op":false, "pack.op": false} ) # no need to return the op only need version info
+			.sort( sort )
+		# if we have packs, we will trim the results more later after expanding them
+		if limit?
+			cursor.limit(limit)
+
+		# take the part of the query which selects the range over the parameter
+		before = query['meta.end_ts']?['$lt']  # may be null
+
+		updates = [] # used to accumulate the set of results
+		extraQuery = _.clone(query)
+
+		cursor.toArray (err, result) ->
+			if err?
+				return callback err, result
+			if result.length == 0 && not before?  # no results and no time range specified
+				return callback err, result
+
+			unpackedSet = MongoManager._unpackResults(result)
+			if limit?
+				unpackedSet = unpackedSet.slice(0, limit)
+			# find the end time of the last result, we will take all the
+			# results up to this, and then all the changes at that time
+			# (without imposing a limit)
+			cutoff = if unpackedSet.length then unpackedSet[unpackedSet.length-1].meta.end_ts else before
+			console.log 'before is', before
+			console.log 'cutoff is', cutoff
+			console.log 'limit  is', limit
+
+			filterFn = (item) ->
+				ts = item?.meta?.end_ts
+				return false if before? && ts >= before
+				return false if cutoff? && ts < cutoff
+				return true
+
+			updates = MongoManager._filterAndLimit(updates, unpackedSet, filterFn, limit)
+			console.log 'initial updates are', updates
+			# now find any extra entries which are exactly at the last time (cutoff)
+			# or in packs that overlap it
+			extraQuery['meta.end_ts'] = {"$gte": +cutoff}
+			extraQuery['meta.start_ts'] = {"$lte": +cutoff}
+			# we don't specify a limit here, as there could be any number of
+			# docs at that timestamp
+			#
+			# NB. need to catch items in original query and followup query for duplicates
+			console.log 'extraQuery is', extraQuery
+			extra = db.docHistory
+				.find(extraQuery, {"op": false, "pack.op": false})
+				.sort(sort)
+			extra.toArray (err, result2) ->
+				console.log 'got extra result', err, result
+				if err?
+					return callback err, updates
+				else
+					extraSet = MongoManager._unpackResults(result2)
+					# note: final argument is null, no limit applied because we
+					# need all the updates at the final time to avoid breaking
+					# the changeset into parts
+					updates = MongoManager._filterAndLimit(updates, extraSet, filterFn, null)
+					# remove duplicates
+					seen = {}
+					updates = updates.filter (item) ->
+						key = item.doc_id + ' ' + item.v
+						console.log 'key is', key
+						if seen[key]
+							return false
+						else
+							seen[key] = true
+					console.log 'extra updates are', updates
+					callback err, updates
+

 	_unpackResults: (updates) ->
 		#	iterate over the updates, if there's a pack, expand it into ops and
@@ -183,12 +263,12 @@
 			item
 		return result.reverse()

-	_filterAndLimit: (results, extra, filterFn, limit)
+	_filterAndLimit: (results, extra, filterFn, limit) ->
 		# update results with extra docs, after filtering and limiting
 		filtered = extra.filter(filterFn)
 		newResults = results.concat filtered
 		newResults.slice(0, limit) if limit?
-		results = newResults
+		return newResults

 	getDocUpdates:(doc_id, options = {}, callback = (error, updates) ->) ->
 		query =
@@ -200,7 +280,7 @@
 			query["v"] ||= {}
 			query["v"]["$lte"] = options.to

-		MongoManager._findResults('v', query, options.limit, callback)
+		MongoManager._findDocResults(query, options.limit, callback)

 	getProjectUpdates: (project_id, options = {}, callback = (error, updates) ->) ->
 		query =
@@ -209,7 +289,7 @@
 		if options.before?
 			query["meta.end_ts"] = { $lt: options.before }

-		MongoManager._findResults('meta.end_ts', query, options.limit, callback)
+		MongoManager._findProjectResults(query, options.limit, callback)

 	backportProjectId: (project_id, doc_id, callback = (error) ->) ->
 		db.docHistory.update {


Fix login and register function in Auth service

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -69,11 +69,11 @@
     ## Http interface
     ###################

-    login: (data) ->
+    login: (data, type) ->
         url = @urls.resolve("auth")

         data = _.clone(data, false)
-        data.type = "normal"
+        data.type = if type then type else "normal"

         return @http.post(url, data).then (data, status) =>
             user = @model.make_model("users", data.data)
@@ -81,11 +81,12 @@
             @.setUser(user)
             return user

-    register: (data) ->
+    register: (data, type) ->
         url = @urls.resolve("auth-register")

         data = _.clone(data, false)
-        data.type = "public"
+        data.type = if type then type else "public"
+        data.existing = false

         return @http.post(url, data).then (response) =>
             user = @model.make_model("users", response.data)


:art:

:art:

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -216,7 +216,7 @@
         callback(isMoveTargetValidErr)
         return

-      if !isTargetValid
+      unless isTargetValid
         error = new Error("'#{target}' already exists.")
         error.code = 'EEXIST'
         callback(error)
@@ -243,9 +243,7 @@
       throw error

     targetParentPath = path.dirname(target)
-    if !fs.existsSync(targetParentPath)
-      fsPlus.makeTreeSync(targetParentPath)
-
+    fsPlus.makeTreeSync(targetParentPath) unless fs.existsSync(targetParentPath)
     fs.renameSync(source, target)

   # Public: Removes the file or directory at the given path synchronously.
@@ -448,7 +446,7 @@
   # extensions, otherwise it's undefined.
   resolveExtension: (pathToResolve, extensions) ->
     for extension in extensions
-      if extension == ""
+      if extension is ""
         return fsPlus.absolute(pathToResolve) if fsPlus.existsSync(pathToResolve)
       else
         pathWithExtension = pathToResolve + "." + extension.replace(/^\./, "")
@@ -565,7 +563,7 @@
       return

     fs.stat target, (newErr, newStat) ->
-      if newErr and newErr.code == 'ENOENT'
+      if newErr and newErr.code is 'ENOENT'
         callback(undefined, true) # new path does not exist so it is valid
         return

@@ -580,8 +578,7 @@
   oldStat = statSyncNoException(source)
   newStat = statSyncNoException(target)

-  if oldStat is false or newStat is false
-    return true
+  return true unless oldStat and newStat

   # New path exists so check if it points to the same file as the initial
   # path to see if the case of the file name is being changed on a case


Fix java regex

Fix java regex

--- actions/java.coffee
+++ actions/java.coffee
@@ -20,7 +20,7 @@
     code_skipped: 1
   , (err, installed, stdout) ->
     return next err, ctx.PASS if err or not installed
-    packages = for l in stdout.trim().split('\n') then /(.*?) *$/.exec(l)[1]
+    packages = for l in stdout.trim().split('\n') then /(.*?) .*$/.exec(l)[1]
     ctx.execute
       cmd: "yum remove -y #{packages.join ' '}"
     , (err) ->


fix link

fix link

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -13,7 +13,7 @@
       id = matches[0]

     if $item.resource_uri.search("production") != -1
-      $state.go("artwork", {id: id})
+      $state.go("artwork-detail", {id: id})

     else if $item.resource_uri.search("student") != -1
       $state.go("school.student", {id: id})


modify transmission format

modify transmission format

--- app/scripts/robot/radio.coffee
+++ app/scripts/robot/radio.coffee
@@ -86,13 +86,13 @@
               _ndl3Radio.send(object, 'code')
             else
               object._channel = channel
-              _ndl3Radio.send(channel: object)
+              _ndl3Radio.send(object)
           else
             console.log "_ndl3Radio not defined"
             return false

-          console.log "radio channel 'chname': fake sent object"
-          .replace(/object/, String(object))
+          console.log "radio channel 'chname': sent \nobject"
+          .replace(/object/, JSON.stringify(object, null, 4))
           .replace(/chname/, channel)
         return true
     }


Fix double socket close issue

Fix double socket close issue

Fix thanks to @Timer on github.

--- drivers/javascript/net.coffee
+++ drivers/javascript/net.coffee
@@ -1133,7 +1133,7 @@
                         # called in response to the rawSocket 'close'
                         # event, we need to additionally clean up the
                         # rawSocket.
-                        @rawSocket.removeAllListeners()
+                        @rawSocket?.removeAllListeners()
                         @rawSocket = null
                     )
                     @rawSocket.end()


Fixed: Sort was broken

Fixed: Sort was broken

--- server/controllers/crud.coffee
+++ server/controllers/crud.coffee
@@ -57,6 +57,8 @@
             options.max = 0
           else
             options.max = v
+      else if k is 'sort'
+        options.sort = v
       else
         options.query[k] = v


Rename ctorTypeDeclaration group

Rename ctorTypeDeclaration group

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -61,7 +61,7 @@
       concat list('functionTypeDeclaration',/{functionName}|{operatorFun}/,/,/),
         /\s*(::|∷)/
     ctorTypeDeclaration:
-      concat list('functionTypeDeclaration',/{className}|{operatorFun}/,/,/),
+      concat list('ctorTypeDeclaration',/{className}|{operatorFun}/,/,/),
         /\s*(::|∷)/
     ctorArgs: ///
       (?!deriving)


whoa typo

whoa typo

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -6,8 +6,8 @@
 mkdirp} = require "fairmont"
 _write = write
 _jade = require "jade"
-_jade_coffee = "jstransformer-coffee-script"
-_jade_stylus = "jstransformer-stylus"
+_jade_coffee = require "jstransformer-coffee-script"
+_jade_stylus = require "jstransformer-stylus"
 _coffee = require "coffee-script"
 _stylus = require "stylus"
 _scss = require "node-sass"


Fix loading of coffee config

Fix loading of coffee config

Resolves #797

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -11,8 +11,8 @@
 debug = require('debug')('brunch:helpers')
 commonRequireDefinition = require 'commonjs-require-definition'
 anysort = require 'anysort'
-# Just require.
-require 'coffee-script'
+coffee = require 'coffee-script'
+coffee.register()

 # Extends the object with properties from another object.
 # Example


Bounded

Bounded

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -9,6 +9,24 @@

 Bounded = (I) ->
   I ||= {}
+
+  $.reverseMerge, I
+    x: 0
+    y: 0
+    width: 8
+    height: 8
+
+  ###*
+  The position of this game object, the top left point in the local transform.
+
+  @returns The position of this object
+  @type Point
+  ###
+  position: ->
+    Point(I.x, I.y)
+
+  collides: (bounds) ->
+    Collision.rectangular(I, bounds)

   ###*
   The bounds method returns infomation about the location


Simplifies execution and render call

Simplifies execution and render call

--- src/capsule.coffee
+++ src/capsule.coffee
@@ -10,14 +10,12 @@

       @data = {}

-    render: ->
-      return unless @template
-      @template @data
+      @on 'render', =>
+        return unless @template
+        @template @data

     execute: ->
-      @before?.apply? @, arguments
-      @render()
-      @after?.apply? @, arguments
+      @trigger.apply @ 'render', arguments

   Capsule.TemplateConnector = {}


Modificaions on match APIs.

Modificaions on match APIs.

--- src/matches.coffee
+++ src/matches.coffee
@@ -4,12 +4,11 @@

 module.exports =  (appCredentials, gamerCred, domain)->

-	list: (properties, options=null, cb)->
-		unless cb?
-			cb = options
-			options = null
+	#list: (properties, options=null, cb)->
+	list: (options, cb)->

-		queryoptions = "&properties=#{JSON.stringify(properties)}"
+		#queryoptions = "&properties=#{JSON.stringify(properties)}"
+		queryoptions = ""
 		queryoptions += "&full" if options?.full?
 		queryoptions += "&participating" if options?.participating?
 		queryoptions += "&finished" if options?.finished?
@@ -108,7 +107,7 @@
 			if err? then return cb(err)
 			if res.error then cb new ClanError res.status, res.body else cb null, res.body

-	move: (matchID, lastEventID, move, osn, cb)->
+	move: (matchID, lastEventID, move, globalState, osn, cb)->
 		if typeof osn is "function" # keep compatibility with previous version
 			cb = osn
 			osn = null
@@ -121,7 +120,7 @@
 		.set appCredentials
 		.auth gamerCred.gamer_id, gamerCred.gamer_secret
 		.type 'json'
-		.send move
+		.send { move, globalState }
 		.end (err, res)->
 			if err? then return cb(err)
 			if res.error then cb new ClanError res.status, res.body else cb null, res.body


Use coffeescript classes

Use coffeescript classes

--- www/tupelo.coffee
+++ www/tupelo.coffee
@@ -59,20 +59,21 @@
     this

   # Player
-  my.Player = (name) ->
-    @player_name = name
+  class my.Player
+    constructor: (name) ->
+      @player_name = name

-  my.Player::fromJSON = (json) ->
-    obj = eval("(" + json + ")")
-    @fromObj obj
+    fromJSON: (json) ->
+      obj = eval("(" + json + ")")
+      @fromObj obj

-  my.Player::fromObj = (obj) ->
-    for prop of obj
-      this[prop] = obj[prop]
-    this
+    fromObj: (obj) ->
+      for prop of obj
+        this[prop] = obj[prop]
+      this

-  my.Player::toString = ->
-    "" + @player_name + " (" + @id + ")"
+    toString: ->
+      "" + @player_name + " (" + @id + ")"

   # Card
   suits = [
@@ -102,18 +103,19 @@
       else
         value + ""

-  my.Card = (suit, value) ->
-    @suit = suit
-    @value = value
+  class my.Card
+    constructor: (suit, value) ->
+      @suit = suit
+      @value = value

-  my.Card::toString = ->
-    "" + valueToChar(@value) + " of " + suits[@suit].name
+    toString: ->
+      "" + valueToChar(@value) + " of " + suits[@suit].name

-  my.Card::toShortString = ->
-    "" + valueToChar(@value) + suits[@suit].html
+    toShortString: ->
+      "" + valueToChar(@value) + suits[@suit].html

-  my.Card::toShortHtml = ->
-    "" + @toShortString() + ""
+    toShortHtml: ->
+      "" + @toShortString() + ""

   my


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -25,7 +25,8 @@
 # => Sets up a health meter that will be drawn during the enemy overlay event.
 # Since maxHealth wasn't provided, it is set to the value of I.health (500)

-someOtherObject.meter 'health'
+someOtherObject.meter 'he'
+

 # now player has all the methods provided by this module
 player.position()


Adding the real method sig

Adding the real method sig

--- src/methods.coffee
+++ src/methods.coffee
@@ -55,7 +55,7 @@
       @static_value = null  # loaded in when getstatic is called

 native_methods = {
-  'arraycopy': ((rs) ->
+  'java/lang/System::arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V': ((rs) ->
     args = rs.curr_frame().locals
     src_array = rs.get_obj(args[0]).array
     src_pos = args[1]


Minor fix.

Minor fix.

--- src/methods.coffee
+++ src/methods.coffee
@@ -250,7 +250,7 @@
         o 'intern()L!/!/!;', (rs, _this) ->
             js_str = rs.jvm2js_str(_this)
             unless rs.string_pool[js_str]
-              rs.string_pool[js_str] = str_ref
+              rs.string_pool[js_str] = _this.ref
             rs.string_pool[js_str]
       ]
       System: [


misc: Use the Platform package

misc: Use the Platform package

--- src/js/misc.coffee
+++ src/js/misc.coffee
@@ -1,3 +1,5 @@
+Platform = require('platform')
+
 # prioritize objects on the right
 merge = (objects...) ->
   res = {}
@@ -23,7 +25,7 @@

 flatten = (arr) -> Array::concat(arr...)

-colorsAvailable = Pebble.getActiveWatchInfo?().platform == "basalt"
+colorsAvailable = Platform.version() == "basalt"

 module.exports =
   merge: merge


comments

comments

--- src/motions.coffee
+++ src/motions.coffee
@@ -147,6 +147,7 @@
       # Move to the `row` and `column` that have been determined.
       jim.adaptor.moveTo row, column

+# Move to the next beginning of a **WORD**.
 map 'W', class MoveToNextBigWord extends MoveToNextWord
   bigWord: yes

@@ -155,7 +156,7 @@
 lastWORDRegex = ///#{WORDRegex().source}\s*$///
 lastWordRegex = ///(#{wordRegex().source})\s*$///

-# move to the last beginning of a word
+# Move to the last beginning of a **word**.
 map 'b', class MoveBackWord extends Motion
   exclusive: yes
   exec: repeatCountTimes (jim) ->
@@ -167,12 +168,12 @@
     match = regex.exec leftOfCursor
     if match
       column = match.index
+
+    # If there are no matches left of the cursor, go to the last word on the
+    # previous line.  Vim skips lines that are have only whitespace on them, but
+    # not completely empty lines.
     else
-      # there are no matches left of the cursor
-      # go to the last word on the previous line
       row--
-      # Vim skips lines that are only whitespace
-      # (but not completely empty lines)
       row-- while /^\s+$/.test(line = jim.adaptor.lineText row)
       match = regex.exec line
       column = match?.index or 0
@@ -180,6 +181,7 @@
     # Move to the `row` and `column` that have been determined.
     jim.adaptor.moveTo row, column

+# Move to the last beginning of a **WORD**.
 map 'B', class MoveBackBigWord extends MoveBackWord
   bigWord: yes

@@ -187,12 +189,12 @@
 # Other left/right motions
 # ------------------------

-# move to the first column on the line
+# Move to the first column on the line.
 map '0', class MoveToBeginningOfLine extends Motion
   exclusive: yes
   exec: (jim) -> jim.adaptor.moveTo jim.adaptor.row(), 0

-# move to the first non-blank character on the line
+# Move to the first non-blank character on the line.
 map '^', class MoveToFirstNonBlank extends Motion
   exec: (jim) ->
     row = jim.adaptor.row()
@@ -200,7 +202,7 @@
     column = /\S/.exec(line)?.index or 0
     jim.adaptor.moveTo row, column

-# move to the last column on the line
+# Move to the last column on the line.
 map '$', class MoveToEndOfLine extends Motion
   exec: (jim) ->
     additionalLines = @count - 1
@@ -211,7 +213,7 @@
 # Jump motions
 # ------------

-# go to `{count}` line number or the first line
+# Go to `{count}` line number or the first line.
 map 'gg', class GoToLine extends Motion
   linewise: yes
   exec: (jim) ->
@@ -220,21 +222,21 @@
     jim.adaptor.moveTo rowNumber, 0
     new MoveToFirstNonBlank().exec jim

-# go to `{count}` line number or the last line
+# Go to `{count}` line number or the last line.
 map 'G', class GoToLineOrEnd extends GoToLine
   constructor: (@count) ->
   exec: (jim) ->
     @count or= jim.adaptor.lastRow() + 1
     super

-# go to the first line that's visible in the viewport
+# Go to the first line that's visible in the viewport.
 map 'H', class GoToFirstVisibleLine extends Motion
   linewise: yes
   exec: (jim) ->
     line = jim.adaptor.firstFullyVisibleRow() + @count
     new GoToLineOrEnd(line).exec jim

-# go to the middle line of the lines that exist and are visible in the viewport
+# Go to the middle line of the lines that exist and are visible in the viewport.
 map 'M', class GoToMiddleLine extends Motion
   linewise: yes
   exec: (jim) ->
@@ -243,7 +245,7 @@
     linesFromTop = Math.floor(lines / 2)
     new GoToLineOrEnd(topRow + 1 + linesFromTop).exec jim

-# go to the last line of the lines that exist and are visible in the viewport
+# Go to the last line of the lines that exist and are visible in the viewport.
 map 'L', class GoToLastVisibleLine extends Motion
   linewise: yes
   exec: (jim) ->


timeinput on order manage page had gotten turned off

--- app/assets/javascripts/app/manage_orders.coffee
+++ app/assets/javascripts/app/manage_orders.coffee
@@ -129,4 +129,5 @@

   $('.updated-order-detail').animateHighlight({ highlightClass: 'alert-info', solidDuration: 5000 })

+  $('.timeinput').timeinput();
   $('#product_add').chosen();


Keeps cursor at the correct place after changing text in dropzone

--- app/assets/javascripts/dropzone_input.js.coffee
+++ app/assets/javascripts/dropzone_input.js.coffee
@@ -65,13 +65,7 @@
         return

       success: (header, response) ->
-        link_markdown = response.link.markdown
-        child = $(dropzone[0]).children("textarea")
-        cursor_pos = child.prop "selectionStart"
-        value = $(child).val()
-        new_text = "#{value.substring(0,  cursor_pos)}#{link_markdown}#{value.substring(cursor_pos, value.length)}"
-
-        $(child).val new_text + "\n"
+        pasteText response.link.markdown
         return

       error: (temp, errorMessage) ->
@@ -133,6 +127,7 @@
       beforeSelection = $(child).val().substring 0, caretStart
       afterSelection = $(child).val().substring caretEnd, textEnd
       $(child).val beforeSelection + text + afterSelection
+      child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
       form_textarea.trigger "input"

     getFilename = (e) ->


embed: fix bug due to tags all change

embed: fix bug due to tags all change

--- app/assets/javascripts/embed/election.js.coffee
+++ app/assets/javascripts/embed/election.js.coffee
@@ -6,7 +6,7 @@
     app.models.election = new ElectionModel()

     app.models.election.bind 'change:id', (election)=>
-      election.fetch()
+      election.fetch data: {tags: 'all'}

     app.models.tag = new TagModel()


Fixed scroll position of select2 for mobile

--- app/assets/javascripts/initialization.js.coffee
+++ app/assets/javascripts/initialization.js.coffee
@@ -56,11 +56,11 @@
           $(this).attr( "data-height", $(this).find(".select2-results").height() )
           $(".select2-drop").addClass("height-checked")

-          #if $(".select2-dropdown-open").closest(".input").find("select").attr( "data-scroll" )
-          #  $(this).find(".select2-results").animate {
-          #    scrollTop: 0
-          #    $(".select2-dropdown-open").closest(".input").find("select").attr( "data-scroll" )
-          #  }, 0
+          opened_select_scrolled = $(".select2-dropdown-open").closest(".input").attr( "data-scroll" )
+          if opened_select_scrolled
+            $(this).find(".select2-results").animate {
+              scrollTop: opened_select_scrolled
+            }, 0
       ))
     )).on("select2-close", -> (
       $(".scheme-finder-frontend").removeClass("select2-open")
@@ -127,6 +127,7 @@
                 select_values.splice(select_values.indexOf(clicked_value), 1)
                 clicked_select.select2("close").select2("val", select_values).select2("open")
                 e.preventDefault()
-          #$(".scheme-finder-frontend header.page-header h1").text($(e.target).text()+":"+clicked_value)
-          #$(".select2-dropdown-open").closest(".input").find("select").attr( "data-scroll", $(e.target).closest(".select2-drop").find(".select2-results").scrollTop())
+          drop_scrolled = $(e.target).closest(".select2-drop").find(".select2-results").scrollTop()
+          if drop_scrolled
+            $(".select2-dropdown-open").closest(".input").attr("data-scroll", drop_scrolled)
   ))


removing standalone app js

removing standalone app js

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -1,24 +1,4 @@
 window.templates = {}
-
-@is_standalone_iOS_app = window.navigator.standalone? && window.navigator.standalone
-
-$ ->
-    if @is_standalone_iOS_app
-        if gon.auth_token?
-            if gon.auth_token is 'destroy'
-                localStorage.removeItem('auth_token')
-            else
-                localStorage.setItem('auth_token', gon.auth_token)
-
-        auth_token = localStorage.getItem('auth_token')
-
-        if !gon.authenticated and auth_token? and auth_token isnt 'destroy'
-            location.search = "auth_token=#{auth_token}"
-
-        $('a').click ->
-            return if ($ @).data('js-handle') is 'true'
-            location.href = ($ @).attr('href')
-            false

 $ ->
     window.has_touch_events = !($ 'html').hasClass('no-touch')


set title

set title

--- src/nvim/ui.coffee
+++ src/nvim/ui.coffee
@@ -218,7 +218,7 @@
           else
             for args in e[1..]
               handler.apply @, args
-        else console.log 'Redraw event not handled: ' + JSON.stringify e
+        else console.log 'Redraw event not handled: ' + e.toString()
       catch ex
         console.log 'Error when processing event!'
         console.log e.toString()
@@ -314,6 +314,10 @@
       @scroll_right - @scroll_left + 1, \
       clr_bottom - clr_top + 1

+  nv_set_title: (title) ->
+    match = /(.*) - VIM$/.exec(title)
+    document.title = if match? then match[1] + ' - Neovim.AS' else title
+
   nv_set_scroll_region: (top, bottom, left, right) ->
     @scroll_top = top
     @scroll_bottom = bottom


Forgot to account for doubles/longs.

Forgot to account for doubles/longs.

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -660,10 +660,17 @@
   181: new root.FieldOpcode 'putfield', { execute: (rs) ->
     # Check if the object is null; if we do not do this before get_class, then
     # we might try to get a class that we have not initialized!
-    _val = rs.pop()
-    _obj = rs.check_null(rs.pop())
-    rs.push _obj
-    rs.push _val
+    if @field_spec.type not in ['J','D']
+      _val = rs.pop()
+      _obj = rs.check_null(rs.pop())
+      rs.push _obj
+      rs.push _val
+    else
+      _val = rs.pop2()
+      _obj = rs.check_null(rs.pop())
+      rs.push _obj
+      rs.push2 _val, null
+
     cls_obj = rs.get_class(@field_spec.class)
     field = cls_obj.field_lookup(rs, @field_spec)
     name = field.cls.get_type() + @field_spec.name


Server: fix disconnection

Server: fix disconnection

--- components/Server.coffee
+++ components/Server.coffee
@@ -39,7 +39,7 @@
       @sendError("Cannot listen to port #{port} : #{e.message}")

   closeServer: (port) ->
-    unless @server[port]
+    unless @servers[port]
       @sendError("Port #{port} not listened to")
       return
     @servers[port].close()


Revert "Restore theme find!"

Revert "Restore theme find!"

This reverts commit df31febb017a509f5a97e06902c3da263a334710.

--- server/test/units/theme.coffee
+++ server/test/units/theme.coffee
@@ -96,8 +96,6 @@

     assert.ok themeSpy.calledOnce, "Expected Theme.find to be called once"
     assert.ok themeSpy.calledWith(filter), "Expected Theme.find to be called with the filter"
-
-    themeSpy.restore()
     done()
   )
 )


grammarsPromise not grammarPromise

grammarsPromise not grammarPromise

--- src/package.coffee
+++ src/package.coffee
@@ -219,8 +219,8 @@
   deactivate: ->
     @activationDeferred?.reject()
     @activationDeferred = null
-    @grammarPromise?.reject()
-    @grammarPromise = null
+    @grammarsPromise?.reject()
+    @grammarsPromise = null
     @scopedPropertiesPromise?.reject()
     @scopedPropertiesPromise = null
     @unsubscribeFromActivationEvents()


add rejection to ready to submit task assessment without uploading documents

--- src/app/tasks/partials/directives/modals.coffee
+++ src/app/tasks/partials/directives/modals.coffee
@@ -38,8 +38,9 @@
     $scope.taskStatusLabel = statusLabels[$scope.task.status]

   $scope.triggerTransition = (status) ->
-    $scope.task.status = status
+    oldStatus = $scope.task.status
     Task.update({ id: $scope.task.id, trigger: status }).$promise.then (
+      # Success
       (value) ->
         $scope.task.status = value.status
         $modalInstance.close(status)
@@ -53,7 +54,12 @@
             onChange()
         else
           alertService.add("danger", "Status change was not changed.", 2000)
-    )
+      ),
+      # Fail
+      (value) ->
+        $modalInstance.close(value.data.error)
+        $scope.task.status = oldStatus
+        alertService.add("danger", value.data.error, 4000)

   $scope.readyToAssessStatuses = ['ready_to_mark', 'not_submitted']


Button

Button

- Improved check for parameter "tabindex".

--- src/elements/Button/Button.coffee
+++ src/elements/Button/Button.coffee
@@ -68,6 +68,7 @@
 			@addClass("cui-button-button")

 		DOM.setAttributeMap(@DOM, @_attr)
+
 		DOM.setAttribute(@DOM, "tabindex", @_tabindex)

 		if not @_attr?.role
@@ -480,7 +481,9 @@
 		super()
 		@addOpts
 			tabindex:
-				default: "0"
+				default: 0
+				check: (v) ->
+					isInteger(v) or v == false
 			role:
 				default: "button"
 				check: String


:lipstick:

:lipstick:

--- spec/app/text-buffer-spec.coffee
+++ spec/app/text-buffer-spec.coffee
@@ -3,7 +3,7 @@
 fsUtils = require 'fs-utils'
 _ = require 'underscore'

-describe 'Buffer', ->
+describe 'TextBuffer', ->
   [filePath, fileContents, buffer] = []

   beforeEach ->


Use offset of results workflow to calculate right of context panel

--- src/coffee/cilantro/ui/workflows/results.coffee
+++ src/coffee/cilantro/ui/workflows/results.coffee
@@ -110,7 +110,7 @@
             # Find the bounds of the results workflow to properly fix the
             # position of the context/filter panel.
             @workflowTopOffset = @$el.offset().top
-            @workflowRightOffset = window.innerWidth - (@$el.position().left + @$el.width())
+            @workflowRightOffset = window.innerWidth - (@$el.offset().left + @$el.width())

             @ui.contextContainer.css('top', @workflowTopOffset)
             @ui.contextContainer.css('right', @workflowRightOffset)


Fix inline object literal formatting in model spec

--- spec/brainstem-model-spec.coffee
+++ spec/brainstem-model-spec.coffee
@@ -36,7 +36,7 @@
       context 'model is cached in storage manager', ->
         context '"cached" option is set to `false`', ->
           beforeEach ->
-            newModel = new App.Models.Task({ id: model.id}, { cached: false })
+            newModel = new App.Models.Task({ id: model.id }, { cached: false })

           itReturnsNewInstance()


Questions needs to be an array

Questions needs to be an array

Ref #17

--- config/kiosk-config.cson
+++ config/kiosk-config.cson
@@ -62,19 +62,22 @@
     " # format with Markdown
     select: "Tap Anywhere To Begin"
   }
-  questions: {
-    sex: {
+  questions: [
+    {
+      key: 'sex'
       title: "What is your gender?"
       why: "This helps us better estimate your health risks"
       select: {M: "Male", F: "Female", other: "Other"}
     }
-    dry_eyes: {
+    {
+      key: 'dry_eyes'
       title: "Do you have dry eyes?"
       description: "Do your eyes itch?  Do they have redness?"
       why: "This helps us better estimate your health risks"
       select: {yes: "Yes", no: "No", other: "Not Sure"}
     }
-    dry_eyes_duration: {
+    {
+      key: 'dry_eyes_duration'
       when: {dry_eyes: 'yes'}
       title: "How long have you had dry eyes?"
       description: "Do your eyes itch?  Do they have redness?"
@@ -86,6 +89,5 @@
         other: "Not sure"
       }
     }
-  # result = {sex: 'M', dry_yes: 'yes', dry_eyes_duration: 'm12'}
-  }
+  ]
 }