2 spaces indentation and Strict mode

2 spaces indentation and Strict mode

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,3 +1,7 @@
+# vim: ts=2 sw=2 sts=2
+
+"use strict"
+
 async = require 'async'
 fs = require 'fs'
 mime = require 'mime'
@@ -8,124 +12,124 @@

 # Parse command line options
 argv = require('optimist')
-    .usage('Usage: $0 -c [directory]')
-    .demand('c')
-    .alias('c', 'content')
-    .describe('c', 'Share the content of directory')
-    .argv
+  .usage('Usage: $0 -c [directory]')
+  .demand('c')
+  .alias('c', 'content')
+  .describe('c', 'Share the content of directory')
+  .argv

 dir = argv.c

 # Sort `files` after content type into separate arrays in an object.
 sortFiles = (dir, files, cb) ->
-    sortedFiles = {}
-    async.forEach files,
-        (file, cb) ->
-            fullPath = path.join dir, file
-            fs.stat fullPath, (err, stat) ->
-                type =
-                    if stat?.isDirectory() then 'folder'
-                    else if stat?.isFile() then mime.lookup(file).split('/')[0]
-                    else 'unknown'
-                (sortedFiles[type]?=[]).push fullPath
-                cb (if err? then err else null)
-        (err) -> cb err, sortedFiles
+  sortedFiles = {}
+  async.forEach files,
+    (file, cb) ->
+      fullPath = path.join dir, file
+      fs.stat fullPath, (err, stat) ->
+        type =
+          if stat?.isDirectory() then 'folder'
+          else if stat?.isFile() then mime.lookup(file).split('/')[0]
+          else 'unknown'
+        (sortedFiles[type]?=[]).push fullPath
+        cb (if err? then err else null)
+    (err) -> cb err, sortedFiles

 # Get the key of the biggest array in an object.
 getContainerType = (fileTypes) ->
-    maxVal = 0
-    for key, val of fileTypes when val.length > maxVal
-        maxVal = val
-        type = key
-    type
+  maxVal = 0
+  for key, val of fileTypes when val.length > maxVal
+    maxVal = val
+    type = key
+  type

 # Parse tags using musicmediadata module
 parseTags = (file, cb) ->
-    stream = fs.createReadStream file
-    stream.on 'error', (err) -> console.log "#{err.message} - #{stream.path}"
-    parser = new mmd stream
-    parser.on 'metadata', (data) ->
-        # Massage output a little. Will make it easier to switch parsing module
-        # in the future.
-        data.track = if data.track?.no > 0 then data.track.no else null
-        data.year = if data.year > 0 then data.year else null
-        data.genre = if data.genre?[0]? then data.genre[0] else null
-        data.artist = if data.artist?[0]? then data.artist[0] else null
-        data.albumartist = if data.albumartist?[0]? then data.albumartist[0] else null
-        cb data
-    parser.on 'done', (err) ->
-        console.log "#{err.message} - #{stream.path}" if err?
-        stream.destroy()
+  stream = fs.createReadStream file
+  stream.on 'error', (err) -> console.log "#{err.message} - #{stream.path}"
+  parser = new mmd stream
+  parser.on 'metadata', (data) ->
+    # Massage output a little. Will make it easier to switch parsing module
+    # in the future.
+    data.track = if data.track?.no > 0 then data.track.no else null
+    data.year = if data.year > 0 then data.year else null
+    data.genre = if data.genre?[0]? then data.genre[0] else null
+    data.artist = if data.artist?[0]? then data.artist[0] else null
+    data.albumartist = if data.albumartist?[0]? then data.albumartist[0] else null
+    cb data
+  parser.on 'done', (err) ->
+    console.log "#{err.message} - #{stream.path}" if err?
+    stream.destroy()

 mimeMap =
-    container:
-        audio: 'object.container.album.musicAlbum'
-        image: 'object.container.photoAlbum'
-    item:
-        audio: 'object.item.audioItem.musicTrack'
-        image: 'object.item.imageItem'
-        text: 'object.item.textItem'
+  container:
+    audio: 'object.container.album.musicAlbum'
+    image: 'object.container.photoAlbum'
+  item:
+    audio: 'object.item.audioItem.musicTrack'
+    image: 'object.item.imageItem'
+    text: 'object.item.textItem'

 # Make UPnP objects.
 makeObject = (base, type, file, cb) ->
-    getDir = if base is 'container' then path.dirname else (p) -> p
-    media =
-        class: mimeMap[base][type] or "object.#{base}"
-        location: getDir file
-    filename = path.basename getDir file
-    if type is 'audio'
-        parseTags file, (data) ->
-            if base is 'container'
-                media.creator = media.artist = data.albumartist or data.artist or 'Unknown'
-                media.title = data.album or filename
-            else
-                media.creator = media.artist = data.artist or 'Unknown'
-                media.title = data.title or filename
-                media.album = data.album or 'Untitled'
-                media.genre = data.genre if data.genre?
-                media.track = data.track if data.track?
-                media.date = data.year if data.year?
-            cb null, media
-    else
-        media.creator = 'Unknown'
-        media.title = filename
-        cb null, media
+  getDir = if base is 'container' then path.dirname else (p) -> p
+  media =
+    class: mimeMap[base][type] or "object.#{base}"
+    location: getDir file
+  filename = path.basename getDir file
+  if type is 'audio'
+    parseTags file, (data) ->
+      if base is 'container'
+        media.creator = media.artist = data.albumartist or data.artist or 'Unknown'
+        media.title = data.album or filename
+      else
+        media.creator = media.artist = data.artist or 'Unknown'
+        media.title = data.title or filename
+        media.album = data.album or 'Untitled'
+        media.genre = data.genre if data.genre?
+        media.track = data.track if data.track?
+        media.date = data.year if data.year?
+      cb null, media
+  else
+    media.creator = 'Unknown'
+    media.title = filename
+    cb null, media

 makeContainer = (sortedFiles, cb) ->
-    contentType = getContainerType sortedFiles
-    makeObject 'container', contentType, sortedFiles[contentType][0], cb
+  contentType = getContainerType sortedFiles
+  makeObject 'container', contentType, sortedFiles[contentType][0], cb

 makeItem = (type, file, cb) ->
-    makeObject 'item', type, file, cb
+  makeObject 'item', type, file, cb

 addContainer = (parentId, dir, cb) ->
-    fs.readdir dir, (err, files) ->
-        # Ignore hidden files.
-        files = files.filter (file) -> file[...1] isnt '.'
-        sortFiles dir, files, (err, sortedFiles) ->
-            makeContainer sortedFiles, (err, container) ->
-                mediaServer.addMedia parentId, container, (err, id) ->
-                    add id, dir, sortedFiles, cb
+  fs.readdir dir, (err, files) ->
+    # Ignore hidden files.
+    files = files.filter (file) -> file[...1] isnt '.'
+    sortFiles dir, files, (err, sortedFiles) ->
+      makeContainer sortedFiles, (err, container) ->
+        mediaServer.addMedia parentId, container, (err, id) ->
+          add id, dir, sortedFiles, cb

 addItem = (parentId, type, file, cb) ->
-    makeItem type, file, (err, item) ->
-        mediaServer.addMedia parentId, item, cb
+  makeItem type, file, (err, item) ->
+    mediaServer.addMedia parentId, item, cb

 add = (parentId, path, sortedFiles, cb) ->
-    async.forEachLimit Object.keys(sortedFiles), 5,
-        (type, cb) -> async.forEach sortedFiles[type],
-            (item, cb) ->
-                if type is 'folder'
-                    addContainer parentId, item, cb
-                else
-                    addItem parentId, type, item, cb
-            cb
-        (err) -> cb null
+  async.forEachLimit Object.keys(sortedFiles), 5,
+    (type, cb) -> async.forEach sortedFiles[type],
+      (item, cb) ->
+        if type is 'folder'
+          addContainer parentId, item, cb
+        else
+          addItem parentId, type, item, cb
+      cb
+    (err) -> cb null

 mediaServer = upnp.createDevice 'MediaServer', 'Bragi'
@@ -133,4 +137,4 @@
 mediaServer.on 'error', (e) -> throw e

 mediaServer.on 'ready', ->
-    addContainer 0, dir, (err, id) -> throw err if err?
+  addContainer 0, dir, (err, id) -> throw err if err?


Call openPanel instead of openPane

Call openPanel instead of openPane

--- lib/index.coffee
+++ lib/index.coffee
@@ -35,7 +35,7 @@
       openPanel('Keybindings')

     atom.workspaceView.command 'settings-view:change-themes', ->
-      openPane('Themes')
+      openPanel('Themes')

     atom.workspaceView.command 'settings-view:install-packages', ->
       openPanel('Packages')


Update index.coffee

Update index.coffee

--- lib/index.coffee
+++ lib/index.coffee
@@ -107,7 +107,7 @@

     filename = filename.toString()

-    headers['Accept-Encoding:'] = 'gzip'
+    headers['Accept-Encoding'] = 'gzip'

     @getFile filename, headers, (err, res) ->
       return cb err if err


feature: default constructor arguments

feature: default constructor arguments

--- lib/index.coffee
+++ lib/index.coffee
@@ -14,7 +14,7 @@
 class Symfio extends kantaina.Container
   _require = require

-  constructor: (@name, @applicationDirectory) ->
+  constructor: (@name = "symfio", @applicationDirectory = process.cwd()) ->
     super()

   require: (name, module = name) ->


allow plotter to handle modal operation codes. fixes #6

--- src/plotter.coffee
+++ src/plotter.coffee
@@ -37,6 +37,7 @@
     # operating mode
     @mode = null
     @quad = null
+    @lastOp = null
     @region = false
     @done = false
     # operation state (position and current region or trace path)
@@ -257,6 +258,11 @@

   # operate method takes the operation object
   operate: (op) ->
+    # get and set the lastOp as necessary
+    if op.do is 'last'
+      op.do = @lastOp
+      console.warn 'modal operation codes are deprecated'
+    else @lastOp = op.do
     # get the start position
     sx = @pos.x; sy = @pos.y
     # move the plotter position


Use the full name of the -G option

Use the full name of the -G option

--- src/project.coffee
+++ src/project.coffee
@@ -339,7 +339,7 @@
     command = require.resolve('.bin/nak')
     args = ['--hidden', '--ackmate', regex.source, @getPath()]
     ignoredNames = config.get('core.ignoredNames') ? []
-    args.unshift('-G', options.paths.join(',')) if options.paths and options.paths.length > 0
+    args.unshift('--pathInclude', options.paths.join(',')) if options.paths and options.paths.length > 0
     args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
     args.unshift('--ignoreCase') if regex.ignoreCase
     args.unshift('--addVCSIgnores') if config.get('core.excludeVcsIgnoredPaths')


Add per brand UI specific chat message styles.

--- services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -296,10 +296,14 @@
 	webRouter.use (req, res, next) ->
 		isOl = (Settings.brandPrefix == 'ol-')
 		res.locals.uiConfig =
-			defaultResizerSizeOpen   : if isOl then 2 else 24
-			defaultResizerSizeClosed : if isOl then 2 else 24
-			eastResizerCursor        : if isOl then "ew-resize" else null
-			westResizerCursor        : if isOl then "ew-resize" else null
-			chatResizerSizeOpen      : if isOl then 2 else 12
-			chatResizerSizeClosed    : 0
-		next()
+			defaultResizerSizeOpen     : if isOl then 2 else 24
+			defaultResizerSizeClosed   : if isOl then 2 else 24
+			eastResizerCursor          : if isOl then "ew-resize" else null
+			westResizerCursor          : if isOl then "ew-resize" else null
+			chatResizerSizeOpen        : if isOl then 2 else 12
+			chatResizerSizeClosed      : 0
+			chatMessageBorderSaturation: if isOl then 90 else 70
+			chatMessageBorderLightness : if isOl then 50 else 70
+			chatMessageBgSaturation    : if isOl then 90 else 60
+			chatMessageBgLightness     : if isOl then 50 else 97
+		next()


Catch attempts to set to invalid values

Catch attempts to set to invalid values

--- client/lanes/models/State.coffee
+++ client/lanes/models/State.coffee
@@ -43,8 +43,11 @@
         # Big decimal for attributes that need precision math
         bigdec:
             set: (newVal) ->
-                val = _.bigDecimal(newVal) unless _.isBlank(newVal)
-                { val, type: 'bigdec' }
+                try
+                    val = _.bigDecimal(newVal)
+                    { val, type: 'bigdec' }
+                catch
+                    {val: newVal}
             default: -> new _.bigDecimal(0)

         integer:


Temporarily disable topic follow/unfollow tests

--- tests/src/activity/others.coffee
+++ tests/src/activity/others.coffee
@@ -42,24 +42,24 @@
     browser.end()

-  followTopic: (browser) ->
+  # followTopic: (browser) ->

-    helpers.doFollowTopic(browser)
-    browser.end()
+  #   helpers.doFollowTopic(browser)
+  #   browser.end()

-  unfollowTopic: (browser) ->
+  # unfollowTopic: (browser) ->

-    hashtag    = helpers.doFollowTopic(browser)
-    selector   = '.activity-sidebar .followed.topics'
-    publicLink = '[testpath="public-feed-link/Activity/Topic/public"]'
+  #   hashtag    = helpers.doFollowTopic(browser)
+  #   selector   = '.activity-sidebar .followed.topics'
+  #   publicLink = '[testpath="public-feed-link/Activity/Topic/public"]'

-    browser
-      .click                   '[testpath=channel-title]' + ' .following'
-      .waitForElementVisible   publicLink, 25000
-      .click                   publicLink
-      .refresh()
-      .getText selector, (result) =>
-        index = result.value.indexOf(hashtag.replace('#', ''))
-        assert.equal(index, -1)
-        browser.end()
+  #   browser
+  #     .click                   '[testpath=channel-title]' + ' .following'
+  #     .waitForElementVisible   publicLink, 25000
+  #     .click                   publicLink
+  #     .refresh()
+  #     .getText selector, (result) =>
+  #       index = result.value.indexOf(hashtag.replace('#', ''))
+  #       assert.equal(index, -1)
+  #       browser.end()


Fixed bug-67: Now we can unasigned a task

--- app/coffee/controllers/taskboard.coffee
+++ app/coffee/controllers/taskboard.coffee
@@ -157,7 +157,7 @@

 TaskboardTaskController = ($scope, $rootScope, $q) ->
     $scope.updateTaskAssignation = (task, id) ->
-        task.assigned_to = id ? id : null
+        task.assigned_to = id || null
         task.save()

     $scope.getTaskColorStyle = (task) ->


Get rid of labels array.

Get rid of labels array.

--- app/static/coffee/production-chart.coffee
+++ app/static/coffee/production-chart.coffee
@@ -3,14 +3,12 @@
   # Read CSV file: first row =>  labels
   reserve_scale = 0.01

-  labels = new Array()
   values = new Array()

   i = 0
   while i <= data.length - 1
-    labels[i] = parseInt(data[i]["Year"])
     values[i] =
-      x: labels[i]
+      x: parseInt(data[i]["Year"])
       y: parseFloat(data[i]["World production"]) or 0
       z: parseFloat(data[i]["Estimated"]) or 0
       zlog: parseFloat(data[i]["Log. Estimated"]) or 0
@@ -22,6 +20,8 @@
   maxval = (1 + Math.floor(maxval / 10)) * 10
   maxval = Math.pow(2, Math.ceil(Math.log(maxval) / Math.log(2)))

+  window.data = values
+
   color1 = "#ffaa00"
   color2 = "#770033"
   color3 = "#ff0055"
@@ -31,8 +31,8 @@
   h = 600
   p = 30
   x = d3.scale.linear().domain([
-    labels[0]
-    labels[data.length - 1]
+    values[0].x
+    values[data.length - 1].x
   ]).range([
     0
     w


Fix more game-breaking bugs with InventoryModal

--- app/views/play/menu/InventoryModal.coffee
+++ app/views/play/menu/InventoryModal.coffee
@@ -70,7 +70,7 @@
   onItemsLoaded: ->
     for item in @items.models
       item.notInLevel = true
-      programmableConfig = _.find(item.get('components'), (c) -> c.config?)?.config
+      programmableConfig = _.find(item.get('components'), (c) -> c.config?.programmableProperties)?.config
       item.programmableProperties = (programmableConfig?.programmableProperties or []).concat programmableConfig?.moreProgrammableProperties or []
     @equipment = @options.equipment or @options.session?.get('heroConfig')?.inventory or me.get('heroConfig')?.inventory or {}
     @equipment = $.extend true, {}, @equipment
@@ -184,7 +184,7 @@
     @canvasWidth = @$el.find('canvas').innerWidth()
     @canvasHeight = @$el.find('canvas').innerHeight()
     @inserted = true
-    @requireLevelEquipment() if @items.loaded
+    @requireLevelEquipment()

   #- Draggable logic

@@ -328,7 +328,7 @@
     slotEl = @getSelectedSlot()
     @clearSelection()
     itemEl = @unequipItemFromSlot(slotEl)
-    return unless itemEl
+    return unless itemEl?.length
     itemEl.addClass('active')
     slotEl.effect('transfer', to: itemEl, duration: 500, easing: 'easeOutCubic')
     selectedSlotItemID = itemEl.data('item-id')
@@ -399,7 +399,7 @@

   requireLevelEquipment: ->
     # This is called frequently to make sure the player isn't using any restricted items and knows she must equip any required items.
-    return unless @inserted
+    return unless @inserted and @items.loaded
     equipment = if @supermodel.finished() then @getCurrentEquipmentConfig() else @equipment  # Make sure we're using latest equipment.
     hadRequired = @remainingRequiredEquipment?.length
     @remainingRequiredEquipment = []
@@ -432,6 +432,7 @@
       restrictedPropertiesOnThisItem = _.intersection(item.programmableProperties, restrictedProperties)
       continue unless requiredPropertiesOnThisItem.length and not restrictedPropertiesOnThisItem.length
       for slot in item.getAllowedSlots()
+        continue if slot isnt 'right-hand' and _.isEqual requiredPropertiesOnThisItem, ['buildXY']  # Don't require things like caltrops belt
         requiredGear[slot] ?= []
         requiredGear[slot].push(item.get('original')) unless item.get('original') in requiredGear[slot]
         requiredPropertiesPerSlot[slot] ?= []


Start sentences with upper case in compress.coffee

--- src/coffee/compress.coffee
+++ src/coffee/compress.coffee
@@ -11,7 +11,7 @@

   @compressImage: (source, destination, extension) ->
     new Promise (resolve, reject) ->
-      debug 'about to compress image from path: %s', source
+      debug 'About to compress image from path: %s', source
       switch extension
         when "png" then plugin = imagemin.optipng({optimizationLevel: 3})
         when "gif" then plugin = imagemin.gifsicle({interlaced: true})
@@ -26,11 +26,11 @@
             .use(plugin)
             .run (err) ->
               if err
-                reject 'error while compressing file: ' + err
+                reject 'Error while compressing file: ' + err
               else
                 resolve true
         else
-          debug 'error while getting stats for source: %s, with error %s', source, err
-          reject 'error while getting stats for source: ' + source
+          debug 'Error while getting stats for source: %s, with error %s', source, err
+          reject 'Error while getting stats for source: ' + source

 module.exports = Compress


update navbar spec

update navbar spec

--- test/components/navbar/user-actions-menu.spec.coffee
+++ test/components/navbar/user-actions-menu.spec.coffee
@@ -17,8 +17,7 @@
     it 'should have expected dropdown menu', (done) ->
       Testing.renderComponent( UserActionsMenu, props: {courseId: courseModel.id} ).then ({dom}) =>
         dropdownItems = dom.querySelectorAll('li')
-        roleItems = Array.prototype.slice.call(dropdownItems, 0, -4)
-
+        roleItems = Array.prototype.slice.call(dropdownItems, 1, -4)
         labels = _.pluck(@roleTestParams.menu, 'label')
         labels.push 'Browse the Book'
         expect(_.pluck(roleItems, 'innerText')).to.deep.equal(labels)


Update format for campfire plugin

Update format for campfire plugin

--- plugins/campfire/index.coffee
+++ plugins/campfire/index.coffee
@@ -4,7 +4,7 @@
   @receiveEvent: (config, event) ->
     # Build the message
     if event.error
-      message = "#{event.error.exceptionClass}" + (if event.error.message then ": #{event.error.message}" else "") + " (#{event.error.url})"
+      message = "#{event.trigger.message} in #{event.project.name}! #{event.error.exceptionClass}" + (if event.error.message then ": #{event.error.message}" else "") + " (#{event.error.url})"
     else
       message =  "#{event.trigger.message} in #{event.project.name} (#{event.project.url})" + ( if event.error then " in #{event.error.context} (#{event.error.url})" else "")


Autoresize caption while typing

Autoresize caption while typing

--- src/trix/controllers/attachment_editor_controller.coffee
+++ src/trix/controllers/attachment_editor_controller.coffee
@@ -106,8 +106,9 @@
       textareaClone.value = textarea.value
       textarea.style.height = textareaClone.scrollHeight + "px"

+    handleEvent("input", onElement: textarea, withCallback: autoresize)
+    handleEvent("input", onElement: textarea, withCallback: @didInputCaption)
     handleEvent("keydown", onElement: textarea, withCallback: @didKeyDownCaption)
-    handleEvent("input", onElement: textarea, withCallback: @didInputCaption)
     handleEvent("change", onElement: textarea, withCallback: @didChangeCaption)
     handleEvent("blur", onElement: textarea, withCallback: @didBlurCaption)


add remove attribute tests

add remove attribute tests

--- tests/unit/scripts/editor.coffee
+++ tests/unit/scripts/editor.coffee
@@ -130,11 +130,77 @@

   describe('applyAttribute', ->
-    reset = ->
-      $('#editor-container').html(Tandem.Utils.removeHtmlWhitespace('
-

-          123
-          456
+    originalHtml = Tandem.Utils.removeHtmlWhitespace('
+

+        123
+        456
+

+

+        <s>7</s>
+        8
+        <s>9</s>
+        0
+

+

+        abcdefg
+

+    ')
+
+    reset = (html = originalHtml) ->
+      $('#editor-container').html(Tandem.Utils.removeHtmlWhitespace(html))
+      return new Tandem.Editor('editor-container')
+
+    tests = [{
+      target: 'single node'
+      start: 8
+      length: 1
+      expected:
+        '

+          123
+          456
+

+

+          <s>7</s>
+
+            8
+
+          <s>9</s>
+          0
+

+

+          abcdefg
+
'
+    }, {
+      target: 'multiple nodes'
+      start: 8
+      length: 2
+      expected:
+        '

+          123
+          456
+

+

+          <s>7</s>
+
+            8
+            <s>9</s>
+
+          0
+

+

+          abcdefg
+
'
+    }, {
+      target: 'part of node'
+      start: 3
+      length: 2
+      expected:
+        '

+          123
+
+            45
+
+          6

           <s>7</s>
@@ -144,59 +210,17 @@

           abcdefg
-

-      '))
-      return new Tandem.Editor('editor-container')
-
-    tests = [{
-      name: 'should apply attribute to single node'
-      start: 8
+
'
+    }, {
+      target: 'inside of node'
+      start: 4
       length: 1
       expected:
         '

           123
-          456
-

-

-          <s>7</s>
-
-            8
-
-          <s>9</s>
-          0
-

-

-          abcdefg
-
'
-    }, {
-      name: 'should wrap multiple nodes'
-      start: 8
-      length: 2
-      expected:
-        '

-          123
-          456
-

-

-          <s>7</s>
-
-            8
-            <s>9</s>
-
-          0
-

-

-          abcdefg
-
'
-    }, {
-      name: 'should apply to part of node'
-      start: 3
-      length: 2
-      expected:
-        '

-          123
-
-            45
+          4
+
+            5

           6

@@ -210,21 +234,21 @@
           abcdefg

'
     }, {
-      name: 'should apply to inside of node'
-      start: 4
-      length: 1
-      expected:
-        '

-          123
-          4
-
-            5
-
-          6
-

-

-          <s>7</s>
-          8
+      target: 'nodes spanning multiple lines'
+      start: 3
+      length: 6
+      expected:
+        '

+          123
+
+            456
+
+

+

+
+            <s>7</s>
+            8
+
           <s>9</s>
           0

@@ -232,29 +256,7 @@
           abcdefg

'
     }, {
-      name: 'should apply to nodes spanning multiple lines'
-      start: 3
-      length: 6
-      expected:
-        '

-          123
-
-            456
-
-

-

-
-            <s>7</s>
-            8
-
-          <s>9</s>
-          0
-

-

-          abcdefg
-
'
-    }, {
-      name: 'should apply to entire line'
+      target: 'entire line'
       start: 7
       length: 4
       expected:
@@ -274,7 +276,7 @@
           abcdefg

'
     }, {
-      name: 'should apply to entire line with trailing newline'
+      target: 'entire line with trailing newline'
       start: 7
       length: 5
       expected:
@@ -294,7 +296,7 @@
           abcdefg

'
     }, {
-      name: 'should apply to entire line with preceding newline'
+      target: 'entire line with preceding newline'
       start: 6
       length: 5
       expected:
@@ -314,7 +316,7 @@
           abcdefg

'
     }, {
-      name: 'should apply to entire line with preceding and trailing newline'
+      target: 'entire line with preceding and trailing newline'
       start: 6
       length: 6
       expected:
@@ -336,14 +338,22 @@
     }]

     _.each(tests, (test) ->
-      it(test.name, ->
+      it('should apply to ' + test.target, ->
         editor = reset()
         editor.applyAttribute(test.start, test.length, 'bold', true)
         expect(editor.iframeDoc.body.innerHTML).to.equal(Tandem.Utils.removeHtmlWhitespace(test.expected))
       )
     )
+
+    _.each(tests, (test) ->
+      it('should remove from ' + test.target, ->
+        editor = reset(test.expected)
+        editor.applyAttribute(test.start, test.length, 'bold', false)
+        expect(editor.iframeDoc.body.innerHTML).to.equal(originalHtml)
+      )
+    )
   )
-
+

   describe('insertAt', ->
     reset = ->


hardcode system init (on load)

hardcode system init (on load)

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -104,6 +104,8 @@
       # fetch the relevant class file, make a ClassFile, put it in @classes[cls]
       console.log "loading new class: #{cls}"
       @classes[cls] = new ClassFile @read_classfile cls
+      if cls is 'java/lang/System'  # zomg hardcode
+        @method_lookup({'class': cls, 'sig': {'name': 'initializeSystemClass'}}).run(this)
     throw "class #{cls} not found!" unless @classes[cls]
     @classes[cls]
   method_lookup: (method_spec) ->


add support for url

add support for url

--- lib/quest.coffee
+++ lib/quest.coffee
@@ -34,6 +34,7 @@
   options.method isnt 'PUT' and options.method isnt 'POST' and options.method isnt 'DELETE'))

 quest = (options, cb) ->
+  options.uri = options.url if options.url? and not options.uri?
   return cb 'Options does not include uri' if not options?.uri?
   return cb "Uri #{JSON.stringify options.uri} is not a string" if not _(options.uri).isString()
   options = _.clone options


WIP - ReactiveFunctionRunner: ExpressionFunction with currentValue working for most cases

--- src/main/code/ReactiveEvaluators.coffee
+++ src/main/code/ReactiveEvaluators.coffee
@@ -222,6 +222,10 @@
     @_activateArgs context

   copy: -> new FunctionCallWithArgs @expr, @name, (a.copy() for a in @args)
+  currentValue: (argValues) ->
+    if @isUserFunction then throw new Error("Unexpected call to currentValue for user function in #{@toString()}")
+    if @func.returnKind == FunctionTypes.STREAM_RETURN then throw new Error("Unexpected call to currentValue for stream return function in #{@toString()}")
+    @func.apply null, @_currentValues(argValues)

   _updateFunction: (funcDef) =>
     argSubject = (arg) ->


Tiny cleanup

Tiny cleanup

--- lib/spigg.coffee
+++ lib/spigg.coffee
@@ -81,11 +81,9 @@
       o[i]

     arr.reduce walker, @data
-    @data = @_filter()
+    @data = @_filter() # Call filter

   _filter: (obj, fields) ->
-
-
     obj ?=    @data
     fields ?= @fields
     o =       {}


fullpieagent testing

fullpieagent testing


fix(service): fix npe if vertxEventBus is disabled (and null)

--- src/service.coffee
+++ src/service.coffee
@@ -104,7 +104,7 @@
     { enabled, debugEnabled, prefix, urlServer, urlPath, reconnectEnabled,
       sockjsStateInterval, sockjsReconnectInterval, sockjsOptions,
       messageBuffer
-    } = vertxEventBus.getOptions()
+    } = vertxEventBus?.getOptions() or {}

     connectionState = vertxEventBus?.EventBus?.CLOSED
     validSession = false


Teach URL canonicalizer about 'view-source:' prefix.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -32,9 +32,10 @@

   # Completes a partial URL (without scheme)
   createFullUrl: (partialUrl) ->
-    if (!/^[a-z]{3,}:\/\//.test(partialUrl))
-      partialUrl = "http://" + partialUrl
-    partialUrl
+    if (!/^[a-z]{3,}:\/\//.test(partialUrl)) and not /^view-source:/.test partialUrl
+      "http://" + partialUrl
+    else
+      partialUrl

   # Tries to detect, whether :str is a valid URL.
   isUrl: (str) ->
@@ -52,6 +53,10 @@

     # are there more?
     specialHostNames = [ 'localhost' ]
+
+    # special-case view-source:[url]
+    if /^view-source:/.test str
+      return true

     # it starts with a scheme, so it's definitely an URL
     if (/^[a-z]{3,}:\/\//.test(str))


Make walking routes dotted with circles

Make walking routes dotted with circles

--- src/transit.coffee
+++ src/transit.coffee
@@ -145,18 +145,23 @@
         for leg in legs
             points = (new L.LatLng(point[0], point[1]) for point in leg.legGeometry.points)
             color = google_colors[leg.routeType ? leg.mode]
+            style = {color: color, weight: 8, opacity: 0.2, clickable: false}
             # For walking a dashed line is used
-            if leg.routeType?
-                dash_array = null
-            else
-                dash_array = "5,10"
-                color = "#000" # override line color to black for visibility
-
-            style = {color: color, weight: 8, opacity: 0.2, clickable: false, dashArray: dash_array}
+            if not leg.routeType?
+                style.weight = 10
+                style.dashArray = '1,20'
+                style.lineCap = 'round'
+                style.color = '#50c7ff'
+
             polyline = new L.Polyline points, style
             polyline.addTo route_layer # The route leg line is added to the routeLayer

-            style = {color: color, opacity: 0.4, dashArray: dash_array}
+            style.opacity = 0.4
+            delete style.weight
+            delete style.clickable
+            if not leg.routeType?
+                style.weight = 10
+
             polyline = new L.Polyline points, style
             # Make zooming to the leg via click possible.
             polyline.on 'click', (e) ->


テストの説明間違いを修正

テストの説明間違いを修正

--- bin/js/test/jenkinsNodeJSBuildTest.coffee
+++ bin/js/test/jenkinsNodeJSBuildTest.coffee
@@ -5,7 +5,7 @@
 jenkinsBuild = require("./jenkinsBuild.js")
 test = require("./resq.js")

-describe "Jenkins Job (example-java)", ->
+describe "Jenkins Job (example-nodejs)", ->

   it "build", (done) ->
     @timeout(30 * 60 * 1000)


Add method to determine if a model is editable

--- src/scripts/models/contents/node.coffee
+++ src/scripts/models/contents/node.coffee
@@ -186,3 +186,9 @@
     isDraft: () -> return @get('version') is 'draft' or /@draft$/.test(@id)

     isSaveable: () -> !!@get('mediaType')
+
+    isEditable: () ->
+      if @get('editable') or (@get('book.editable') and @isDraft())
+        return true
+
+      return false


experienceinfo guesses pitch/rhythm-based codes

--- tools/src/experienceinfo.coffee
+++ tools/src/experienceinfo.coffee
@@ -8,7 +8,7 @@
   console.error 'Writes output to stdout as CSV'
   process.exit 0

-headings = ['file','input','filters','nprofiles','profiles','ncodes','codes','nwildcards','ninexact','errors','actions','actiontypes','nchannels','channels','state','npreconditions','preconditions','nexamples']
+headings = ['file','input','filters','nprofiles','profiles','ncodes','codes','npitch','nrhythm','nboth','nwildcards','wildcards','ninexact','errors','actions','actiontypes','nchannels','channels','state','npreconditions','preconditions','nexamples']

 outrow = ( els ) ->
   out = ''
@@ -61,7 +61,7 @@
     info.nprofiles = 0
     info.profiles = ''
   # codes
-  info.ncodes = info.nwildcards = info.ninexact = info.npreconditions = info.nchannels = 0
+  info.ncodes = info.nwildcards = info.ninexact = info.npreconditions = info.nchannels = info.npitch = info.nrhythm = info.nboth = 0
   info.codes = []
   info.actions = []
   info.errors = []
@@ -71,6 +71,27 @@
   info.actiontypes = []
   for marker in (exp.markers ? [])
     code = marker.code
+
+    rhythm = (code.indexOf '/') >= 0
+    pitch = /[a-gA-G]/.test code
+    if rhythm
+      if pitch
+        info.nboth++
+      else
+        info.nrhythm++
+    else if pitch
+      info.npitch++
+
+    wc = false
+
+    for wildcard in wildcards
+      if (code.indexOf wildcard) >= 0
+        if (info.wildcards.indexOf wildcard)<0
+          info.wildcards.push wildcard
+        wc = true
+    if wc
+      info.nwildcards++
+
     if marker.projection?
       code = marker.projection+':'+code
     else if marker.codeformat?
@@ -79,15 +100,6 @@
     info.ncodes++
     if (info.codes.indexOf code) < 0
       info.codes.push code
-
-    wc = false
-    for wildcard in wildcards
-      if (code.indexOf wildcard) >= 0
-        if (info.wildcards.indexOf wildcard)<0
-          info.wildcards.push wildcard
-        wc = true
-    if wc
-      info.nwildcards++

     if marker.inexact
       info.ninexact++


auto reset auth hash

auto reset auth hash

--- src/hull.coffee
+++ src/hull.coffee
@@ -101,6 +101,7 @@
     if window?.opener?.Hull? and window?.opener?.__hull_login_status__ and !!hash
       window.opener.__hull_login_status__(hash)
       window.close()
+  window.location.hash = '' if hash && hash.hasOwnProperty('success')

 parseHash()


Throw an error if a type isn't specified

--- src/init.coffee
+++ src/init.coffee
@@ -29,8 +29,9 @@
     if options.argv.package?
       packagePath = path.join(process.cwd(), options.argv.package)
       @generatePackage(packagePath)
-
-    callback()
+      callback()
+    else
+      callback('Error: You must specify either --project or --theme to `apm init`')

   generatePackage: (packagePath) ->
     templatePath = path.resolve(__dirname, '..', 'templates', 'package')


remove debug

remove debug

--- tutor/specs/components/new-course/offering.spec.cjsx
+++ tutor/specs/components/new-course/offering.spec.cjsx
@@ -22,7 +22,6 @@
     find(burntOfferings.items, id: @props.offeringId).appearance_code = 'firefirefire'
     OfferingsActions.loaded(burntOfferings)
     wrapper = shallow(<Offering {...@props} />)
-    console.log wrapper.debug()
     expect(wrapper.is('[data-appearance="firefirefire"]')).to.be.true
     expect(wrapper.find('CourseChoiceContent[data-book-title=""]')).to.have.length(1)
     undefined


Don't require robot name in one to one chats, clean up room stuff a bit

--- src/quip.coffee
+++ src/quip.coffee
@@ -36,7 +36,7 @@
       options.annotationId = packet.message.annotation.id if packet.message.annotation
     else
       options =
-        threadId: envelope.room
+        threadId: envelope.room or envelope.message.room
     options.attachments = attachments.join "," if attachments.length
     options.content = text.join "\n\n" if text.length
     @robot.logger.info "Sending to #{envelope.room}: #{JSON.stringify(options)}"
@@ -137,9 +137,13 @@
         if -1 != @robot.name.indexOf packet.user.id
           return
         user = @robot.brain.userForId packet.user.id, name: @quipMention packet.user.id, room: packet.thread.id
+        user.room = packet.thread.id
         text = packet.message.text
         if @robotName == text.substr 0, @robotName.length
           text = @robot.name + text.substr @robotName.length
+        else if packet.thread.thread_class == "two_person_chat" and @robot.name != text.substr 0, @robot.name.length
+          # Pretend we were mention for 1:1 chats
+          text = "#{@robot.name} #{text}"
         message = new QuipTextMessage user, text, packet
         @robot.receive message
       else


forgot to remove a log

forgot to remove a log

--- src/ui/util.coffee
+++ src/ui/util.coffee
@@ -168,7 +168,6 @@

     patterns = [inferedPattern]

-    console.log('patterns', patterns[0])
     emojiCodeRegex = new RegExp(patterns.join('|'),'g')

     text = text.replace(emojiCodeRegex, (emoji) ->


adapt frontend comment model to new db format

--- www/src/models/comment.coffee
+++ www/src/models/comment.coffee
@@ -2,23 +2,21 @@
     Backbone.Model.extend
         idAttribute: "_id"
         urlRoot: ->
-            "/api/quest/" + @get("quest_id") + "/comment"
+            # backward compatibility, can be removed after migration to entity/eid
+            entity = @get("entity") || "quest"
+            eid = @get("eid") || @get("quest_id")

-        like: ->
-            @act "like"
+            return "/api/#{entity}/#{eid}/comment"

-        unlike: ->
-            @act "unlike"
+        like: -> @act "like"
+        unlike: -> @act "unlike"

         act: (action) ->
             model = this
-
+
             # FIXME - copypasted from models/quest.js
             # TODO - send only on success?
             ga "send", "event", "comment", action
             mixpanel.track action + " comment"
             $.post(@url() + "/" + action).done ->
                 model.fetch()
-
-
-


[battles] fix display bug with HP

[battles] fix display bug with HP


Remove pseudo-placeholder to prevent IE crashes

Tiggered by some combination of frequent calls to the selection API

--- src/trix.coffee
+++ src/trix.coffee
@@ -55,11 +55,6 @@

   config:
     editorCSS: """
-      .trix-editor[contenteditable=true]:empty:before {
-        content: attr(data-placeholder);
-        color: graytext;
-      }
-
       .trix-editor a[contenteditable=false] {
         cursor: text;
       }


fixed issue with zerofilling integer years

--- src/viz/types/helpers/graph/nest.coffee
+++ src/viz/types/helpers/graph/nest.coffee
@@ -10,12 +10,19 @@
   timeAxis = discrete.value is vars.time.value
   if timeAxis
     ticks = vars.data.time.ticks
-    if vars.time.solo.value.length
-      serialized = vars.time.solo.value.map(Number)
-      ticks = ticks.filter (f) -> serialized.indexOf(+f) >= 0
-    else if vars.time.mute.value.length
-      serialized = vars.time.mute.value.map(Number)
-      ticks = ticks.filter (f) -> serialized.indexOf(+f) < 0
+    key = if vars.time.solo.value.length then "solo" else "mute"
+    if vars.time[key].value.length
+      serialized = vars.time[key].value.slice().map (f) ->
+        if f.constructor isnt Date
+          f = f + ""
+          f += "/01/01" if f.length is 4 and parseInt(f)+"" is f
+          f = new Date f
+        +f
+      ticks = ticks.filter (f) ->
+        if key is "solo"
+          serialized.indexOf(+f) >= 0
+        else
+          serialized.indexOf(+f) < 0
   else if discrete.ticks.values
     ticks = discrete.ticks.values
   else


Refactored handleChange.

Refactored handleChange.

--- src/scripts/views/article-editor.coffee
+++ src/scripts/views/article-editor.coffee
@@ -12,6 +12,18 @@

 module.exports = React.createClass
 	displayName: 'ArticleEditor'
+	set: (k, v) ->
+		# this will do for now
+		fieldFormats =
+			title: 'txt'
+			content: 'md'
+		lang = @state.get 'lang'
+		data = @state.get 'data'
+		if data.hasIn [k, lang, fieldFormats[k]]
+			data = data.setIn [k, lang, fieldFormats[k]], v
+		else
+			data = data.set k, v
+		@replaceState @state.set 'data', data
 	setLanguage: (lang) ->
 		@replaceState @state.set('lang', lang)
 	getInitialState: ->
@@ -34,19 +46,7 @@
 			props.placeholder = utils.getFieldValueFromFormats utils.localize @state.get('lang'), @props.data.doc[name]
 		props
 	handleChange: (e) ->
-		# this will do for now
-		fieldFormats =
-			title: 'txt'
-			content: 'md'
-		lang = @state.get 'lang'
-		data = @state.get 'data'
-		k = e.target.name
-		v = e.target.value
-		if data.hasIn [k, lang, fieldFormats[k]]
-			data = data.setIn [k, lang, fieldFormats[k]], v
-		else
-			data = data.set k, v
-		@replaceState @state.set 'data', data
+		@set e.target.name, e.target.value
 	handleLanguageChange: (e) ->
 		@setLanguage e.target.value
 	handleSubmit: (e) ->


remove redundant check for browser loading

--- src/server/virtual_browser/index.coffee
+++ src/server/virtual_browser/index.coffee
@@ -266,20 +266,19 @@

         if !@browserInitialized
             return @queuedSockets.push(socket)
-
-        if not @browserLoading
-            nodes = serialize(@browser.window.document,
-                              @resources,
-                              @browser.window.document,
-                              @server.config)
-            compressionTable = undefined
-            if @server.config.compression
-                compressionTable = @compressor.textToSymbol
-            socket.emit('PageLoaded',
-                        nodes,
-                        @registeredEventTypes,
-                        @browser.clientComponents,
-                        compressionTable)
+        # the socket is added after the first pageloaded event emitted
+        nodes = serialize(@browser.window.document,
+                          @resources,
+                          @browser.window.document,
+                          @server.config)
+        compressionTable = undefined
+        if @server.config.compression
+            compressionTable = @compressor.textToSymbol
+        socket.emit('PageLoaded',
+                    nodes,
+                    @registeredEventTypes,
+                    @browser.clientComponents,
+                    compressionTable)

         @sockets.push(socket)
         gc() if @server.config.traceMem


chore(tests): Refactor variable name

chore(tests): Refactor variable name

--- src/spec/integration/export.spec.coffee
+++ src/spec/integration/export.spec.coffee
@@ -91,17 +91,17 @@
       '''
       productType,name,variantId
       '''
-    file = '/tmp/output.csv'
-    expectedCSV =
-      """
-      productType,name,variantId
-
-
-      """
-    @export.exportDefault(template, file, false)
-    .then (result) ->
-      expect(result).toBe 'Export done.'
-      fs.readFileAsync file, {encoding: 'utf8'}
+    outputLocation = '/tmp/output.csv'
+    expectedCSV =
+      """
+      productType,name,variantId
+
+
+      """
+    @export.exportDefault(template, outputLocation, false)
+    .then (result) ->
+      expect(result).toBe 'Export done.'
+      fs.readFileAsync outputLocation, {encoding: 'utf8'}
     .then (content) ->
       expect(content).toBe expectedCSV
       done()
@@ -112,7 +112,7 @@
       '''
       productType,name,variantId
       '''
-    file = '/tmp/output.csv'
+    outputLocation = '/tmp/output.csv'
     expectedCSV =
       """
       productType,name,variantId
@@ -120,10 +120,10 @@
       ,,2

       """
-    @export.exportDefault(template, file)
-    .then (result) ->
-      expect(result).toBe 'Export done.'
-      fs.readFileAsync file, {encoding: 'utf8'}
+    @export.exportDefault(template, outputLocation)
+    .then (result) ->
+      expect(result).toBe 'Export done.'
+      fs.readFileAsync outputLocation, {encoding: 'utf8'}
     .then (content) ->
       expect(content).toBe expectedCSV
       done()
@@ -134,7 +134,7 @@
     '''
       productType,name,variantId,attr-lenum-n.en,attr-set-lenum-n.en
       '''
-    file = '/tmp/output.csv'
+    outputLocation = '/tmp/output.csv'
     expectedCSV =
     """
       productType,name,variantId,attr-lenum-n.en,attr-set-lenum-n.en
@@ -142,10 +142,10 @@
       ,,2,Enum1,Enum1;Enum2

       """
-    @export.exportDefault(template, file)
-    .then (result) ->
-      expect(result).toBe 'Export done.'
-      fs.readFileAsync file, {encoding: 'utf8'}
+    @export.exportDefault(template, outputLocation)
+    .then (result) ->
+      expect(result).toBe 'Export done.'
+      fs.readFileAsync outputLocation, {encoding: 'utf8'}
     .then (content) ->
       expect(content).toBe expectedCSV
       done()
@@ -154,23 +154,23 @@

   it 'should do a full export', (done) ->
     tempDir = tmp.dirSync({ unsafeCleanup: true })
-    file = path.join tempDir.name, 'output.zip'
+    outputLocation = path.join tempDir.name, 'output.zip'
     expectedHeader = '_published,_hasStagedChanges,productType,variantId,id,sku,prices,tax,categories,images,name.en,description.en,slug.en,metaTitle.en,metaDescription.en,metaKeywords.en,searchKeywords.en,attr-text-n,attr-ltext-n.en,attr-enum-n,attr-lenum-n,attr-number-n,attr-boolean-n,attr-money-n,attr-date-n,attr-time-n,attr-datetime-n,attr-ref-product-n,attr-ref-product-type-n,attr-ref-channel-n,attr-ref-state-n,attr-ref-zone-n,attr-ref-shipping-method-n,attr-ref-category-n,attr-ref-review-n,attr-ref-key-value-n,attr-set-text-n,attr-set-ltext-n.en,attr-set-enum-n,attr-set-lenum-n,attr-set-number-n,attr-set-boolean-n,attr-set-money-n,attr-set-date-n,attr-set-time-n,attr-set-datetime-n,attr-set-ref-product-n,attr-set-ref-product-type-n,attr-set-ref-channel-n,attr-set-ref-state-n,attr-set-ref-zone-n,attr-set-ref-shipping-method-n,attr-set-ref-category-n,attr-set-ref-review-n,attr-set-ref-key-value-n,attr-text-u,attr-ltext-u.en,attr-enum-u,attr-lenum-u,attr-number-u,attr-boolean-u,attr-money-u,attr-date-u,attr-time-u,attr-datetime-u,attr-ref-product-u,attr-ref-product-type-u,attr-ref-channel-u,attr-ref-state-u,attr-ref-zone-u,attr-ref-shipping-method-u,attr-ref-category-u,attr-ref-review-u,attr-ref-key-value-u,attr-set-text-u,attr-set-ltext-u.en,attr-set-enum-u,attr-set-lenum-u,attr-set-number-u,attr-set-boolean-u,attr-set-money-u,attr-set-date-u,attr-set-time-u,attr-set-datetime-u,attr-set-ref-product-u,attr-set-ref-product-type-u,attr-set-ref-channel-u,attr-set-ref-state-u,attr-set-ref-zone-u,attr-set-ref-shipping-method-u,attr-set-ref-category-u,attr-set-ref-review-u,attr-set-ref-key-value-u,attr-text-cu,attr-ltext-cu.en,attr-enum-cu,attr-lenum-cu,attr-number-cu,attr-boolean-cu,attr-money-cu,attr-date-cu,attr-time-cu,attr-datetime-cu,attr-ref-product-cu,attr-ref-product-type-cu,attr-ref-channel-cu,attr-ref-state-cu,attr-ref-zone-cu,attr-ref-shipping-method-cu,attr-ref-category-cu,attr-ref-review-cu,attr-ref-key-value-cu,attr-set-text-cu,attr-set-ltext-cu.en,attr-set-enum-cu,attr-set-lenum-cu,attr-set-number-cu,attr-set-boolean-cu,attr-set-money-cu,attr-set-date-cu,attr-set-time-cu,attr-set-datetime-cu,attr-set-ref-product-cu,attr-set-ref-product-type-cu,attr-set-ref-channel-cu,attr-set-ref-state-cu,attr-set-ref-zone-cu,attr-set-ref-shipping-method-cu,attr-set-ref-category-cu,attr-set-ref-review-cu,attr-set-ref-key-value-cu,attr-text-sfa,attr-ltext-sfa.en,attr-enum-sfa,attr-lenum-sfa,attr-number-sfa,attr-boolean-sfa,attr-money-sfa,attr-date-sfa,attr-time-sfa,attr-datetime-sfa,attr-ref-product-sfa,attr-ref-product-type-sfa,attr-ref-channel-sfa,attr-ref-state-sfa,attr-ref-zone-sfa,attr-ref-shipping-method-sfa,attr-ref-category-sfa,attr-ref-review-sfa,attr-ref-key-value-sfa,attr-set-text-sfa,attr-set-ltext-sfa.en,attr-set-enum-sfa,attr-set-lenum-sfa,attr-set-number-sfa,attr-set-boolean-sfa,attr-set-money-sfa,attr-set-date-sfa,attr-set-time-sfa,attr-set-datetime-sfa,attr-set-ref-product-sfa,attr-set-ref-product-type-sfa,attr-set-ref-channel-sfa,attr-set-ref-state-sfa,attr-set-ref-zone-sfa,attr-set-ref-shipping-method-sfa,attr-set-ref-category-sfa,attr-set-ref-review-sfa,attr-set-ref-key-value-sfa'
     expectedProduct = 'false,false,ImpEx with all types,1,MONGO_ID,,,,,,Foo,,foo,,,,'
     expectedVariant =   ',,,2,,123,,,,,,,,,,,,,,,lenum1,,,,,,,,,,,,,,,,,,,lenum1;lenum2'

-    @export.exportFull(file)
+    @export.exportFull(outputLocation)
     .then (result) ->
       expect(result).toBe 'Export done.'

       try
-        fs.statSync(file).isFile()
+        fs.statSync(outputLocation).isFile()
         return Promise.resolve()
       catch err
         return Promise.reject "Archive was not generated"
     .then ->
       console.log "Archive was generated successfully"
-      extractArchive(file, {dir: tempDir.name})
+      extractArchive(outputLocation, {dir: tempDir.name})
     .then ->

       try
@@ -213,7 +213,7 @@
     '''
       productType,name,variantId,attr-lenum-n.en,attr-set-lenum-n.en,žškřďťň
       '''
-    file = '/tmp/output.csv'
+    outputLocation = '/tmp/output.csv'
     expectedCSV =
     """
       productType,name,variantId,attr-lenum-n.en,attr-set-lenum-n.en,žškřďťň
@@ -224,17 +224,17 @@

     # export data in win1250 encoding
     @export.options.encoding = encoding
-    @export.exportDefault(template, file)
-    .then (result) ->
-      expect(result).toBe 'Export done.'
-
-      fs.readFileAsync file, {encoding: 'utf8'}
+    @export.exportDefault(template, outputLocation)
+    .then (result) ->
+      expect(result).toBe 'Export done.'
+
+      fs.readFileAsync outputLocation, {encoding: 'utf8'}
     .then (content) ->
       # compare exported data with text encoded in utf8
       expect(content).not.toBe expectedCSV

       # decode from win1250 to utf8 and compare with expected result
-      decoded = iconv.decode(fs.readFileSync(file), encoding)
+      decoded = iconv.decode(fs.readFileSync(outputLocation), encoding)
       expect(decoded).toBe expectedCSV

       done()


Fix pointer normalization and streamline inlineMemcpy

--- src/type.coffee
+++ src/type.coffee
@@ -76,10 +76,10 @@
   # Compute the ratio.
   if lalign < ralign
     ratio = ralign / lalign
-    op = '<<'
+    op = '>>'
   else
     ratio = lalign / ralign
-    op = '>>'
+    op = '<<'
   # Pretty sure this is true all the time, commenting it out for now.
   # if ratio and not ((ratio & (ratio - 1)) is 0)
   #   error this, "ratio between primitive type sizes must be a power of 2"
@@ -730,7 +730,8 @@

 DeclareType::declareType = (r, o) ->
   scope = o.scope
-  declare scope, v, @type.lint(o.types), (@variables = []) for v in @typeables
+  @variables = variables = []
+  declare scope, v, @type.lint(o.types), variables for v in @typeables
   return

 Param::declareType = (r, o) ->
@@ -759,31 +760,18 @@
   scope = o.scope
   stmts = []
   # Store the pointer to the struct we're copying to.
-  destPtr = freshVariable 'dest'
-  destPtr.computedType = dest.computedType
-  stmts.push new Assign new Value(destPtr), dest
+  destPtr = new Value freshVariable 'dest'
+  destPtr.computedType = destPtr.base.computedType = ty
+  stmts.push new Assign destPtr, dest
   # Store the pointer to the struct we're copying from.
-  srcPtr = freshVariable 'src'
-  srcPtr.computedType = src.computedType
+  srcPtr = new Value freshVariable 'src'
+  srcPtr.computedType = srcPtr.base.computedType = ty
   stmts.push new Assign new Value(srcPtr), src
   # Unroll the copy loop over the bytes.
-  if ty instanceof StructType
-    # We can do better than byte-by-byte if we're memcpying a struct. We need
-    # to manually set the computedType so they get transformed properly in
-    # `Value::transformNode`.
-    for field in ty.fields
-      access = new Access new Literal field.name
-      access.computedField = field
-      destProp = new Value destPtr, [access]
-      srcProp = new Value srcPtr, [access]
-      asn = new Assign destProp, srcProp
-      destProp.computedType = srcProp.computedType = asn.computedType = field.type
-      stmts.push asn
-  else
-    for i in [0...ty.size]
-      offset = new Value new Literal i
-      stmts.push new Assign new Value(U8(o), [new Index new Op '+', new Value(destPtr), offset]),
-                            new Value(U8(o), [new Index new Op '+', new Value(srcPtr), offset])
+  align = ty.view.BYTES_PER_ELEMENT
+  for i in [0...alignmentUnits ty]
+    stmts.push new Assign makeDeref(o, destPtr, ty, i * align, ty),
+                          makeDeref(o, srcPtr, ty, i * align, ty)
   stmts.push new Value destPtr
   new Value new Parens new Block stmts

@@ -814,7 +802,8 @@
   U32(o)
   # Assign all the on-stack locals to their addresses.
   scope = o.scope
-  exprs.unshift new Assign SP, normalizePtr(SPREAL, U32.BYTES_PER_ELEMENT, 1)
+  exprs.unshift new Assign SP, normalizePtr(SPREAL, SPREAL.computedType.view.BYTES_PER_ELEMENT,
+                                                    SP.computedType.view.BYTES_PER_ELEMENT)
   exprs.unshift new Assign SPREAL, new Value(new Literal frameSize), '-='
   restoreStack = new Assign SPREAL, new Value(new Literal frameSize), '+='
   if opts.unsafe
@@ -890,7 +879,7 @@
         offset = stackOffsetExpr bind.spOffset, bind.type
         if ty instanceof StructType
           return offset
-        return new Value U32(o), [new Index offset]
+        return new Value ty.view(o), [new Index offset]
       if inner.value is 'null' and ty instanceof PointerType
         # This matters for TI: null will cause the typeset of pointers to be
         # dimorphic.
@@ -903,6 +892,7 @@
   v = stackOffsetExpr bind.spOffset, bind.type if bind?.onStack
   cumulativeOffset = 0
   for prop in props
+    vty = vty.base if vty instanceof PointerType
     field = prop.computedField
     if (fty = field.type) instanceof StructType
       cumulativeOffset += field.offset
@@ -910,8 +900,8 @@
       v = makeDeref o, v, vty, field.offset + cumulativeOffset, fty
       vty = fty
       cumulativeOffset = 0
-  if fty instanceof StructType and cumulativeOffset isnt 0
-    new Op '+', v, new Value new Literal cumulativeOffset
+  if cumulativeOffset isnt 0
+    offsetExpr v, vty, cumulativeOffset, fty
   else
     v

@@ -953,7 +943,7 @@
       # This is only called when this deref is _not_ the base of a struct
       # access, as in struct accesses, dereferencing is the same as not
       # dereferencing, since . is extended to work like -> in C.
-      ty1 = @first.computedType
+      ty1 = @computedType
       makeDeref o, @first, ty1, 0, ty1
     else if (op is '++' or op is '--') and
             (ty1 = @computedType) instanceof PointerType and


Make sure item exists in array before removing

--- src/util.coffee
+++ src/util.coffee
@@ -52,5 +52,5 @@

 exports.arrayRemove = (a, item) ->
 	i = a.indexOf item
-	a[i..i] = []
+	a[i..i] = [] if ~i
 	i


Moved image smoothing disabling to resize code

--- src/io/web-renderer.coffee
+++ src/io/web-renderer.coffee
@@ -72,16 +72,16 @@

 		@effects = []

+		@useTiles = no # ascii by default
+
+	updateSize: ->
+		@viewport.canvas.width = window.innerWidth
+		@viewport.canvas.height = window.innerHeight
+
 		@viewport.webkitImageSmoothingEnabled = false
 		@viewport.mozImageSmoothingEnabled = false
 		@viewport.oImageSmoothingEnabled = false
 		@viewport.imageSmoothingEnabled = false
-
-		@useTiles = no # ascii by default
-
-	updateSize: ->
-		@viewport.canvas.width = window.innerWidth
-		@viewport.canvas.height = window.innerHeight

 	invalidate: ->
 		if not @invalidated


Add path to ws url

Add path to ws url

--- virtual-file-system/main.coffee
+++ virtual-file-system/main.coffee
@@ -8,24 +8,27 @@
 FSAdapter = require './adapters/fs-adapter'
 SingleSocket = require 'single-socket'

-require('dotenv').config({
+require('dotenv').config
   path: _path.join(__dirname, '../.env'),
   silent: true
-});

 WS_SERVER_URL = (->
   config = _.defaults
-    host: process.env['IDE_WS_HOST'],
+    host: process.env['IDE_WS_HOST']
     port: process.env['IDE_WS_PORT']
+    path: process.env['IDE_WS_PATH']
   ,
     host: 'ile.learn.co',
     port: 443,
+    path: 'go_fs_server'
     protocol: 'wss'

-  if config.port != 443
+  if config.port isnt 443
     config.protocol = 'ws'

-  "#{config.protocol}://#{config.host}:#{config.port}"
+  {protocol, host, port, path} = config
+
+  "#{protocol}://#{host}:#{port}/#{path}"
 )()

 token = atom.config.get('learn-ide.oauthToken')
@@ -56,8 +59,8 @@
       change: @onRecievedChange
       rescue: @onRecievedRescue

-    @websocket = new SingleSocket "#{WS_SERVER_URL}/tree?token=#{token}",
-      onopen: () =>
+    @websocket = new SingleSocket "#{WS_SERVER_URL}?token=#{token}",
+      onopen: =>
         @send {command: 'init'}
       onmessage: (data) ->
         {type, payload} = JSON.parse(data)


opt: stack info

opt: stack info

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -237,6 +237,7 @@
 				reason
 		), stackStr

+       stackInfo.unshift reason
 		return stackInfo

 	isLongStackTrace = false


more specific test about payload

more specific test about payload

--- test/11-indexes.coffee
+++ test/11-indexes.coffee
@@ -7,7 +7,7 @@
 _gamer = null
 _friend = null

-describe 'Indexes', ->
+describe.only 'Indexes', ->

 	indexes = Clan.indexes(Clan.privateDomain)

@@ -56,6 +56,7 @@
 			if err? then return done(err)
 			should.exist(res.found)
 			res.found.should.be.true
+			res._source.payload.whatever.should.eql 'something'
 			done()

@@ -65,6 +66,7 @@
 			should.exist(res.total)
 			should.exist(res.hits)
 			res.total.should.eql(1)
+			res.hits[0]._source.payload.whatever.should.eql 'something'
 			done()

 	it 'should query index', (done)->


NEW: scrollingIdle and scrollingActive events

--- source/Art/Engine/Elements/Widgets/PagingScrollElement.coffee
+++ source/Art/Engine/Elements/Widgets/PagingScrollElement.coffee
@@ -18,6 +18,7 @@
   maxChange
   absLt
   requestAnimationFrame
+  defineModule
 } = Foundation

 {point, Point, rect, Rectangle, matrix, Matrix, isPoint} = Atomic
@@ -338,7 +339,7 @@

       queueNextFrameUpdate()

-module.exports = createWithPostCreate class PagingScrollElement extends AnimatorSupport Element
+defineModule module, class PagingScrollElement extends AnimatorSupport Element

   constructor: ->
     @initAnimatorSupport()
@@ -353,6 +354,8 @@
     @_pagesBeforeBaselineWrapper =
     @_pagesAfterBaselineWrapper = null
     @_setVerticalAxis()
+    @_lastScrollUpdatedAt = currentSecond()
+    @_activelyScrolling = false
     super
     self.pagingScrollElement = @
     @_updateHiddenChildren()
@@ -484,6 +487,16 @@
     scrollPosition:
       default: 0
       postSetter: (position) ->
+        unless @_activelyScrolling
+          @queueEvent "scrollingActive"
+          @_activelyScrolling = true
+
+        @_lastScrollUpdatedAt = thisScrollUpdateWasAt = currentSecond()
+        timeout 500, =>
+          if @_lastScrollUpdatedAt == thisScrollUpdateWasAt
+            @_activelyScrolling = false
+            @queueEvent "scrollingIdle"
+
         # throw new Error if maxCount-- < 0
         # console.error "setScrollPosition #{position}"
         @onNextReady => @_updateAtStartAndAtEnd()
@@ -953,3 +966,5 @@
     @_pagesBeforeBaselineWrapper.setSize commonSizeLayout
     @_pagesAfterBaselineWrapper.setSize commonSizeLayout

+    @_lastScrollUpdatedAt = currentSecond()
+    @_activelyScrolling = false


重写active导航

重写active导航

--- core/static/script/layout.coffee
+++ core/static/script/layout.coffee
@@ -1,4 +1,4 @@
 $ ->
-  $('nav a').each (index) ->
-    if $('nav a')[index].pathname == location.pathname
-      $($('nav a')[index]).parent().addClass('active')
+  $('nav a').each  ->
+    $(@).parent().addClass('active') if $(@).attr('href') is location.pathname
+


Comment out _.deepest

Comment out _.deepest

--- underscore-deep.coffee
+++ underscore-deep.coffee
@@ -38,18 +38,18 @@
     if keys.length is 0 then obj else undefined

-  deepest: (obj, key)->
-    if (typeof obj is not 'object' or typeof key is not 'string' or not key)
-      return undefined
-
-    keys = key.split('.')
-
-    key = keys.pop()
-    value = oby[key] if type of obj[key] is not undefined
-
-    while typeof obj is 'object' and keys.length > 0
-      value = oby[key]
-      obj = obj[keys.shift()]
-
-    if keys.length is 0 then obj else undefined
-})+#  deepest: (obj, key)->
+#    if (typeof obj is not 'object' or typeof key is not 'string' or not key)
+#      return undefined
+#
+#    keys = key.split('.')
+#
+#    key = keys.pop()
+#    value = oby[key] if type of obj[key] is not undefined
+#
+#    while typeof obj is 'object' and keys.length > 0
+#      value = oby[key]
+#      obj = obj[keys.shift()]
+#
+#    if keys.length is 0 then obj else undefined
+})


Fix table rows bug

Fix table rows bug

--- app/assets/javascripts/questions_helper.js.coffee
+++ app/assets/javascripts/questions_helper.js.coffee
@@ -15,7 +15,7 @@

 #{root_section}

Title	Type

-

#{question.title}	#{question.type}

+

#{question.title}	#{question.type}

       """
     )
@@ -23,7 +23,7 @@
   add_to_table: (question, root_section) ->
     $("##{@format_section_name(root_section)} tbody").append(
       """
-
#{question.title}	#{question.type}

+
#{question.title}	#{question.type}

       """
     )


input.detach -> input.remove in utils.fileInput()

--- app/assets/javascripts/uploadcare/utils.js.coffee
+++ app/assets/javascripts/uploadcare/utils.js.coffee
@@ -88,7 +88,7 @@

     input.on 'change', (e) ->
       fn(e)
-      input.detach()
+      input.remove()
       ns.fileInput(container, multiple, fn)

     # to make it posible to set `cursor:pointer` on button


[TEST] Added tests for increment/decrement blocks

--- test/blocks.coffee
+++ test/blocks.coffee
@@ -79,3 +79,16 @@
       '{% raw %}{% if value %}{{ value }}{% endif %}{% endraw %}',
       value: true

+describe "Increment", ->
+  it "increments like i++", ->
+    renderTest '1', '{% increment i %}', i: 1
+
+  it "interprents non-existing variables as 0", ->
+    renderTest '0', '{% increment i %}'
+
+describe "Decrements", ->
+  it "decrements like --i", ->
+    renderTest '0', '{% decrement i %}', i: 1
+
+  it "interprents non-existing variables as 0", ->
+    renderTest '-1', '{% decrement i %}'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -31,6 +31,12 @@
   equal color.b, 3
   equal color.a, 0.4

-test "accepts an array of length 4 as the first argument and an alpha value. The alpha value passed as the second argument overrides the alpha in the array"
+test "accepts an array of length 4 as the first argument and an alpha value. The alpha value passed as the second argument overrides the alpha in the array", ->
+  color = Color2([4, 20, 90, 0.1], 0.6)
+
+  equal color.r, 4
+  equal color.g, 20
+  equal color.b, 90
+  equal color.a, 0.6

 module()


Commented App.coffee

Commented App.coffee

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -1,8 +1,8 @@

-# App config
+# Application configuration manifest
 require './config'

-# Application object
+# Application class definition
 App = require './cordova_app'

 # Top-level layout configuration - singleton global variable
@@ -13,17 +13,18 @@
 # and requesting requisite permissions
 require './services/sms'

-# Components
-# TODO - what are components? - routeless services with views
-# Generally accessible anywhere in the application
+# Components routeless services with views that are
+# accessible anywhere in the application
+# Used to manage the header, sidebar, flash, and confirm UI elements
 require './components/header/component'
 require './components/sidebar/component'
 require './components/flash/component'
 require './components/confirm/component'

 # Modules
-# TODO - what are modules? - they have routes & entities
-# they contain 'routes' which represent end-points in the app
+# Modules represent collections of endpoints in the application.
+# They have routes and entities (models and collections)
+# Each route represents an endpoint, or 'page' in the app.
 require './modules/auth/router'
 require './modules/contact/router'
 require './modules/contact_method/router'
@@ -34,7 +35,7 @@

 # # # # # #

-# Starts application
+# Page has loaded, document is ready
 $(document).on 'ready', =>
   app = new App() # Instantiates new App


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -28,6 +28,6 @@
   fastEnemy.include(Follow)
   fastEnemy.follow(player)

-  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEnemy should head toward '
+  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEnemy should head toward player with velo'

 module()


Fix tests.

Fix tests.

--- test/helper.coffee
+++ test/helper.coffee
@@ -2,7 +2,7 @@
 chai.should()
 chai.use require 'chai-as-promised'

-{Api} = require '../lib'
+Api = require '../lib'

 before ->
   global.api = Api


activity: define parentId property on SocialMessages

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -124,6 +124,10 @@
     {accountOldId, replies, interactions}           = data
     {createdAt, deletedAt, updatedAt, typeConstant} = plain

+    parentId = if typeConstant is 'reply' and data.parentId
+    then data.parentId
+    else null
+
     cachedItem = kd.singletons.socialapi.retrieveCachedItem typeConstant, plain.id

     if cachedItem
@@ -138,6 +142,8 @@

     m = new remote.api.SocialMessage plain
     m.account = mapAccounts(accountOldId)[0]
+
+    m.parentId = parentId

     # since node.js(realtime) and golang(regular fetch) is returning different
     # timestamps, these are to unify all timestamp values. ~Umut


socialapi: don't depend on group on socialapicontroller

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -7,7 +7,7 @@
 checkFlag = require './util/checkFlag'
 whoami = require './util/whoami'
 kd = require 'kd'
-isKoding = require './util/isKoding'
+isFeedEnabled = require './util/isFeedEnabled'
 KDController = kd.Controller
 MessageEventManager = require './messageeventmanager'

@@ -139,7 +139,7 @@

     m = new remote.api.SocialMessage plain

-    m.account = if isKoding()
+    m.account = unless isFeedEnabled()
       mapAccounts(accountOldId)[0]
     else
       if isIntegrationMessage m
@@ -303,7 +303,7 @@
     # we only allow name, purpose and payload to be updated
     item.payload             = data.payload

-    if not isKoding() and item.typeConstant in ['privatemessage', 'bot']
+    if isFeedEnabled() and item.typeConstant in ['privatemessage', 'bot']
       item._originalName = item._originalName or item.name
       item.name = data.purpose
       item.purpose = item.payload?.description or ''


readability.

readability.

--- public/views/widget_view.coffee
+++ public/views/widget_view.coffee
@@ -6,10 +6,12 @@

   template: _.template """

+

+

Loading ...


Refactor tests

Refactor tests

--- test/integration/connection-test.coffee
+++ test/integration/connection-test.coffee
@@ -1054,6 +1054,7 @@
         test.ok(err instanceof Error)
         test.strictEqual err?.number, 515 # Cannot insert the value NULL

+<<<<<<< HEAD
 testArithAbort = (test, setting) ->
   test.expect(5)

@@ -1107,11 +1108,11 @@

   test.done()

-# Test that the default setting for DATEFIRST is 7
-exports.testDatefirstDefault = (test) ->
+testDateFirstImpl = (test, dateFirst) =>
   test.expect(3)

   config = getConfig()
+  config.options.datefirst = dateFirst

   connection = new Connection(config)

@@ -1122,7 +1123,7 @@

   request.on('row', (columns) ->
     dateFirst = columns[0].value
-    test.strictEqual(dateFirst, 7)
+    test.strictEqual(dateFirst, dateFirst || 7)
   )

   connection.on 'connect', (err) ->
@@ -1132,29 +1133,10 @@
   connection.on 'end', (info) ->
     test.done()

+# Test that the default setting for DATEFIRST is 7
+exports.testDatefirstDefault = (test) ->
+  testDateFirstImpl(test, undefined)
+
 # Test that the DATEFIRST setting can be changed via an optional configuration
-exports.testDatefirstDefault = (test) ->
-  test.expect(3)
-
-  config = getConfig()
-  config.options.datefirst = 3
-
-  connection = new Connection(config)
-
-  request = new Request('select @@datefirst', (err) ->
-    test.ifError(err)
-    connection.close()
-  )
-
-  request.on('row', (columns) ->
-    dateFirst = columns[0].value
-    test.strictEqual(dateFirst, 3)
-  )
-
-  connection.on 'connect', (err) ->
-    test.ifError(err)
-    connection.execSql(request)
-
-  connection.on 'end', (info) ->
-    test.done()
-
+exports.testDatefirstCustom = (test) ->
+  testDateFirstImpl(test, 3)


Add tests.

Add tests.

--- test/javascripts/turbolinks_test.coffee
+++ test/javascripts/turbolinks_test.coffee
@@ -88,6 +88,16 @@

       window.removeEventListener('page:before-change', listener)

+    it 'supports passing request headers', ->
+      @server.respondWith([200, { "Content-Type": "text/html" }, "<html>Hello World</html>"]);
+
+      Turbolinks.visit "/some_request", {headers: {'foo': 'bar', 'fizz': 'buzz'}}
+      @server.respond()
+
+      assert.equal 1, @server.requests.length
+      assert.equal 'bar', @server.requests[0].requestHeaders['foo']
+      assert.equal 'buzz', @server.requests[0].requestHeaders['fizz']
+
     describe 'with partial page replacement', ->
       it 'uses just the part of the response body we supply', ->
         @server.respondWith([200, { "Content-Type": "text/html" }, html_one]);


Avoid measuring lineHeight/defaultCharWidth on show unless needed

We only need to re-measure if the fontSize, fontFamily, or lineHeight
changed while the editor was hidden.

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -12,6 +12,8 @@
 module.exports =
 LinesComponent = React.createClass
   displayName: 'LinesComponent'
+
+  measureWhenShown: false

   render: ->
     if @isMounted()
@@ -44,9 +46,7 @@
     false

   componentDidUpdate: (prevProps) ->
-    {visible} = @props
-    if visible and not isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight', 'visible')
-      @measureLineHeightAndCharWidth()
+    @measureLineHeightAndCharWidthIfNeeded(prevProps)
     @clearScreenRowCaches() unless prevProps.lineHeight is @props.lineHeight
     @removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
     @updateLines()
@@ -198,7 +198,18 @@
   lineNodeForScreenRow: (screenRow) ->
     @lineNodesByLineId[@lineIdsByScreenRow[screenRow]]

+  measureLineHeightAndCharWidthIfNeeded: (prevProps) ->
+    {visible} = @props
+
+    unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight')
+      if visible
+        @measureLineHeightAndCharWidth()
+      else
+        @measureWhenShown = true
+    @measureLineHeightAndCharWidth() if visible and not prevProps.visible and @measureWhenShown
+
   measureLineHeightAndCharWidth: ->
+    @measureWhenShown = false
     node = @getDOMNode()
     node.appendChild(DummyLineNode)
     lineHeight = DummyLineNode.getBoundingClientRect().height


Fixed relationship panel link attachment [#144047533]

--- src/code/views/relation-inspector-view.coffee
+++ src/code/views/relation-inspector-view.coffee
@@ -5,6 +5,7 @@
 tr = require "../utils/translate"

 inspectorPanelStore = require '../stores/inspector-panel-store'
+graphStore = require '../stores/graph-store'

 {div, h2, label, span, input, p, i, select, option} = React.DOM

@@ -12,7 +13,9 @@

   displayName: 'RelationInspectorView'

-  mixins: [ inspectorPanelStore.mixin ]
+  # listen to graphStore so that the tab for the link will re-render when the link is fully defined
+  # (link-relation-view uses @props.graphStore.changeLink() to update the link)
+  mixins: [ inspectorPanelStore.mixin, graphStore.mixin ]

   renderTabforLink: (link) ->
     relationView = (LinkRelationView {link: link, graphStore: @props.graphStore})


ListController: customscrollbars made optional

--- src/components/list/listviewcontroller.coffee
+++ src/components/list/listviewcontroller.coffee
@@ -1,5 +1,6 @@
 KDView             = require './../../core/view.coffee'
 KDViewController   = require './../../core/viewcontroller.coffee'
+KDScrollView       = require './../scrollview/scrollview.coffee'
 KDCustomScrollView = require './../scrollview/customscrollview.coffee'
 KDListView         = require './../list/listview.coffee'
 KDLoaderView       = require './../loader/loaderview.coffee'
@@ -8,15 +9,16 @@

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

-    options.wrapper               ?= yes
-    options.scrollView            ?= yes
-    options.keyNav                ?= no
-    options.multipleSelection     ?= no
-    options.selection             ?= no
-    options.ownScrollBars         ?= no
-    options.startWithLazyLoader   ?= no
+    options.wrapper                ?= yes
+    options.scrollView             ?= yes
+    options.keyNav                 ?= no
+    options.multipleSelection      ?= no
+    options.selection              ?= no
+    options.ownScrollBars          ?= no
+    options.startWithLazyLoader    ?= no
     options.itemChildClass        or= null
     options.itemChildOptions      or= {}
+    options.useCustomScrollView    ?= no
     # rename these options
     options.noItemFoundWidget     or= null
     options.noMoreItemFoundWidget or= null
@@ -42,10 +44,13 @@
       @setListView listView = new KDListView viewOptions

     if options.scrollView
-      @customScrollView = new KDCustomScrollView
-        lazyLoadThreshold : options.lazyLoadThreshold
-        ownScrollBars     : options.ownScrollBars
-      @scrollView = @customScrollView.wrapper
+      if options.useCustomScrollView
+        @customScrollView = new KDCustomScrollView
+          lazyLoadThreshold : options.lazyLoadThreshold
+        @scrollView = @customScrollView.wrapper
+      else KDScrollView
+        @scrollView = new KDScrollView
+          lazyLoadThreshold : options.lazyLoadThreshold

     options.view = if options.wrapper
     then new KDView cssClass : "listview-wrapper"
@@ -76,7 +81,7 @@
     {windowController} = KD.singletons

     if scrollView
-      mainView.addSubView @customScrollView
+      mainView.addSubView @customScrollView or @scrollView
       @scrollView.addSubView @getListView()
       @showLazyLoader no  if startWithLazyLoader


View for the pictures

View for the pictures

--- src/diary/site/static/coffee/diary.app.coffee
+++ src/diary/site/static/coffee/diary.app.coffee
@@ -45,6 +45,13 @@
             <time datetime="{{post.date}}">{{post.date|date:'fullDate'}}</time>

+
+

+
+
+
+

+

"""
 ]
@@ -124,7 +131,7 @@

   restrict: "A"
   template: """

-
+


Close span only if there was at least one brush used

--- app/assets/javascripts/player/terminal_view.js.coffee
+++ app/assets/javascripts/player/terminal_view.js.coffee
@@ -66,9 +66,9 @@
       else
         html += ' '

-    html += '' if html.length > 0
+    html += '' if prevBrush

-    @$el.find(".line:eq(" + n + ")").html html #.join("")
+    @$el.find(".line:eq(" + n + ")").html html

   spanFromBrush: (brush, hasCursor) ->
     key = "#{AsciiIo.Brush.hash(brush)}_#{hasCursor}"


created resizeGrid method

created resizeGrid method

--- app/coffee/drm-flexible-grid/drm-flexible-grid.coffee
+++ app/coffee/drm-flexible-grid/drm-flexible-grid.coffee
@@ -72,6 +72,28 @@

                 that.height(imageHeight).hide()

+        resizeGrid: (items) =>
+            self = @
+            tallestColumn = 0
+            columnHeights = []
+
+            i = 0
+            until i is self.imagesPerRow
+                columnHeights.push 0
+                i = i + 1
+
+            $.each items, (key, value) ->
+                that = $ value
+                columnNum = that.data 'column'
+                height = that.outerHeight true
+
+                columnHeights[columnNum] += height
+
+            $.each columnHeights, (key, value) ->
+                if value > tallestColumn then tallestColumn = value
+
+            self.grid.css 'height': tallestColumn + 40
+
         positionListItems: =>
             self = @
             items = self.grid.find '.drm-grid-item'
@@ -104,9 +126,9 @@
                         'left': 0
                         'position': 'relative'

-                that.fadeIn 1000
+                that.fadeIn 1000

-            @grid.css 'height': (items.last().outerHeight(true) + items.last().position().top) + 500
+            self.resizeGrid items

         filterListItems: (filter) =>
             self = @


title accuracy is important

title accuracy is important

--- test/sample.coffee
+++ test/sample.coffee
@@ -6,7 +6,7 @@
 describe 'Load page: http://localhost:4567 and', ->
   browser  = null
   testOpts =
-    title: 'AcmeCredit'
+    title: 'My Way!'

   before ->
     browser = new Browser()


hard delete a doc on health check

hard delete a doc on health check


added test to deal with optional culprit. changed other tests to reflect change in argument order to error

--- test/sentry.coffee
+++ test/sentry.coffee
@@ -94,7 +94,7 @@
       .post("/api/#{sentry_settings.project_id}/store/", 'error')
       .reply(200, {"id": "534f9b1b491241b28ee8d6b571e1999d"}) # mock sentry response with a random uuid

-    @sentry.error 'not an Error', 'message', 'path/to/logger'
+    @sentry.error 'not an Error', 'path/to/logger', 'culprit'
     scope.done()

   it 'send error correctly', ->
@@ -110,7 +110,23 @@
       .post("/api/#{sentry_settings.project_id}/store/", 'error')
       .reply(200, {"id": "534f9b1b491241b28ee8d6b571e1999d"}) # mock sentry response with a random uuid

-    @sentry.error new Error('Error message'), 'message', '/path/to/logger'
+    @sentry.error new Error('Error message'), '/path/to/logger', 'culprit'
+    scope.done()
+
+  it.only 'send error correctly when culprit not defined', ->
+    scope = nock('https://app.getsentry.com')
+      .matchHeader('X-Sentry-Auth'
+      , "Sentry sentry_version=4, sentry_key=#{sentry_settings.key}, sentry_secret=#{sentry_settings.secret}, sentry_client=sentry-node")
+      .filteringRequestBody (path) ->
+        params = JSON.parse path
+        if _.every(['message','logger','server_name','platform','level'], (prop) -> _.has(params, prop))
+          if params.extra?.stacktrace?
+            return 'error'
+        throw Error 'Body of Sentry error request is incorrect.'
+      .post("/api/#{sentry_settings.project_id}/store/", 'error')
+      .reply(200, {"id": "534f9b1b491241b28ee8d6b571e1999d"}) # mock sentry response with a random uuid
+
+    @sentry.error new Error('Error message'), '/path/to/logger', null
     scope.done()

   it 'send message correctly', ->
@@ -172,4 +188,4 @@
     @sentry.once 'note', (err) ->
       assert.equal err.message, "CONVERT_TO_STRING: #{JSON.stringify logger}"
       done()
-    @sentry.error new Error('Error message'), "some culprit", logger
+    @sentry.error new Error('Error message'), logger, "some culprit"


Use prefix on name property of radio buttons

--- src/components/exercise/question-format-type.cjsx
+++ src/components/exercise/question-format-type.cjsx
@@ -16,6 +16,9 @@

 QuestionFormatType = React.createClass

+  propTypes:
+    questionId: React.PropTypes.number.isRequired
+    sync: React.PropTypes.func.isRequired

   update: -> @forceUpdate()

@@ -24,10 +27,6 @@

   componentWillUnmount: ->
     QuestionStore.removeChangeListener(@update)
-
-  propTypes:
-    questionId: React.PropTypes.number.isRequired
-    sync: React.PropTypes.func.isRequired

   updateFormat: (ev) ->
     selected = ev.target.value
@@ -51,7 +50,7 @@
         <BS.Input
           key={id}
           type="radio"
-          name='formats'
+          name={"#{@props.questionId}-formats"}
           label={name}
           value={id}
           onClick={@updateFormat}


hopefully fix travis build

hopefully fix travis build

--- test/string.coffee
+++ test/string.coffee
@@ -1,43 +1,44 @@
-fragments = require '../fragments'
+fragments = require '../src/fragments'
+app = fragments()

 module.exports =

   'camelToSnake': (test) ->
-    fragments (camelToSnake) ->
+    app (camelToSnake) ->
       test.equal 'camel_case', camelToSnake('camelCase')
       test.done()

   'snakeToCamel': (test) ->
-    fragments (snakeToCamel) ->
+    app (snakeToCamel) ->
       test.equal 'snakeCase', snakeToCamel('snake_case')
       test.done()

   'camelToHyphen': (test) ->
-    fragments (camelToHyphen) ->
+    app (camelToHyphen) ->
       test.equal 'camel-case', camelToHyphen('camelCase')
       test.done()

   'hyphenToCamel': (test) ->
-    fragments (hyphenToCamel) ->
+    app (hyphenToCamel) ->
       test.equal 'hyphenDelimited', hyphenToCamel('hyphen-delimited')
       test.done()

   'colonToSnake': (test) ->
-    fragments (colonToSnake) ->
+    app (colonToSnake) ->
       test.equal 'colon_delimited', colonToSnake('colon:delimited')
       test.done()

   'snakeToColon': (test) ->
-    fragments (snakeToColon) ->
+    app (snakeToColon) ->
       test.equal 'snake:case', snakeToColon('snake_case')
       test.done()

   'hyphenColonToCamelSnake': (test) ->
-    fragments (hyphenColonToCamelSnake) ->
+    app (hyphenColonToCamelSnake) ->
       test.equal 'hyphen_colonTo_camelSnake', hyphenColonToCamelSnake('hyphen:colon-to:camel-snake')
       test.done()

   'camelSnakeToHyphenColon': (test) ->
-    fragments (camelSnakeToHyphenColon) ->
+    app (camelSnakeToHyphenColon) ->
       test.equal 'camel:snake-to:hyphen-colon', camelSnakeToHyphenColon('camel_snakeTo_hyphenColon')
       test.done()


Fixes #110: Cancel accounts poller timeout after the first poll;

--- server/lib/accounts-poller.coffee
+++ server/lib/accounts-poller.coffee
@@ -17,7 +17,7 @@
             clearTimeout @timeout
             @timeout = null

-        # day after between 02:00am and 04:00am
+        # day after between 02:00am and 04:00am UTC
         delta = Math.random() * 120 | 0 # opa asm.js style
         now = moment()
         nextUpdate = now.clone().add(1, 'days')
@@ -26,12 +26,9 @@
                             .seconds(0)

         format = "DD/MM/YYYY [at] HH:mm:ss"
-        msg = "> Next check of bank accounts on #{nextUpdate.format(format)}"
-        console.log msg
-        @timeout = setTimeout(
-            () =>
-                @checkAllAccesses()
-            , nextUpdate.diff(now))
+        console.log "> Next check of bank accounts on #{nextUpdate.format(format)}"
+
+        @timeout = setTimeout (@checkAllAccesses.bind @), nextUpdate.diff(now)

     checkAllAccesses: ->
@@ -47,10 +44,9 @@

             async.each accesses, process, (err) =>
                 if err?
-                    console.log "Error when fetching operations: #{err}"
+                    console.log "Error when polling accounts: #{err}"
                     return
                 console.log "All accounts have been polled."
-                @timeout = null
                 @prepareNextCheck()

 module.exports = accountPoller = new AccountsPoller


Add a focus method which can be called from outer space

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -66,6 +66,11 @@
   # un**** our editable after paste or other ****
   cleanEditable: =>
     @_cleanChildNodes(@$editable[0])
+
+  # can be called from outer space to set focus on mentions-kinder editable
+  # EXAMPLE: $('textarea').data('mentionsKinder').focus()
+  focus: ->
+    @$editable.focus()

   # start autocompletion for trigger char
   # Create new temp mention, create autompleter and set caret correctly


remove console logs

remove console logs

--- client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
@@ -71,11 +71,10 @@

   updateDomainRules:(field, op, value)->
-    console.log "updating domain #{@getData().domain.domain} for #{field} with #{op} as #{value}"
     fieldMethod = switch field
       when "whiteList" then "updateWhiteList"
       when "blockList" then "updateBlockList"
-    KD.remote.api.JDomain[fieldMethod] {domainName:@getData().domain.domain, field, op, value}, (err)->
+    KD.remote.api.JDomain[fieldMethod] {domainName:@getData().domain.domain, op, value}, (err)->
       if err
         new KDNotificationView
           type  : "top"


DerpFox

DerpFox

--- vendor/assets/javascripts/expressionui.popover.js.coffee
+++ vendor/assets/javascripts/expressionui.popover.js.coffee
@@ -178,7 +178,7 @@

   $.fn.popover = (method, evt) ->
     if evt?
-      evt.stopPropagation()
+      evt.stopPropagation() if evt.stopPropagation?
     if methods[method]
       methods[method].apply this, Array::slice.call(arguments, 1)
     else if typeof method is "object" or not method


bugfix: typo is corrected

bugfix: typo is corrected

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -98,7 +98,7 @@
           callback message: "No such VM!"

   info:(vm, callback)->
-    [callback, vm] = [vm, callback]  if 'function' is typeof callback
+    [callback, vm] = [vm, callback]  if 'function' is typeof vm
     @_runWrapper 'vm.info', vm, (err, info)=>
       warn "[VM-#{vm}]", err  if err
       if err?.name is "UnderMaintenanceError"


Convert old bundle names until specs are updated

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -94,6 +94,10 @@
     packagePath = path.join(@resourcePath, 'node_modules', name)
     return packagePath if @isInternalPackage(packagePath)

+    #TODO Remove once all package specs have been updated
+    if match = /(.+)-tmbundle$/.exec(name)
+      @resolvePackagePath("language-#{match[1]}")
+
   isInternalPackage: (packagePath) ->
     {engines} = Package.loadMetadata(packagePath, true)
     engines?.atom?


Reloading after cropping

Reloading after cropping

--- app/assets/javascripts/active_admin_jcrop/crop_modal.js.coffee
+++ app/assets/javascripts/active_admin_jcrop/crop_modal.js.coffee
@@ -38,6 +38,7 @@
                   data:
                     image_data: cropper
                   success: ->
+                    location.reload();
                     submitButton.value = previousValue
                     submitButton.disabled = false
                   error: ->


Use double quotes in image uploads instead of single quotes. Our sanitizer seems to hate single quotes.

--- app/assets/javascripts/discourse/views/composer_view.js.coffee
+++ app/assets/javascripts/discourse/views/composer_view.js.coffee
@@ -192,7 +192,7 @@
       done: (e, data) =>
         @set('loadingImage', false)
         upload = data.result
-        html = ""
+        html = ""
         @addMarkdown(html)

       fail: (e, data) =>


support 'off' and 'fast' transition temp option

--- src/scripts/appview.coffee
+++ src/scripts/appview.coffee
@@ -17,8 +17,22 @@
   ### public ###
   init: ->
     super()
-    if localStorage.transition isnt 'off' # tmp
-      document.documentElement.className += 'transition'
+    _trans = localStorage.transition
+    if _trans isnt 'off' # tmp
+      el = document.documentElement
+      el.className += ' transition'
+      el.className += ' fast' if _trans is 'fast' # tmp
+    # document.body.onresize = =>
+    #   if @height isnt (h = window.innerHeight)
+    #     console.log h, @height
+    #     alert 'resize ' + h
+    #     @el.style.height = h + 'px'
+    #   return
+    # document.addEventListener 'touchmove', (e) ->
+    #   if e.target is document.body
+    #     e.preventDefault()
+    #     false
+    # , false
     # init views
     @login.init()
     # @chat.init() # chat init after login


working on snapshot mounting

working on snapshot mounting

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -8,6 +8,7 @@

 {join}      = require('path')
 fs          = require('fs')
+os          = require('os')

 async       = require('async')
 winston     = require('winston')
@@ -847,6 +848,38 @@
             process.exit(0)
     )

+# ssh to the given compute server and setup an sshfs mount on
+# it to this machine, if it isn't already setup.
+# This must be run as root.
+exports.mount_snapshot_path = (opts) ->
+    opts = defaults opts,
+        host : required     # hostname of compute server
+        cb   : required
+    server = os.hostname()  # name of this server
+    numfiles = undefined
+    async.series([
+        (cb) ->
+            mnt = "/mnt/snapshots/#{server}/"
+            misc_node.execute_code
+                command : 'ssh'
+                args    : [opts.host, "fusermount -u #{mnt}; mkdir -p #{mnt}/; chmod a+rx /mnt/snapshots/ #{mnt}; sshfs -o ro,allow_other,default_permissions #{server}:/ #{mnt}/; ls #{mnt}/"]
+                cb      : (err, output) ->
+                    if err
+                        cb(err)
+                    else
+                        numfiles = misc.split(output.stdout).length
+                        cb()
+        (cb) ->
+            fs.readdir "/#{server}/.zfs/snapshot", (err, files) ->
+                if err
+                    cb(err)
+                else
+                    if Math.abs(files.length - numfiles) > 5
+                        cb("wrong number of files on remote - mount likely failed")
+                    else
+                        cb()
+    ], opts.cb)
+
 ###
 Everything below is one-off code -- has no value, except as examples.
 ###


Test getKeys.

Test getKeys.

--- services/web/test/UnitTests/coffee/ReferencesSearch/ReferencesSearchControllerTests.coffee
+++ services/web/test/UnitTests/coffee/ReferencesSearch/ReferencesSearchControllerTests.coffee
@@ -19,8 +19,62 @@
 			'settings-sharelatex': @settings = {
 				apis: {web: {url: 'http://some.url'}}
 			}
-			'../Project/ProjectLocator': @ProjectLocator = {findElement: sinon.stub()}
-			'./ReferencesSearchHandler': @ReferencesSearchHandler = {indexFile: sinon.stub(), getKeys: sinon.stub()}
+			'../Project/ProjectLocator': @ProjectLocator = {
+				findElement: sinon.stub()
+			}
+			'./ReferencesSearchHandler': @ReferencesSearchHandler = {
+				indexFile: sinon.stub()
+				getKeys: sinon.stub()
+			}
+
+	describe 'getKeys', ->
+
+		beforeEach ->
+			@req = new MockRequest()
+			@req.params.Project_id = @project_id
+			@res = new MockResponse()
+			@data =
+				projectId: @project_id,
+				keys: ['one', 'two', 'three']
+			@ReferencesSearchHandler.getKeys.callsArgWith(1, null, @data)
+
+		describe 'when remote service works', ->
+
+			it 'should produce a json response', (done) ->
+				@res.json = (data) =>
+					data.should.not.equal null
+					data.should.deep.equal @data
+					done()
+				@controller.getKeys(@req, @res)
+
+			it 'should call getKeys on ReferencesSearchHandler', (done) ->
+				@res.json = (data) =>
+					@ReferencesSearchHandler.getKeys
+					.calledOnce.should.equal true
+					@ReferencesSearchHandler.getKeys
+					.calledWith(@project_id).should.equal true
+					done()
+				@controller.getKeys(@req, @res)
+
+		describe 'when remote service produces an error', ->
+
+			beforeEach ->
+				@ReferencesSearchHandler.getKeys.callsArgWith(1, new Error('nope'))
+
+			it 'should produce a 500 response', (done) ->
+				@res.send = (status) =>
+					status.should.equal 500
+					done()
+				@controller.getKeys(@req, @res)
+
+			it 'should call getKeys on ReferencesSearchHandler', (done) ->
+				@res.send = (status) =>
+					@ReferencesSearchHandler.getKeys
+					.calledOnce.should.equal true
+					@ReferencesSearchHandler.getKeys
+					.calledWith(@project_id).should.equal true
+					done()
+				@controller.getKeys(@req, @res)

 	describe 'indexFile', ->

@@ -102,13 +156,15 @@
 			beforeEach ->
 				@req.body = {docId: @doc_id}
 				@ProjectLocator.findElement.callsArgWith(1, null, {})
-				@ReferencesSearchHandler.indexFile.callsArgWith(2, new Error('something went wrong'))
+				@ReferencesSearchHandler.indexFile
+				.callsArgWith(2, new Error('something went wrong'))

 			it 'should call ReferencesSearchHandler.indexFile', (done) ->
 				@res.send = (status) =>
 					@ReferencesSearchHandler.indexFile.calledOnce.should.equal true
 					expected_url = "http://some.url/project/2222/doc/3333"
-					@ReferencesSearchHandler.indexFile.calledWith(@project_id, expected_url).should.equal true
+					@ReferencesSearchHandler.indexFile
+					.calledWith(@project_id, expected_url).should.equal true
 					done()
 				@controller.indexFile(@req, @res)


Enable previously failing test

Enable previously failing test

--- test/server/doc.coffee
+++ test/server/doc.coffee
@@ -122,8 +122,7 @@

   describe '#create', ->

-    xit 'calls callback', (done)->
-      # TODO this currently fails, but should not
+    it 'calls callback', (done)->
       @doc.create(textType, done)
       @doc.flush()
       sendMessage connection.sent[0]


default port to 3042

default port to 3042

--- services/notifications/app.coffee
+++ services/notifications/app.coffee
@@ -39,6 +39,6 @@
 	res.send 404

 host = Settings.internal?.notifications?.host || "localhost"
-port = Settings.internal?.notifications?.port || 3041
+port = Settings.internal?.notifications?.port || 3042
 app.listen port, host, ->
 	logger.info "notifications starting up, listening on #{host}:#{port}"