modernized Assignment tests - all passing

--- test/tests/CaffeineScript/Parser/Working/Assignment.coffee
+++ test/tests/CaffeineScript/Parser/Working/Assignment.coffee
@@ -2,52 +2,47 @@
 {log, formattedInspect} = Neptune.Art.Foundation
 {Parser} = CaffeineScript

-{parseTests, illegalSyntaxTests} = require '../../Helper'
+{parseTestSuite, illegalSyntaxTests} = require '../../Helper'

-module.exports = suite:
-  basic: ->
-    parseTests
-      "a = 1": "let a; a = 1;"
-      "b = 1 + 2": "let b; b = 1 + 2;"
+module.exports = suite: parseTestSuite
+  basic:
+    "a = 1": "let a; a = 1;"
+    "b = 1 + 2": "let b; b = 1 + 2;"

-  this: ->
-    parseTests
-      "@f = 1":         "this.f = 1;"
+  this:
+    "@f = 1":         "this.f = 1;"

   withAccessors:
-    basic: ->
-      parseTests
-        "f().foo = 1":   "f().foo = 1;"
-        "f.foo.bar = 1": "f.foo.bar = 1;"
+    basic:
+      "f().foo = 1":   "f().foo = 1;"
+      "f.foo.bar = 1": "f.foo.bar = 1;"

-    this: ->
-      parseTests
-        "@f().foo = 1":   "this.f().foo = 1;"
-        "@f.foo.bar = 1": "this.f.foo.bar = 1;"
+    this:
+      "@f().foo = 1":   "this.f().foo = 1;"
+      "@f.foo.bar = 1": "this.f.foo.bar = 1;"

-        "@.f = 1":          "this.f = 1;"
-        "@.f().foo = 1":    "this.f().foo = 1;"
-        "@.f.foo.bar = 1":  "this.f.foo.bar = 1;"
+      "@.f = 1":          "this.f = 1;"
+      "@.f().foo = 1":    "this.f().foo = 1;"
+      "@.f.foo.bar = 1":  "this.f.foo.bar = 1;"

-  block: ->
-    parseTests
-      """
-      foo =
-        bar: 1
-      """: "let foo; foo = {bar: 1};"
+  block:
+    """
+    foo =
+      bar: 1
+    """: "let foo; foo = {bar: 1};"

   multiline:
-    basic: ->
-      parseTests
-        "f\n.foo = 1":   "f.foo = 1;"
-        "f\n|| foo = 1": "let foo; f || (foo = 1);"
+    basic:
+      "f\n.foo = 1":   "f.foo = 1;"
+      "f\n|| foo = 1": "let foo; f || (foo = 1);"

-    compound: ->
-      parseTests
-        "f\n.foo.bar = 1":   "f.foo.bar = 1;"
-        "f\n.foo().bar = 1": "f.foo().bar = 1;"
+    compound:
+      "f\n.foo.bar = 1":   "f.foo.bar = 1;"
+      "f\n.foo().bar = 1": "f.foo().bar = 1;"

-    block: ->
-        parseTests
-          "f\n  .foo = 1":     "f.foo = 1;"
-          "f\n  || foo = 1":   "let foo; f || (foo = 1);"
+    block:
+      "f\n  .foo = 1":     "f.foo = 1;"
+      "f\n  || foo = 1":   "let foo; f || (foo = 1);"
+
+  # destructuring: ->
+  #   pa


fix(atom): Never allow the window to be set to fractional sizes

--- src/atom.coffee
+++ src/atom.coffee
@@ -416,6 +416,8 @@
   # * `width` The {Number} of pixels.
   # * `height` The {Number} of pixels.
   setSize: (width, height) ->
+    width = Math.round(width)
+    height = Math.round(height)
     @getCurrentWindow().setSize(width, height)

   # Essential: Transition and set the size of the current window.
@@ -426,6 +428,8 @@
   setSizeAnimated: (width, height, duration=400) ->
     cubicInOut = (t) -> if t<.5 then 4*t**3 else (t-1)*(2*t-2)**2+1
     win = @getCurrentWindow()
+    width = Math.round(width)
+    height = Math.round(height)
     startBounds = win.getBounds()

     startTime = Date.now()
@@ -447,6 +451,7 @@

   setMinimumWidth: (minWidth) ->
     win = @getCurrentWindow()
+    minWidth = Math.round(minWidth)
     minHeight = win.getMinimumSize()[1]
     win.setMinimumSize(minWidth, minHeight)

@@ -465,6 +470,8 @@
   # * `x` The {Number} of pixels.
   # * `y` The {Number} of pixels.
   setPosition: (x, y) ->
+    x = Math.round(x)
+    y = Math.round(y)
     ipc.send('call-window-method', 'setPosition', x, y)

   # Extended: Get the current window


remove useless console.log

remove useless console.log

--- src/run.coffee
+++ src/run.coffee
@@ -29,7 +29,6 @@
       unless channel?
         for channelId, info of team.props.connection.client.channels
           if channelId == msg.channel
-            console.log info.name
             channel = new React.createElement Channel, {key: channelId, name: info.name, info: info, team: team, messages: []}
             team.props.channels.push(channel)


Eq: Fix bug on null original program

Eq: Fix bug on null original program

--- src/equality-checker.coffee
+++ src/equality-checker.coffee
@@ -13,6 +13,9 @@
             [ { index: 0 }, { type: 'mtexec', index: -1 } ]

     programs = new ProgramFamily original, others, { indexFilter }
+
+    unless all
+        programs.original ?= programs.others.shift()

     # Check that all programs are executable


:memo: Pluralize return

:memo: Pluralize return

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -140,7 +140,7 @@
   # filePath     - A {String} file path.
   # fileContents - A {String} of text for the file path.
   #
-  # Return a {Grammar}, never null.
+  # Returns a {Grammar}, never null.
   selectGrammar: (filePath, fileContents) ->
     _.max @grammars, (grammar) -> grammar.getScore(filePath, fileContents)


Listen for correct path loader progress event

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -221,8 +221,11 @@
         @projectPaths = paths
         @reloadProjectPaths = false
         @populateProjectPaths(options)
-      @loadPathsTask.on 'load-paths:paths-loaded', (paths) =>
-        @loadingBadge.text(humanize.intcomma(paths.length))
+
+      pathsFound = 0
+      @loadPathsTask.on 'load-paths:paths-found', (paths) =>
+        pathsFound += paths.length
+        @loadingBadge.text(humanize.intcomma(pathsFound))

   populateOpenBufferPaths: ->
     editSessions = project.getEditSessions().filter (editSession) ->


Delegate Composition#insertFile to #insertFiles

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -110,9 +110,7 @@
     @setSelection(selectedRange)

   insertFile: (file) ->
-    if @delegate?.compositionShouldAcceptFile(file)
-      attachment = Trix.Attachment.attachmentForFile(file)
-      @insertAttachment(attachment)
+    @insertFiles([file])

   insertFiles: (files) ->
     text = new Trix.Text


Refactor, simplify insertLineBreak logic

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -109,7 +109,6 @@
     block = @document.getBlockAtIndex(endLocation.index)
     breaksOnReturn = block.breaksOnReturn()
     previousCharacter = block.getPreviousCharacter(endLocation.offset)
-    nextCharacter = block.getNextCharacter(endLocation.offset)

     if block.hasAttributes()
       if block.isListItem()
@@ -124,13 +123,9 @@
       else
         if block.isEmpty()
           @removeLastBlockAttribute()
-        else if previousCharacter is "\n"
-          @breakFormattedBlock()
-        else if breaksOnReturn and nextCharacter is "\n"
-          @breakFormattedBlock()
         else if breaksOnReturn and startLocation.offset is 0
           @insertBlockBreak()
-        else if breaksOnReturn
+        else if previousCharacter is "\n" or breaksOnReturn
           @breakFormattedBlock()
         else
           @insertString("\n")


Improve log messages

Improve log messages

--- services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
@@ -171,8 +171,8 @@
 			return next(error) if error?
 			planCode = req.body.plan_code
 			if !planCode?
-				err = new Error('plan_code not defined')
-				logger.err {user_id: user._id, err}, "error updating subscription"
+				err = new Error('plan_code is not defined')
+				logger.err {user_id: user._id, err, planCode}, "[Subscription] error in updateSubscription form"
 				return next(err)
 			logger.log planCode: planCode, user_id:user._id, "updating subscription"
 			SubscriptionHandler.updateSubscription user, planCode, null, (err)->


Speed up HTMLParser

Speed up HTMLParser

Using "basic" sample HTML

  Before: Mean: 3.313ms; 1 x 302 ops/sec ±7.28% (46 runs sampled)
  After:  Mean: 2.320ms; 0 x 431 ops/sec ±7.63% (46 runs sampled)

Using "long text" sample HTML

  Before: Mean: 687.125ms; 1 x 1.46 ops/sec ±4.13% (8 runs sampled)
  After:  Mean: 65.867ms; 0 x 15.18 ops/sec ±11.92% (30 runs sampled)

--- src/trix/models/html_parser.coffee
+++ src/trix/models/html_parser.coffee
@@ -1,4 +1,4 @@
-{arraysAreEqual, makeElement, tagName, walkTree,
+{arraysAreEqual, benchmark, makeElement, tagName, walkTree,
  findClosestElementFromNode, elementContainsNode, elementMatchesSelector} = Trix

 class Trix.HTMLParser extends Trix.BasicObject
@@ -14,7 +14,10 @@
     @blockElements = []
     @processedElements = []

-  parse: ->
+  getDocument: ->
+    Trix.Document.fromJSON(@blocks)
+
+  parse: benchmark "HTMLParser#parse", ->
     try
       @createHiddenContainer()
       html = sanitizeHTML(@html)
@@ -49,7 +52,7 @@
   appendBlockForElement: (element) ->
     if @isBlockElement(element) and not @isBlockElement(element.firstChild)
       attributes = getBlockAttributes(element)
-      unless elementContainsNode(@currentBlockElement, element) and arraysAreEqual(attributes, @currentBlock.getAttributes())
+      unless elementContainsNode(@currentBlockElement, element) and arraysAreEqual(attributes, @currentBlock.attributes)
         @currentBlock = @appendBlockForAttributes(attributes, element)
         @currentBlockElement = element

@@ -104,34 +107,40 @@
           unless element.parentNode.firstChild is element
             @appendStringWithAttributes(" ")

-
   appendBlockForAttributes: (attributes, element) ->
-    @text = new Trix.Text
-    block = new Trix.Block @text, attributes
+    @blockElements.push(element)
+    block = blockForAttributes(attributes)
     @blocks.push(block)
-    @blockElements.push(element)
     block

   appendStringWithAttributes: (string, attributes) ->
-    text = Trix.Text.textForStringWithAttributes(string, attributes)
-    @appendText(text)
-
-  appendAttachmentForAttributes: (attributes, textAttributes) ->
-    attachment = Trix.Attachment.attachmentForAttributes(attributes)
-    text = Trix.Text.textForAttachmentWithAttributes(attachment, textAttributes)
-    @appendText(text)
-
-  appendText: (text) ->
+    @appendPiece(pieceForString(string, attributes))
+
+  appendAttachmentForAttributes: (attachment, attributes) ->
+    @appendPiece(pieceForAttachment(attachment, attributes))
+
+  appendPiece: (piece) ->
     if @blocks.length is 0
       @appendBlockForAttributes({})
-
-    @text = @text.appendText(text)
-    index = @blocks.length - 1
-    @replaceTextForBlockAtIndex(@text, index)
-
-  replaceTextForBlockAtIndex: (text, index) ->
-    block = @blocks[index]
-    @blocks[index] = block.copyWithText(text)
+    @blocks[@blocks.length - 1].text.push(piece)
+
+  appendStringToTextAtIndex: (string, index) ->
+    {text} = @blocks[index]
+    piece = text[text.length - 1]
+
+    if piece?.type is "string"
+      piece.string += string
+    else
+      text.push(pieceForString(string))
+
+  prependStringToTextAtIndex: (string, index) ->
+    {text} = @blocks[index]
+    piece = text[0]
+
+    if piece?.type is "string"
+      piece.string = string + piece.string
+    else
+      text.unshift(pieceForString(string))

   getMarginOfBlockElementAtIndex: (index) ->
     if element = @blockElements[index]
@@ -145,21 +154,25 @@

   translateBlockElementMarginsToNewlines: ->
     defaultMargin = @getMarginOfDefaultBlockElement()
-    textForNewline = Trix.Text.textForStringWithAttributes("\n")

     for block, index in @blocks when margin = @getMarginOfBlockElementAtIndex(index)
-      text = block.getTextWithoutBlockBreak()
-
       if margin.top > defaultMargin.top * 2
-        newText = text.insertTextAtPosition(textForNewline, 0)
-        @replaceTextForBlockAtIndex(newText, index)
+        @prependStringToTextAtIndex("\n", index)

       if margin.bottom > defaultMargin.bottom * 2
-        newText = text.appendText(textForNewline)
-        @replaceTextForBlockAtIndex(newText, index)
-
-  getDocument: ->
-    new Trix.Document @blocks
+        @appendStringToTextAtIndex("\n", index)
+
+  pieceForString = (string, attributes = {}) ->
+    type = "string"
+    {string, attributes, type}
+
+  pieceForAttachment = (attachment, attributes = {}) ->
+    type = "attachment"
+    {attachment, attributes, type}
+
+  blockForAttributes = (attributes = {}) ->
+    text = []
+    {text, attributes}

   getTextAttributes = (element) ->
     attributes = {}
@@ -228,4 +241,3 @@
     dimensions.width = parseInt(width, 10) if width
     dimensions.height = parseInt(height, 10) if height
     dimensions
-


Add GitRun to git palette

Add GitRun to git palette

--- lib/git-plus-commands.coffee
+++ lib/git-plus-commands.coffee
@@ -28,6 +28,7 @@
   GitStatus              = require './models/git-status'
   GitTags                = require './models/git-tags'
   GitUnstageFiles        = require './models/git-unstage-files'
+  GitRun                 = require './models/git-run'
   GitMerge               = require './models/git-merge'

   commands = []
@@ -68,6 +69,7 @@
     commands.push ['git-plus:status', 'Status', -> GitStatus()]
     commands.push ['git-plus:tags', 'Tags', -> GitTags()]
     commands.push ['git-plus:unstage-files', 'Unstage Files', -> GitUnstageFiles()]
+    commands.push ['git-plus:run', 'Run', -> GitRun()]
     commands.push ['git-plus:merge', 'Merge', -> GitMerge()]

   commands


fix making a user an admin multiple times over

--- src/scripts/auth.coffee
+++ src/scripts/auth.coffee
@@ -101,7 +101,7 @@
     displayRoles = user.roles

     if user.id.toString() in admins
-      displayRoles.push('admin')
+      displayRoles.push('admin') unless 'admin' in user.roles

     if displayRoles.length == 0
       msg.reply "#{name} has no roles."
@@ -112,7 +112,8 @@
     adminNames = []
     for admin in admins
       user = robot.brain.userForId(admin)
-      adminNames.push user.name if user?
+      unless robot.auth.hasRole(msg.envelope.user,'admin')
+        adminNames.push user.name if user?

     if adminNames.length > 0
       msg.reply "The following people have the 'admin' role: #{adminNames.join(', ')}"


fixes help error on unknown help matcher

--- src/scripts/help.coffee
+++ src/scripts/help.coffee
@@ -58,6 +58,9 @@
       cmds = cmds.filter (cmd) ->
         cmd.match new RegExp(msg.match[1], 'i')

+      if cmds.length == 0
+        msg.send "No available commands match #{msg.match[1]}"
+        return
     emit = cmds.join "\n"

     unless robot.name.toLowerCase() is 'hubot'


support usernames like foo.bar and foo-bar in the tell script

--- src/scripts/tell.coffee
+++ src/scripts/tell.coffee
@@ -3,7 +3,7 @@
 # tell <username> <some message>
 module.exports = (robot) ->
    localstorage = {}
-   robot.respond /tell (\w*) (.*)/i, (msg) ->
+   robot.respond /tell ([\w.-]*) (.*)/i, (msg) ->
      datetime = new Date()
      tellmessage = msg.match[1] + ": " + msg.message.user.name + " @ " + datetime.toTimeString() + " said: " + msg.match[2] + "\r\n"
      if localstorage[msg.match[1]] == undefined


Improve regex for 'today' queries

Improve regex for 'today' queries

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -49,6 +49,23 @@
     isDM(name, channel) or
     isClockChannel(channel) or
     isProjectChannel(channel)
+
+  toTimeStr = (duration) ->
+    hours = Math.floor duration
+    if hours is 0
+      hoursStr = ''
+    else if hours is 1
+      hoursStr = "#{hours} hour"
+    else
+      hoursStr = "#{hours} hours"
+    minutes = Math.round((duration - hours) * 60)
+    if minutes is 0
+      minutesStr = ''
+    else if minutes is 1
+      minutesStr = "#{minutes} minute"
+    else
+      minutesStr = "#{minutes} minutes"
+    return "#{hoursStr}#{if minutes > 0 then ', ' else ''}#{minutesStr}"

   parse = (res, msg, mode) ->
     user = Organization.getUserBySlackName res.message.user.name
@@ -320,7 +337,7 @@
                          Logger

   # User feedback
-  robot.respond /(today|(for today)|hours)$/i, (res) ->
+  robot.respond /(?:.*?)(for )?(today|hours)[\?\!\.]?/i, (res) ->
     if not Organization.ready()
       Logger.log "Don\'t output diagnostics, Organization isn\'t ready yet"
       Logger.logToChannel "The #{Organization.name} isn't ready for
@@ -340,23 +357,6 @@
                            at #{Organization.name}.",
                           res.message.user.name
       return
-
-    toTimeStr = (duration) ->
-      hours = Math.floor duration
-      if hours is 0
-        hoursStr = ''
-      else if hours is 1
-        hoursStr = "#{hours} hour"
-      else
-        hoursStr = "#{hours} hours"
-      minutes = Math.round((duration - hours) * 60)
-      if minutes is 0
-        minutesStr = ''
-      else if minutes is 1
-        minutesStr = "#{minutes} minute"
-      else
-        minutesStr = "#{minutes} minutes"
-      return "#{hoursStr}#{if minutes > 0 then ', ' else ''}#{minutesStr}"

     earlyToday = moment({hour: 0, minute: 0, second: 0})
     now = moment({hour: 0, minute: 0, second: 0}).add(1, 'days')


managed: Added meta.managedProvider to managed provider update method

--- workers/social/lib/social/models/computeproviders/managed.coffee
+++ workers/social/lib/social/models/computeproviders/managed.coffee
@@ -107,7 +107,7 @@

   @update = (client, options, callback)->

-    { machineId, queryString, ipAddress, storage } = options
+    { machineId, queryString, ipAddress, storage, managedProvider } = options
     { r: { group, user, account } } = client

     unless machineId? or (queryString? or ipAddress? or storage?)
@@ -123,7 +123,9 @@
       fieldsToUpdate = {domain, ipAddress}

     fieldsToUpdate.queryString = getKiteIdOnly queryString  if queryString?
-    fieldsToUpdate['meta.storage'] = storage  if storage?
+
+    fieldsToUpdate['meta.storage']         = storage          if storage?
+    fieldsToUpdate['meta.managedProvider'] = managedProvider  if managedProvider?

     JMachine = require './machine'
     selector = JMachine.getSelectorFor client, { machineId, owner: yes }


un-show logs on successful run.

un-show logs on successful run.

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -66,6 +66,7 @@
 				$scope.pdf.tooRecentlyCompiled = true
 			else if response.status == "success"
 				$scope.pdf.view = 'pdf'
+				$scope.shouldShowLogs = false
 				# define the base url
 				$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
 				# add a query string parameter for the compile group


Remove tag from projects when deleted

Remove tag from projects when deleted

--- services/web/public/coffee/main/project-list/tag-controllers.coffee
+++ services/web/public/coffee/main/project-list/tag-controllers.coffee
@@ -24,6 +24,13 @@
 					tag: () -> tag
 			)
 			modalInstance.result.then () ->
+				# Remove tag from projects
+				for project in $scope.projects
+					project.tags ||= []
+					index = project.tags.indexOf tag
+					if index > -1
+						project.tags.splice(index, 1)
+				# Remove tag
 				$scope.tags = $scope.tags.filter (t) -> t != tag

 		$scope.renameTag = (tag) ->


fixing broken exports test

fixing broken exports test

--- services/web/test/unit/coffee/Exports/ExportsControllerTests.coffee
+++ services/web/test/unit/coffee/Exports/ExportsControllerTests.coffee
@@ -90,6 +90,6 @@
 		@req.params = {project_id: project_id, export_id: 897}
 		@controller.exportStatus @req, send:(body) =>
 			expect(body).to.deep.equal {export_json: {
-				status_summary: 'completed', status_detail: undefined
+				status_summary: 'completed', status_detail: undefined, partner_submission_id: undefined, token: undefined
 			}}
 			done()


Make intervalFromFeature return an object

--- src/leaflet.timeline.coffee
+++ src/leaflet.timeline.coffee
@@ -79,21 +79,19 @@
     @process timedGeoJSON if timedGeoJSON?

   intervalFromFeature: (feature) ->
-    start = ( new Date feature.properties.start ).getTime()
-    end = ( new Date feature.properties.end ).getTime()
-    return [start, end]
+    start: ( new Date feature.properties.start ).getTime()
+    end: ( new Date feature.properties.end ).getTime()

   process: (data) ->
     earliestStart = Infinity
     latestEnd = -Infinity
     data.features.forEach (feature) =>
       interval = @intervalFromFeature(feature)
-      @ranges.insert interval[0], interval[1], feature
-      @times.push interval[0]
-      @times.push interval[1]
-      if interval[0] < earliestStart then earliestStart = interval[0]
-      if interval[1] > latestEnd then latestEnd = interval[1]
-    @times = @times.sort()
+      @ranges.insert interval.start, interval.end, feature
+      @times.push interval.start
+      @times.push interval.end
+      if interval.start < earliestStart then earliestStart = interval.start
+      if interval.end > latestEnd then latestEnd = interval.end
     if not @options.start then @options.start = earliestStart
     if not @options.end then @options.end = latestEnd


Bigger berries.

Bigger berries.

--- src/draw.coffee
+++ src/draw.coffee
@@ -59,7 +59,7 @@
             c.globalCompositeOperation = old_composite
             c.closePath()
         defaults:
-            radius: 800
+            radius: 1000
             stroke: 125

     class Plant extends CanvasDrawer


add comment to createDic #14

add comment to createDic #14

--- src/lib/base-factory.coffee
+++ src/lib/base-factory.coffee
@@ -316,6 +316,15 @@
         return model

+    ###*
+    create model dic
+
+    @method createDic
+    @public
+    @param {String} dicModelName model name of dic
+    @param {any} obj
+    @return {BaseDic} dic
+    ###
     createDic: (dicModelName, obj) ->

         DicFactory = @getFacade().constructor.DicFactory


afterCreateModel() should be run once in BaseFactory#createObjectModel()

--- src/lib/base-factory.coffee
+++ src/lib/base-factory.coffee
@@ -96,7 +96,8 @@
             return null

-        model = @createEmptyModel()
+        ModelClass = @getModelClass()
+        model = new ModelClass()

         for own prop, value of obj


:cosntruction: More logs for Travis

:cosntruction: More logs for Travis

--- lib/pigments-provider.coffee
+++ lib/pigments-provider.coffee
@@ -26,7 +26,7 @@
       variables = @project.getColorVariables()

     suggestions = @findSuggestionsForPrefix(variables, prefix)
-    console.log suggestions
+    console.log variables, suggestions
     suggestions

   findSuggestionsForPrefix: (variables, prefix) ->


new tag functions are now only in the builder

--- src/xml.coffee
+++ src/xml.coffee
@@ -2,7 +2,7 @@
 { deep_merge, indent, new_attrs, safe } = require './util'
 EVENTS = ['add', 'attr', 'attr:remove', 'text', 'remove', 'close']

-new_tag = (name, attrs, children, opts) ->
+new_tag = (builder, name, attrs, children, opts) ->
     unless typeof attrs is 'object'
         [opts, children, attrs] = [children, attrs, {}]
     else
@@ -10,11 +10,15 @@
         attrs ?= {}
     opts ?= {}
     pipe = {}
-    opts.level = @level+1
+    opts.level ?= @level+1
+
+    circular = 'default':@opts.builder, 'input':opts.builder
+    opts.builder = @opts.builder = null
     opts = deep_merge @opts, opts # possibility to overwrite existing opts, like pretty
-
-
-    @pending.push tag = new @Tag name, attrs, _children, opts
+    @opts.builder = circular['default']
+    opts.builder = circular['input'] or @opts.builder
+
+    @pending.push tag = new (builder.Tag) name, attrs, null, opts

     tag.up = (opts = {}) => # set parent
         opts.end ?= true
@@ -74,18 +78,6 @@
     return tag

-sync_tag = (name, attrs, children, opts) ->
-    unless typeof attrs is 'object'
-        [opts, children, attrs] = [children, attrs, {}]
-    else
-        attrs ?= {}
-    opts ?= {}
-    opts.direct ?= yes
-    self_ending_children_scope = ->
-        @children children, direct:yes
-        @end()
-    @tag.call this, name, attrs, self_ending_children_scope, opts
-

 class Tag extends EventEmitter
     constructor: (@name, @attrs, children, @opts) ->
@@ -96,7 +88,7 @@
             @attrs ?= {}
             @opts ?= {}
         @level = @opts.level
-        @Tag = @opts.Tag or Tag # inherit (maybe) extended tag class
+        @builder = @opts.builder #or new Builder # inheritence
         @buffer = [] # after this tag all children emitted data
         @pending = [] # no open child tag
         @closed = false
@@ -105,15 +97,11 @@
         @headers = "<#{@name}#{new_attrs @attrs}"
         @children children, @opts

-    $tag: =>
-        # sync tag, - same as normal tag, but closes it automaticly
-        sync_tag.apply this, arguments
-
-    tag: =>
-        if @headers
-            @emit 'data', "#{indent this}#{@headers}>"
-            delete @headers
-        new_tag.apply this, arguments
+    tag: (args...) =>
+        @builder._new_tag.apply this, [this].concat args
+
+    $tag: (args...) =>
+        @builder._new_sync_tag.apply this, [this].concat args

     attr: (key, value) =>
         if typeof key is 'string'
@@ -158,11 +146,11 @@
         if @headers
             @emit 'data', "#{indent this}#{@headers}>"
             delete @headers
-        @emit 'data', "#{indent this}#{content}" if content
+        @emit 'data', "#{indent this}#{content}" if content # FIXME is this really nessary?
         @content += content
         true

-    up: () -> null # this node has no parent
+    up: () -> @builder # this node has no parent

     end: () =>
         if not @closed or @closed is 'pending'
@@ -197,25 +185,47 @@
         this

+
 class Builder extends EventEmitter
     constructor: (@opts = {}) ->
+        @builder = this
         @buffer = [] # for child output
         @closed = no
         @pending = [] # no open child tag
-        @opts.Tag ?= Tag
+        @opts.builder ?= this
         @opts.pretty ?= off
         @level = @opts.level ? 0
         @Tag = @opts.Tag or Tag

-    tag: =>
+    _new_tag: (parent, args...) =>
+        if parent.headers
+            parent.emit 'data', "#{indent parent}#{parent.headers}>"
+            delete parent.headers
+        new_tag.apply parent, [this].concat args
+
+    _new_sync_tag: (parent, name, attrs, children, opts) =>
+        # sync tag, - same as normal tag, but closes it automaticly
+        unless typeof attrs is 'object'
+            [opts, children, attrs] = [children, attrs, {}]
+        else
+            attrs ?= {}
+        opts ?= {}
+        self_ending_children_scope = ->
+            @children children
+            @end()
+        @_new_tag parent, name, attrs, self_ending_children_scope, opts
+
+    tag: (args...) =>
         @level--
-        tag = new_tag.apply this, arguments
+        tag = @_new_tag.apply this, [this].concat args
         @level++
         tag

     $tag: (args...) =>
-        # sync tag, - same as normal tag, but closes it automaticly
-        sync_tag.apply this, arguments
+        @level--
+        tag = @_new_sync_tag.apply this, [this].concat args
+        @level++
+        tag

     end: (data) =>
         if @pending.length


api to query if the tag is really ready

api to query if the tag is really ready

--- src/xml.coffee
+++ src/xml.coffee
@@ -108,9 +108,10 @@
         @buffer = [] # after this tag all children emitted data
         @pending = [] # no open child tag
         @parent = @builder
-        @closed = false
+        @closed = no
         @writable = true
         @hidden = no
+        @isready = no
         @isempty = yes
         @content = ""
         @children children, opts
@@ -212,6 +213,13 @@
                     @emit 'close', this
                     @emit 'end'
                     @writable = false
+                    set_ready = =>
+                        @isready = yes
+                        @emit 'ready'
+                    if @builder?
+                        @builder.approve('ready', this, set_ready)
+                    else
+                        set_ready()
                 if @builder?
                     @builder.approve('end', this, close_tag)
                 else
@@ -252,6 +260,9 @@
         @emit 'remove', this
         this

+    ready: (callback) =>
+        return callback?.call(this) if @isready
+        @once 'ready', callback

 class Builder extends EventEmitter
@@ -283,6 +294,10 @@
             @closed = yes
         this

+    ready: (callback) =>
+        return callback?() if @closed is yes
+        @once 'end', callback
+
     # intern getter to let intern tag structure stay in sync with eg dom
     query: (type, tag, key) ->
         if type is 'attr'
@@ -293,26 +308,26 @@
             key # this should be a tag

     register: (type, checker) ->
-        unless type is 'new' or type is 'end'
-            throw new Error "only type 'new' or 'end' allowed."
+        unless type is 'new' or type is 'end' or type is 'ready'
+            throw new Error "only type 'ready', 'new' or 'end' allowed."
         @checkers[type] ?= []
         @checkers[type].push checker

     approve: (type, parent, tag, callback) ->
         checkers = @checkers[type]?.slice?() ? []
-
-        if type is 'new'
-            next = (tag) ->
-                checker = checkers.shift() ? callback
-                checker(parent, tag, next)
-
-        else if type is 'end'
-            [callback, tag] = [tag, parent] # shift arguments
-            next = (tag) ->
-                checker = checkers.shift() ? callback
-                checker(tag, next)
-        else
-            throw new Error "type '#{type}' not supported."
+        switch type
+            when 'new'
+                next = (tag) ->
+                    checker = checkers.shift() ? callback
+                    checker(parent, tag, next)
+
+            when 'ready', 'end'
+                [callback, tag] = [tag, parent] # shift arguments
+                next = (tag) ->
+                    checker = checkers.shift() ? callback
+                    checker(tag, next)
+            else
+                throw new Error "type '#{type}' not supported."
         # start
         next(tag)


Builder::add

Builder::add

--- src/xml.coffee
+++ src/xml.coffee
@@ -207,9 +207,6 @@
     add: (rawtag, callback) =>
         tag = @builder?.query 'tag', this, rawtag
         tag = rawtag unless tag? or @builder?
-        unless tag?
-            callback?.call(this)
-            return this
         # TODO query result validation
         add_tag.call(this, tag, callback)
         this
@@ -248,6 +245,13 @@
         @tag = new_tag
         @$tag = sync_tag

+    add: (rawtag, callback) =>
+        tag = @query 'tag', this, rawtag
+        tag = rawtag unless tag?
+        # TODO query result validation
+        add_tag.call(this, tag, callback)
+        this
+
     end: () =>
         @closed = yes
         @emit 'close', this # tag api


don't render in workspace when not visible

--- lib/runtime/workspace.coffee
+++ lib/runtime/workspace.coffee
@@ -16,7 +16,7 @@
   lazyTrees: []

   update: ->
-    return @ws.setItems [] unless client.isActive()
+    return @ws.setItems [] unless client.isActive() and @ws.currentPane()
     clearLazy @lazyTrees
     registerLazy = (id) => @lazyTrees.push id
     p = workspace('Main').then (ws) =>


Bugfixes.

Bugfixes.

--- packages/api/device/publish.coffee
+++ packages/api/device/publish.coffee
@@ -9,7 +9,7 @@
       _id: 1
   throw new Meteor.Error 'unauthorized', "Unauthorized." unless device

-  Device.documents.update device._id
+  Device.documents.update device._id,
     $set:
       onlineSince: new Date()

@@ -24,9 +24,7 @@
       createdAt: 1

   handle = Message.documents.find(query, options).observeChanges
-    added: (id, fields) ->
-      fields.createdAt = fields.createdAt.valueOf()
-
+    added: (id, fields) =>
       @added 'CommonGarden.messages', id, fields
       @removed 'CommonGarden.messages', id


Switch to non-fuzzy suggestions.

Switch to non-fuzzy suggestions.

Suggests only snippets whose trigger starts with the current prefix.

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -77,16 +77,13 @@
   findSuggestionsForWord: (prefix) ->
     return [] unless @snippets?

-    snippetsByPrefixes = {}
-    prefixes = _.values(@snippets).map (snippet) ->
-      snippetsByPrefixes[snippet.prefix] = snippet
-      return snippet.prefix
+    # Only accept snippets that start with prefix
+    matchesPrefix: (snippet) ->
+      snippet.prefix.lastIndexOf(prefix, 0) isnt -1

-    # Merge the scope specific words into the default word list
-    words = fuzzaldrin.filter prefixes, prefix
-
-    results = for word in words
-      snippet = snippetsByPrefixes[word]
-      new Suggestion this, word: word, prefix: prefix, label: snippet.label
+    results = for snippet in @snippets when matchesPrefix(snippet)
+      word = snippet.prefix
+      label = snippet.label
+      new Suggestion this, word: word, prefix: prefix, label: label

     return results


Closes #1068. Don't know why CALL_START was in that file.

--- lexer.coffee
+++ lexer.coffee
@@ -369,7 +369,7 @@
       switch tok[0]
         when ')'
           stack.push tok
-        when '(', 'CALL_START'
+        when '('
           if stack.length then stack.pop()
           else
             tok[0] = 'PARAM_START'


Fix wrong location issue in heregex interpolation

--- lexer.coffee
+++ lexer.coffee
@@ -263,7 +263,7 @@
     @token 'IDENTIFIER', 'RegExp', 0, 0
     @token 'CALL_START', '(', 0, 0
     tokens = []
-    for token in @interpolateString(body, regex: yes)
+    for token in @interpolateString(body, regex: yes, strOffset: 3)
       [tag, value] = token
       if tag is 'TOKENS'
         tokens.push value...


Fix forgotten templating function.

Fix forgotten templating function.

--- services/web/app/coffee/Features/Email/EmailBuilder.coffee
+++ services/web/app/coffee/Features/Email/EmailBuilder.coffee
@@ -99,7 +99,7 @@
 	subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
 	layout: BaseWithHeaderEmailLayout
 	type:"notification"
-	plainTextTemplate: """
+	plainTextTemplate: _.template """
 Hi, <%= owner.email %> wants to share '<%= project.name %>' with you.

 Follow this link to view the project: <%= inviteUrl %>


redo assert

redo assert

--- src/game.coffee
+++ src/game.coffee
@@ -45,11 +45,12 @@

 exports.game = (rx, query) ->
   game_dict = find_game query.name
-  my.assert game_dict.levels and _.isArray(game_dict.levels)
   world = god(rx, game_dict)
+  game_levels = world.get('levels')
+  my.assert game_levels and world.is_array(game_levels)

   level = query.level or 0
-  level_dict = create_level(world.get('levels').all(), level)
+  level_dict = create_level(game_levels.all(), level)
   level_dict.game = game_dict.name
   level_world = world.add_child level_dict
   for child in layout


fix another infinite fetch issue

fix another infinite fetch issue

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -10,6 +10,8 @@
             for k,v of i
                 post[k] = v
             if (!this.localSettings.showOnlyUnread || (this.localSettings.showOnlyUnread && post.unread))
+                if post.unread
+                    this._seenUnread = this._seenUnread + 1
                 this.articleList.add post

     _apiError: (type, desc, data) =>
@@ -58,6 +60,7 @@
             this._uid = uid
             this._fid = fid
             this._postPage = 0
+            this._seenUnread = 0
             this._enableFetch = true
             this.currentArticle = -1

@@ -81,6 +84,7 @@
         this._uid = uid
         this._fid = 0
         this._postPage = 0
+        this._seenUnread = 0
         this._enableFetch = true
         this.currentArticle = -1

@@ -265,6 +269,7 @@
             this.articleList.reset()
             this.updateFeeds()
             this._postPage = 0
+            this._seenUnread = 0
             this.fetchMorePosts()

         if this._fid > 0
@@ -311,8 +316,7 @@
                 oldPostCount = this.articleList.length
                 this._processFeedPosts data
                 if (this.articleList.length - oldPostCount) < 10 && this._postPage > 1 && this.localSettings.showOnlyUnread
-                    unreadCount = this.articleList.count((i) -> i.unread)
-                    if unreadCount < this.newsFeedView.getSelectedUnread()
+                    if this._seenUnread < this.newsFeedView.getSelectedUnread()
                         this.fetchMorePosts()

         if this._postPage > 0
@@ -395,6 +399,7 @@
         this.articleList.reset()
         if this._fid?
             this._postPage = 0
+            this._seenUnread = 0
             this.fetchMorePosts()

     toggleOptOut: =>


Added missing widget registration.

Added missing widget registration.

--- app/assets/javascripts/app/controllers/maintenance_widget.js.coffee
+++ app/assets/javascripts/app/controllers/maintenance_widget.js.coffee
@@ -1,7 +1,7 @@
 class App.MaintananceWidget extends App.Controller
   constructor: ->
     super
-
+
     # bind on event to show message
     App.Event.bind 'session:maintanance', (data) =>
       console.log('hannes was here')
@@ -23,3 +23,5 @@
       backdrop: false,
       keyboard: false,
     )
+
+App.Config.set( 'maintanance', App.MaintananceWidget, 'Widgets' )


#162852547 better max photo checking

#162852547 better max photo checking

--- app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
@@ -224,6 +224,14 @@

 checkMaxPhotos = (self, maxPhotos, addedPhotos) ->
+  if addedPhotos > maxPhotos
+    $(self).find('.photo-preview').each( (i) ->
+      if i <= maxPhotos
+        continue
+      else
+        $(this).remove()
+    )
+    alert("You can only upload " + maxPhotos + " photos or less on this question.")
   if addedPhotos >= maxPhotos
     $(self).find('.photo-upload').attr('style','display:none;')
   else
@@ -251,7 +259,11 @@
     if maxPhotos
       e.preventDefault()
       self = $(this).parents('.file-upload')
-      $(self).find('.photo-upload').attr('style','display:block;')
+      setTimeout ->
+        addedPhotos = $(self).find('.photo-preview').find("img").length
+        checkMaxPhotos(self, maxPhotos, addedPhotos)
+      , 100
+

   # when a user removes an upload in edit, we are resetting the sortorder


do not generate empty tag spans

do not generate empty tag spans

--- app/assets/javascripts/species/helpers/handlebars_helpers.js.coffee
+++ app/assets/javascripts/species/helpers/handlebars_helpers.js.coffee
@@ -13,9 +13,13 @@

 Ember.Handlebars.helper "formattedTags", (value, options) ->
   escaped = Handlebars.Utils.escapeExpression(value)
-  formatted = escaped.split(',').map((item) ->
-  	"" + item + ""
-  ).reverse().join ""
+  split = escaped.split(',')
+  formatted = if split.length == 1 && split[0] == ''
+    ''
+  else
+    split.map((item) ->
+      "" + item + ""
+    ).join ""
   new Handlebars.SafeString(formatted)

 Ember.Handlebars.registerHelper('highlight', (suggestion, options) ->


Remove command listeners on deactivate

Remove command listeners on deactivate

--- lib/find.coffee
+++ lib/find.coffee
@@ -89,6 +89,13 @@
     ResultsPaneView.model = null
     @resultsModel = null

+    atom.workspaceView.off 'project-find:show'
+    atom.workspaceView.off 'core:cancel core:close'
+    atom.workspaceView.off 'find-and-replace:show-replace'
+    atom.workspaceView.off 'find-and-replace:show'
+    atom.workspaceView.off 'find-and-replace:use-selection-as-find-pattern'
+    atom.workspaceView.off 'project-find:show-in-current-directory'
+
   serialize: ->
     viewState: @findView?.serialize() ? @viewState
     projectViewState: @projectFindView?.serialize() ? @projectViewState


Refs #68; Heatmap now correctly behaves when the 'buckets' option is changed.

--- src/time/heatmap.coffee
+++ src/time/heatmap.coffee
@@ -16,6 +16,13 @@
     cubic: (value, max) -> Math.pow(value/max, 3)
     quartic: (value, max) -> Math.pow(value/max, 4)
     quintic: (value, max) -> Math.pow(value/max, 5)
+
+  optionListeners =
+    'option:buckets': 'bucketsChanged'
+    'option:bucketRange': 'bucketRangeChanged'
+    'option:opacity': 'opacityChanged'
+    'option:bucketPadding': 'bucketPaddingChanged'
+    'option:paintZeroValues': 'paintZeroValuesChanged'

   # Creates a new heatmap.
   # @param [Object] options Options for the heatmap.
@@ -40,6 +47,9 @@
     # Create the paint canvas
     @_setupPaintCanvas()

+    # Add option change listeners
+    @onAll optionListeners
+
   # Prepares initially set data for rendering.
   # @param [Array] data Layered histogram data for the visualization.
   setData: (data) ->
@@ -50,7 +60,9 @@
   # Distributes the full histogram in the entry into the defined buckets
   # for the visualization.
   # @param [Object] entry Entry to prepare for visualization.
-  _prepareEntry: (entry) ->
+  #_prepareEntry: (entry) ->
+
+  _getBuckets: (entry) ->
     prepared =
       time: entry.time
       max: 0
@@ -121,13 +133,19 @@
     # At the end of a transition we must reset the paint canvas by shifting the viewable
     # buckets to the left (this allows for a fixed cut point and single renders below in @draw)
     @on 'transition:end', '_shiftPaintCanvas'
+    @on 'transition:end', => @draw(@animation.frame * @animation.delta())

   # Redraws the entire heatmap for the current data.
   redraw: ->
     entryIndex = @data[0].values.length
     drawColumn = @options.windowSize
+
+    # This addresses a strange off-by-one issue when the chart is transitioning
+    drawColumn++ if @inTransition()
+
     while (--entryIndex >= 0) and (--drawColumn >= 0)
       @_paintEntry(entryIndex, drawColumn)
+    @draw(@animation.frame * @animation.delta())

   # Computes the correct color for a given bucket.
   # @param [Integer] value Normalized value at the bucket.
@@ -150,7 +168,7 @@
     maxTotal = 0

     for layer in @data
-      entry = layer.values[entryIndex]
+      entry = @_getBuckets( layer.values[entryIndex] )
       for bucket, count of entry.buckets
         bucketTotals[bucket] += count
       maxTotal += entry.max
@@ -210,4 +228,7 @@
     @ctx.drawImage @paint, delta, 0
     super()

+  # Changes the number of buckets in response to an option:buckets event.
+  bucketsChanged: ->@redraw()
+
 # "Audio... Audio... Audio... Video Disco..." - Justice


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -30,5 +30,5 @@
   @param {Function} fn Code to execute after n frames has passed
   ###
   every: (n, fn) ->
-    while I.everyEventLastFired
+
       fn()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

 ###*
-Overlays the title text in the middle of the screen. Us
+Overlays the title text in the middle of the screen. Uses

 @name update
 @methodOf TitleScreen#


Steward: relative url support

Steward: relative url support

--- lib/web/views/Steward.coffee
+++ lib/web/views/Steward.coffee
@@ -19,8 +19,7 @@
 "use strict"

 # Missing features:
-# - relative url support
-# - implement support for older browsers: setInterval and check urls
+# - support for browsers without history & onhashchange -> disable
 # - querystring parameters as part of params
 # - copy build configs from bacon.js
 # - split to own repository
@@ -85,10 +84,15 @@
     @addListener document, "click", (event) =>
       target = event.target
       if( target && target.tagName == "A")
+        target.href = target.href # fix for IE
         if !@metakeyPressed(event) && @targetAttributeIsCurrentWindow(target) && @targetHostSame(target)
           href = target.attributes.href.nodeValue
           @log("Processing click", href)
-          if @exec(href)
+          ret = @exec(href)
+          if !ret
+            href = target.pathname # try with full path name
+            ret = @exec(href)
+          if ret
             @log("New url", href)
             event.preventDefault();
             @previousView = href


remove some commented code. refactor _prettify method

--- lib/git.coffee
+++ lib/git.coffee
@@ -143,12 +143,9 @@
   return p

 _prettify = (data) ->
-  # return [] if data is ''
-  # data = [data[0], data.substring(1)]
-  # data = data.split('\0')[...-1]
   return [] if data is ''
   data = data.split(/\n/)
-  [] = for file in data
+  data.map (file) ->
     {mode: file[0], path: file.substring(1).trim()}

 _prettifyUntracked = (data) ->
@@ -175,15 +172,6 @@
     else
       reject "no current file"

-# module.exports.cmd = gitCmd
-# module.exports.stagedFiles = gitStagedFiles
-# module.exports.unstagedFiles = gitUnstagedFiles
 # module.exports.diff = gitDiff
 # module.exports.refresh = gitRefresh
 # module.exports.status = gitStatus
-# module.exports.reset = gitResetHead
-# module.exports.add = gitAdd
-# module.exports.dir = dir
-# module.exports.relativize = relativize
-# module.exports.getSubmodule = getSubmodule
-# module.exports.getRepo = getRepo


Remove colors which corresponded to status if its quantity is zero (Take.3)

--- app/assets/javascripts/common/components/episodeRatingStateChart.js.coffee
+++ app/assets/javascripts/common/components/episodeRatingStateChart.js.coffee
@@ -24,16 +24,17 @@

     if containerWidth
       colors = ["#FFAB40", "#bdbdbd", "#69F0AE", "#40C4FF"]
+      removedColors = []

       # Remove colors which corresponded to status if its quantity is zero.
       _.forEach @dataset, (data, i) ->
-        colors.splice(i, 1) if data.quantity == 0
+        removedColors.push(colors[i]) if data.quantity == 0

       donutChart
         .width(containerWidth)
         .height(containerWidth - 35)
         .externalRadius(containerWidth / 2.5)
         .internalRadius(containerWidth / 5)
-        .colorSchema(colors)
+        .colorSchema(_.difference(colors, removedColors))

       container.datum(@dataset).call(donutChart)


Rename handler from 'running' to 'runner'.

--- app/assets/javascripts/modules/timelet/views/composites/timelets.js.coffee
+++ app/assets/javascripts/modules/timelet/views/composites/timelets.js.coffee
@@ -73,13 +73,13 @@

     @ui.clockTimer.removeAttr 'contentEditable'
     @model.set running: true
-    @running = setInterval @tick, 1000
+    @runner = setInterval @tick, 1000

   # Stops the current timer.
   #
   stopTimelet: ->
-    clearInterval @running
-    delete @running
+    clearInterval @runner
+    delete @runner

     @model.set running: false
     @ui.clockTimer.attr 'contentEditable', 'true'


:bug: Catch JSON errors

:bug: Catch JSON errors

--- lib/main.coffee
+++ lib/main.coffee
@@ -19,7 +19,12 @@
         text = textEditor.getText()
         parameters = ['--reporter', reporter, '--extract', 'auto', '--filename', filePath, '-']
         return helpers.exec(exePath, parameters, {stdin: text}).then (output) ->
-          output = JSON.parse(output).result
+          try
+            output = JSON.parse(output).result
+          catch error
+            atom.notifications.addError("Invalid Result recieved from JSHint", {detail: "Check your console for more info", dismissible: true})
+            console.log('JSHint Result:', output)
+            return []
           output = output.filter((entry) -> entry.error.id)
           return output.map (entry) ->
             error = entry.error


Add description for new config

Add description for new config

--- lib/main.coffee
+++ lib/main.coffee
@@ -8,7 +8,8 @@
     squashDirectoryNames:
       type: 'boolean'
       default: false
-      title: 'Collapse directories that contain only a single directory'
+      title: 'Collapse directories'
+      description: 'Collapse directories that only contain a single directory.'
     hideVcsIgnoredFiles:
       type: 'boolean'
       default: false


hide command

hide command

--- lib/markem.iced
+++ lib/markem.iced
@@ -125,7 +125,7 @@
     cb err, stdout

   @_spawn:(exec, args, cb)->
-    console.log "> #{exec} #{args.join ' '}"
+    console.log "> #{exec} #{args.join ' '}" if @options.verbose
     proc = childprocess.spawn exec, args,
       cwd: process.cwd()
     proc.stdout.setEncoding 'utf8'


Dirty hack to fix first request error temporarily

--- client/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -698,7 +698,8 @@
   HELPERS
   ###

-  notification = null
+  notification  = null
+  autoTriedOnce = no

   hideNotification: ->
     notification.destroy() if notification
@@ -714,6 +715,13 @@

     style or= 'error' if details
     duration = if reconnect then 0 else if details then 5000 else 2500
+
+    if not autoTriedOnce and reconnect
+      KD.utils.wait 200, =>
+        @emit 'fs.retry.scheduled'
+        @getSingleton('kiteController')?.channels?.sharedHosting?.cycleChannel?()
+      autoTriedOnce = yes
+      return

     notification = new KDNotificationView
       title     : msg or "Something went wrong"


filetree: set default error css class for permission denied notification

--- client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
@@ -172,7 +172,7 @@
         else
           message = "Couldn't fetch files! Click to retry"
           logToExternalWithTime "filetree: Couldn't fetch files"
-        @notify message, 'clickable', \
+        @notify message, null, \
                 """Sorry, a problem occurred while communicating with servers,
                    please try again later.""", yes
         @once 'fs.retry.scheduled', => @expandFolder nodeView, callback


status update widget now uses paste even and resets correctly

--- client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -16,7 +16,6 @@
         rules       :
           maxLength : 2000

-    @previousWhich = 0
     @previousURL = ""

     @largeInput = new KDInputView
@@ -32,13 +31,13 @@
           maxLength : 3000
         messages    :
           required  : "Please type a message..."
+      paste:=>
+        @requestEmbed()
       blur:=>
         @requestEmbed()
-      keydown:=>
-        if ($(event.which)[0] is 32) or ($(event.which)[0] is 86 and @previousWhich is 91)
+      keyup:=>
+        if ($(event.which)[0] is 32) # when space key is hit, URL is usually complete
           @requestEmbed()
-        @previousWhich = $(event.which)[0]
-

     @cancelBtn = new KDButtonView
@@ -143,8 +142,7 @@
     @tagController.reset()
     @submitBtn.setTitle "Submit"
     @removeCustomData "activity"
-    @embedBox.clearEmbedAndHide()
-    @previousWhich = 0
+    @embedBox.resetEmbedAndHide()
     @previousURL = ""

     super


using callback queue instead of md5

using callback queue instead of md5

--- lib/puml.coffee
+++ lib/puml.coffee
@@ -1,77 +1,56 @@
 path = require 'path'
 {spawn} = require 'child_process'
-md5 = require('md5');

 plantumlJarPath = path.resolve(__dirname, '../dependencies/plantuml/plantuml.jar')

+CALLBACKS = []
+CHUNKS = []
 # Async call
 generateSVG = (content, fileDirectoryPath='', callback)->
+  content = content.trim()
+  # ' @mpe_file_directory_path:/fileDirectoryPath
+  # fileDirectoryPath

-  content = content.trim()
   fileDirectoryPath = content.match(/^'\s@mpe_file_directory_path:(.+)$/m)?[1] or fileDirectoryPath

-  #the string have to put this way for make md5 hash right
-  if !content.startsWith('@start')
-    content = """@startuml
+  if !(content.match(/^\@start/m))
+    content = """
+@startuml
 #{content}
-@enduml"""
+@enduml
+    """

-  content = content.replace(/\r/g, "")
+  if !@task # init `plantuml.jar` task
+    @task = spawn 'java', [  '-Djava.awt.headless=true',
+                              '-Dplantuml.include.path='+fileDirectoryPath
+                              '-jar', plantumlJarPath,
+                              # '-graphvizdot', 'exe'
+                              '-pipe',
+                              '-tsvg',
+                              '-charset', 'UTF-8']

-  hash = md5(content)
+    # only `on 'data'` once
+    @task.stdout.on 'data', (chunk)->
+      CHUNKS.push(chunk)
+      data = Buffer.concat(CHUNKS).toString()
+      if data.endsWith('--></g></svg>')
+        CHUNKS = [] # clear CHUNKS
+        CALLBACKS.shift()?(data)

-  # the cache not work , still dont know why
-  # if plantumlAPI.callbacks[hash]?
-  #   console.log("content cached!!")
-  #   callback?(plantumlAPI.callbacks[hash].result)
-  #   return
+  ###
+  @task.stdout.on 'end', ()->
+    data = Buffer.concat(chunks).toString()
+    callback?(data)
+  ###

-  plantumlAPI.callbacks[hash] = {cb:callback, content: content, result: null}
+  CALLBACKS.push(callback) # save callback to CALLBACKS queue
+  @task.stdin.write(content + "\n")
+  # @task.stdin.end()

-  #keep call size tiny
-  if Object.keys(plantumlAPI.callbacks).length > 20
-    # console.log("callbacks too large, shifting:" + plantumlAPI.callbacks.length + " hash:" + Object.keys(plantumlAPI.callbacks)[0]);
-    delete plantumlAPI.callbacks[Object.keys(plantumlAPI.callbacks)[0]]
-
-  if @task == null
-    @task = spawn 'java', [    '-Djava.awt.headless=true',
-                            '-Dplantuml.include.path='+fileDirectoryPath
-                            '-jar', plantumlJarPath,
-                            # '-graphvizdot', 'exe'
-                            '-pipe',
-                            '-tsvg',
-                            '-charset', 'UTF-8']
-
-    chunks = []
-    @task.stdout.on 'data', (chunk)->
-      chunks.push(chunk);
-      data = Buffer.concat(chunks).toString();
-      if data.endsWith("--></g></svg>")
-        #data may contains many diagrams, lets split it
-        diagrams = data.split("--></g></svg>")
-        for key, diag of diagrams
-          if diag.trim() == ""
-            continue
-
-          diag = diag + "--></g></svg>"
-          #same diagram svg data will be trigger times while the diagram initialize
-          #use hash to prevent callbacks error, but it still not work very well
-          data = ""
-          uml = diag.match(/^@startuml$(.|\n|\r)*^@enduml/m);
-          if(uml != null)
-            #walk around of plantuml bug sometimes "-->" will become "- ->"
-            hash = md5(uml[0].replace(/\r/g, "").replace(/- -/g, "--"))
-            plantumlAPI.callbacks[hash]?.result = diag
-            plantumlAPI.callbacks[hash]?.cb(diag)
-
-
-  @task.stdin.write(content)
-  @task.stdin.write("\n")

 # generateSVG('A -> B')
 plantumlAPI = {
-  callbacks : []
-  render: generateSVG
+  render: generateSVG,
   task: null
 }


Added placeholder for auto complete add channel view

--- core/app/backbone/views/channels/auto_complete_channels_view.coffee
+++ core/app/backbone/views/channels/auto_complete_channels_view.coffee
@@ -19,6 +19,7 @@
       results_view: AutoCompleteResultsChannelsView
       search_collection: TopicSearchResults
       search_list_view: AutoCompleteSearchChannelsView
+      placeholder: 'Channel name'

     @_results_view.on "itemview:remove",  (childView, msg) =>
       @trigger 'removeChannel', childView.model


Etutors views

Etutors views

--- common/static/coffee/src/discussion/views/thread_response_show_view.coffee
+++ common/static/coffee/src/discussion/views/thread_response_show_view.coffee
@@ -45,7 +45,7 @@
         @$el.prepend('
' + gettext('staff') + '
')
       else if DiscussionUtil.isTA(@model.get("user_id"))
         @$el.addClass("community-ta")
-        @$el.prepend('
' + gettext('Community TA') + '
')
+        @$el.prepend('
E-tutor
')

     edit: (event) ->
         @trigger "response:edit", event


add navigation directive default

add navigation directive default

--- app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee
+++ app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee
@@ -1,4 +1,6 @@
 angular.module("admin.utils").directive "navigationCheck", (NavigationCheck)->
   link: ($scope) ->
-    # Define navigationCallback on the controller.
+    # Define navigationCallback on a controller in $scope, otherwise this default will be used:
+    $scope.navigationCallback ||= ->
+      "You will lose any unsaved work!"
     NavigationCheck.register($scope.navigationCallback)


MachineStateModal: make sure stack exists for the given machine

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -182,7 +182,8 @@
     computeController.on "stop-#{@machineId}",  @bound 'updateStatus'

     # Stack build events
-    computeController.on "apply-#{@stack._id}", @bound 'updateStatus'
+    if @stack
+      computeController.on "apply-#{@stack._id}", @bound 'updateStatus'

     computeController.on "reinit-#{@machineId}", (event) =>
       @updateStatus event, 'reinit'
@@ -582,7 +583,8 @@

     target     = @machine

-    if @state is NotInitialized and @machine.jMachine.generatedFrom?.templateId?
+    if @stack and @state is NotInitialized and \
+       @machine.jMachine.generatedFrom?.templateId?
       action   = 'buildStack'
       target   = @stack


marketo form coffee

marketo form coffee

--- js/outer/marketo_forms.coffee
+++ js/outer/marketo_forms.coffee
@@ -28,9 +28,7 @@
         event: event
         data:
           FirstName: @FirstName()
-          LastName: @LastName()
           Email: @Email()
-          repo_solution__c: if @repo_solution__c() == 'other' then @other_input() else @repo_solution__c()
           Company: @Company()
           munchkinId: @munchkinId
           formid: @formid


fixing linting issues

fixing linting issues

--- js/src/http/SharingApi.coffee
+++ js/src/http/SharingApi.coffee
@@ -29,9 +29,9 @@
   shareObjectUrl          = -> sharingEndpoint() + OBJECT_PATH + SHARE_PATH
   revokeObjectUrl         = -> sharingEndpoint() + OBJECT_PATH + REVOKE_PATH
   getIncomingSharesUrl    = -> sharingEndpoint() + OBJECT_PATH
-  removeIncomingSharesUrl = (objectId )-> sharingEndpoint() + OBJECT_PATH + '/' + objectId
+  removeIncomingSharesUrl = (id ) -> sharingEndpoint() + OBJECT_PATH + '/' + id
   addObjectIndexPairUrl   = -> sharingEndpoint() + KEYS_PATH
-  getObjectIndexPairUrl   = (objectId) -> sharingEndpoint() + OBJECT_PATH + '/' + objectId + OBJECT_KEYS
+  getObjectIndexPairUrl   = (id) -> sharingEndpoint() + OBJECT_PATH + '/' + id + OBJECT_KEYS

   logger = Logger.get('SharingApi')


fix usage of reduce

fix usage of reduce

--- plugins/time-tracking/time-tracking.coffee
+++ plugins/time-tracking/time-tracking.coffee
@@ -132,7 +132,7 @@
       rowTotalTime = @getRowData id, "rowTotalTime"
       deletedChildrenTotalTimes = _.map deletedChildren, (x) -> x['totalTime']
       totalTimes = _.compact [rowTotalTime].concat(childTotalTimes).concat(deletedChildrenTotalTimes)
-      totalTime = totalTimes.reduce (a,b) -> (a+b)
+      totalTime = totalTimes.reduce ((a,b) -> (a+b)), 0
       @setRowData id, "treeTotalTime", totalTime

     onRowRemoved: (event) ->


applicationtabview: Fixed app session management.

--- client/landing/app/CommonViews/applicationview/applicationtabview.coffee
+++ client/landing/app/CommonViews/applicationview/applicationtabview.coffee
@@ -1,4 +1,5 @@
 class ApplicationTabView extends KDTabView
+
   constructor: (options = {}, data) ->

     options.resizeTabHandles             = yes
@@ -11,8 +12,7 @@

     super options, data

-    # TODO: fatihacet: session logic should refactored for ui and ux
-    @isSessionEnabled = no #options.saveSession and options.sessionName
+    @isSessionEnabled = options.saveSession and options.sessionName

     appView = @getDelegate()

@@ -28,16 +28,12 @@

     @on 'PaneAdded', =>
       @tabHandleContainer.repositionPlusHandle @handles
-      if @isSessionEnabled
-        @initSession @panes.last, => @updateSession()
+      @initSession @panes.last, @bound "updateSession" if @isSessionEnabled

     @on 'SaveSession', (data) =>
       @appStorage.setValue @getOptions().sessionKey, data

-    @on "SessionItemClicked", (items) =>
-      @getDelegate().openFile FSHelper.createFileFromPath file for file in items
-
-    appView.on "AceAppDidQuit", => @removeFromSession no
+    # appView.on "AceAppDidQuit", => @removeFromSession no

   # session related methods

@@ -46,7 +42,7 @@

   initSession: (pane, callback) ->
     options     = @getOptions()
-    @appStorage = new AppStorage options.sessionName, '0.1'
+    @appStorage = new AppStorage options.sessionName, '0.2'

     @fetchStorage (data) =>
       if data then @restoreSession data, pane
@@ -72,25 +68,15 @@
   updateSession: ->
     if @isSessionEnabled
       @fetchStorage (data) =>
-        @getDelegate().emit 'UpdateSessionData', @panes, data
+        @getDelegate().emit "UpdateSessionData", @panes, data

   restoreSession: (data, pane) ->
     return if data.latestSessions.length is 0
-    @getDelegate().emit "SessionListCreated", pane, @createSessionList data
-
-  createSessionList: (data) ->
-    items = @createSessionItems data
-    button = new KDButtonViewWithMenu
-      title    : "Sessions"
-      cssClass : "editor-button ace-session-button"
-      menu     : => items
-
-    return button
+    @getDelegate().emit "SessionListCreated", pane, @createSessionItems data

   createSessionItems: (data) ->
-    items      = {}
-    delegate   = @getDelegate()
-    date       = new Date()
+    items = {}
+    date  = new Date()

     data.latestSessions.forEach (sessionId, i) =>
       isSessionActive = yes for aceApp in appManager.appControllers.Ace when aceApp.getView().id is sessionId
@@ -100,7 +86,7 @@
         itemTxt         = if itemLen is 1 then "file" else "files"
         formattedDate   = dateFormat date.setTime(sessionId.split("_")[1]), "dd mmm yyyy - HH:MM"
         items["#{formattedDate} (#{itemLen} #{itemTxt})"] =
-          callback: => @emit "SessionItemClicked", sessionItems
+          callback: => @getDelegate().emit "SessionItemClicked", sessionItems

     items.separator = type: "separator"

@@ -109,10 +95,10 @@
       isSessionActive = yes for aceApp in appManager.appControllers.Ace when aceApp.getView().id is sessionId
       unless isSessionActive
         sessionItems = data[sessionId]
-        for path, i in sessionItems
+        sessionItems.forEach (path, i) =>
           if fileCount < 10
-            filePath = path.replace("/Users/#{KD.whoami().profile.nickname}", "~")
-            items[filePath] = callback: @emit.bind(@, "SessionItemClicked", [path])
+            filePath = path.replace("/home/#{KD.whoami().profile.nickname}", "~")
+            items[filePath] = callback: => @getDelegate().emit "SessionItemClicked", [path]
             fileCount++

     return items


new google analytics code

new google analytics code

--- app/index.coffee
+++ app/index.coffee
@@ -40,7 +40,7 @@

 new Analytics
-  account: "UA-1224199-42"
+  account: "UA-1224199-43"

 # Configure connection to Api
 api = new Api


set accepted taxon to blank in case there's no resolution available

--- app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
@@ -191,4 +191,8 @@
           Trade.TaxonConcept.find(taxon_concept_id).then( () =>
             @set('currentShipment.taxonConceptId', taxon_concept_id)
           )
+        else
+          # there were no accepted names found, this is likely a data error
+          # so clear the accepted taxon to draw attention to it
+          @set('currentShipment.taxonConceptId', null)
       )


Adds tests for configure-reporter

Adds tests for configure-reporter

--- test/unit/configure-reporters-test.coffee
+++ test/unit/configure-reporters-test.coffee
@@ -0,0 +1,202 @@
+{EventEmitter} = require 'events'
+
+{assert} = require 'chai'
+sinon = require 'sinon'
+proxyquire = require('proxyquire').noCallThru()
+
+loggerStub = require '../../src/logger'
+BaseReporterStub = sinon.spy  require '../../src/reporters/base-reporter'
+XUnitReporterStub = sinon.spy  require '../../src/reporters/x-unit-reporter'
+CliReporterStub = sinon.spy require '../../src/reporters/cli-reporter'
+DotReporterStub = sinon.spy require '../../src/reporters/dot-reporter'
+NyanCatReporterStub = sinon.spy require '../../src/reporters/nyan-reporter'
+HtmlReporterStub = sinon.spy require '../../src/reporters/html-reporter'
+MarkdownReporterStub = sinon.spy require '../../src/reporters/markdown-reporter'
+emitterStub = new EventEmitter()
+
+loggerStub.transports.console.silent = true
+
+configureReporters = proxyquire '../../src/configure-reporters', {
+  './logger' : loggerStub
+  './reporters/base-reporter': BaseReporterStub
+  './reporters/x-unit-reporter': XUnitReporterStub
+  './reporters/cli-reporter': CliReporterStub
+  './reporters/dot-reporter': DotReporterStub
+  './reporters/nyan-reporter': NyanCatReporterStub
+  './reporters/html-reporter': HtmlReporterStub
+  './reporters/markdown-reporter': MarkdownReporterStub
+}
+
+resetStubs = () ->
+  emitterStub.removeAllListeners()
+  BaseReporterStub.reset()
+  CliReporterStub.reset()
+  XUnitReporterStub.reset()
+  DotReporterStub.reset()
+  NyanCatReporterStub.reset()
+  HtmlReporterStub.reset()
+  MarkdownReporterStub.reset()
+
+
+describe 'configureReporters(config, stats, tests, onSaveCallback)', () ->
+
+  configuration =
+    emitter: emitterStub
+    options:
+      reporter: []
+      output: []
+      silent: false
+      'inline-errors': false
+
+  describe 'when there are no reporters', () ->
+
+    beforeEach () ->
+      resetStubs()
+
+    it 'should only add a CliReporter', (done) ->
+      configureReporters(configuration, {}, {}, null)
+      assert.ok CliReporterStub.called
+      done()
+
+    describe 'when silent', ()->
+
+      before () ->
+        configuration.options.silent = true
+
+      after () ->
+        configuration.options.silent = false
+
+      beforeEach () ->
+        resetStubs()
+
+      it 'should not add any reporters', (done) ->
+        configureReporters(configuration, {}, {}, null)
+        assert.notOk CliReporterStub.called
+        done()
+
+  describe 'when there are only cli-based reporters', () ->
+
+    before () ->
+      configuration.options.reporter = ['dot', 'nyan']
+
+    after () ->
+      configuration.options.reporter = []
+
+    beforeEach () ->
+      resetStubs()
+
+    it 'should add a cli-based reporter', (done) ->
+      configureReporters(configuration, {}, {}, null)
+      assert.ok DotReporterStub.called
+      done()
+
+    it 'should not add more than one cli-based reporters', (done) ->
+      configureReporters(configuration, {}, {}, null)
+      assert.notOk CliReporterStub.called
+      done()
+
+
+  describe 'when there are only file-based reporters', () ->
+
+    before () ->
+      configuration.options.reporter = ['junit', 'markdown']
+
+    after () ->
+      configuration.options.reporter = []
+
+    beforeEach () ->
+      resetStubs()
+
+    it 'should add a CliReporter', (done) ->
+      configureReporters(configuration, {}, {}, () -> )
+      assert.ok CliReporterStub.called
+      done()
+
+    describe 'when the number of outputs is greater than or equals the number of reporters', () ->
+
+      before () ->
+        configuration.options.output = ['file1', 'file2', 'file3']
+
+      after () ->
+        configuration.options.output = []
+
+      beforeEach () ->
+        resetStubs()
+
+      it 'should use the output paths in the order provided', (done) ->
+        configureReporters(configuration, {}, {}, () -> )
+        assert.ok XUnitReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, 'file1')
+        assert.ok MarkdownReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, 'file2')
+        done()
+
+    describe 'when the number of outputs is less than the number of reporters', () ->
+
+      before () ->
+        configuration.options.output = ['file1']
+
+      after () ->
+        configuration.options.output = []
+
+      beforeEach () ->
+        resetStubs()
+
+      it 'should use the default output paths', (done) ->
+        configureReporters(configuration, {}, {}, () -> )
+        assert.ok XUnitReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, null)
+        assert.ok MarkdownReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, null)
+        done()
+
+  describe 'when there are both cli-based and file-based reporters', () ->
+
+    before () ->
+      configuration.options.reporter = ['nyan', 'markdown', 'html']
+
+    after () ->
+      configuration.options.reporter = []
+
+    beforeEach () ->
+      resetStubs()
+
+    it 'should add a cli-based reporter', (done) ->
+      configureReporters(configuration, {}, {}, () ->)
+      assert.ok NyanCatReporterStub.called
+      done()
+
+    it 'should not add more than one cli-based reporters', (done) ->
+      configureReporters(configuration, {}, {}, () ->)
+      assert.notOk CliReporterStub.called
+      assert.notOk DotReporterStub.called
+      done()
+
+    describe 'when the number of outputs is greather than or equals the number of file-based reporters', () ->
+
+      before () ->
+        configuration.options.output = ['file1', 'file2']
+
+      after () ->
+        configuration.options.output = []
+
+      beforeEach () ->
+        resetStubs()
+
+      it 'should use the output paths in the order provided', (done) ->
+        configureReporters(configuration, {}, {}, () -> )
+        assert.ok MarkdownReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, 'file1')
+        assert.ok HtmlReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, 'file2')
+        done()
+
+    describe 'when the number of outputs is less than the number of file-based reporters', () ->
+      before () ->
+        configuration.options.output = ['file1']
+
+      after () ->
+        configuration.options.output = []
+
+      beforeEach () ->
+        resetStubs()
+
+      it 'should use the default output paths', (done) ->
+        configureReporters(configuration, {}, {}, () -> )
+        assert.ok MarkdownReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, null)
+        assert.ok HtmlReporterStub.calledWith(emitterStub, {'fileBasedReporters': 2}, {}, null)
+        done()


Add / route so it's easier to see the app is running

--- storage/lib/rest.coffee
+++ storage/lib/rest.coffee
@@ -30,6 +30,10 @@
         res
             .status 403
             .send 'Valid API token required'
+
+    app.get '/', (req, res, next) ->
+        res.status 200
+        res.send 'Okay'

     app.get '/geocaches', async (req, res, next) ->
         res.set 'Content-Type', 'application/json'


fixed timer date

fixed timer date

--- src/middleware/stats.coffee
+++ src/middleware/stats.coffee
@@ -45,6 +45,7 @@
     logger.error error

 exports.koaMiddleware = `function *statsMiddleware(next) {
+    timer = new Date();
     yield next;
     exports.incrementTransactionCount(this)
     exports.measureTransactionDuration(this)


5960: clean up comment

5960: clean up comment

--- app/assets/javascripts/backbone/views/question_form_view.js.coffee
+++ app/assets/javascripts/backbone/views/question_form_view.js.coffee
@@ -7,7 +7,7 @@

   events:
     'change select[id$="_qtype_name"]': 'typeChanged'
-    'click #questioning_read_only': 'readOnlyStatusChanged' #SYNTAX????
+    'click #questioning_read_only': 'readOnlyStatusChanged'

   typeChanged: (e) ->
     @toggleFields()


5960: Fixed variable style

5960: Fixed variable style

--- app/assets/javascripts/backbone/views/question_form_view.js.coffee
+++ app/assets/javascripts/backbone/views/question_form_view.js.coffee
@@ -15,11 +15,11 @@
     @toggleFields()

   prefillPatternChanged: (e) ->
-    prefill_value = e.target.value
-    @toggleReadOnly(prefill_value)
+    prefillValue = e.target.value
+    @toggleReadOnly(prefillValue)

-  toggleReadOnly: (prefill_value) ->
-    @$('.questioning_read_only')[if prefill_value == '' then 'hide' else 'show']()
+  toggleReadOnly: (prefillValue) ->
+    @$('.questioning_read_only')[if prefillValue == '' then 'hide' else 'show']()

   readOnlyStatusChanged: (e) ->
     @toggleRequired(e.target.checked)


cleanup

cleanup

--- app/assets/javascripts/components/profile/profile_widget.js.coffee
+++ app/assets/javascripts/components/profile/profile_widget.js.coffee
@@ -1,16 +0,0 @@
-#= require_tree ./content
-
-{div} = React.DOM
-
-@UserProfileWidget = React.createFactory React.createClass
-  getInitialState: ->
-    edit_mode: false
-    user: @props.user
-
-  render: ->
-    (div className: 'profile-card__content container',
-      (ProfileBreadCrumbs())
-      (div className: 'col-xs-12 col-sm-12 col-md-10',
-        (@ProfileInfo(user: @state.user))
-        (@References(user: @state.user))
-      ))


#172963368 correctly handling data inside and outside of repeaters

--- app/assets/javascripts/hanuman/hanuman-conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-conditional-logic.js.coffee
@@ -444,15 +444,23 @@
   updateCalculation: (rule) ->
     parameters = {}
     $target = $('[data-question-id="' + rule.question_id + '"]').find('.form-control')
+    $targetRepeater = $target.closest(".form-container-repeater")
+
     targetType = $('[data-question-id="' + rule.question_id + '"]').data('element-type')

+
     $.each rule.conditions, (index, condition) ->
-      $question = $('[data-question-id="' + condition.question_id + '"]')
+
+      if $targetRepeater.length > 0 && $('[data-question-id="' + condition.question_id + '"]').closest(".form-container-repeater").length > 0
+        $question = $targetRepeater.find('[data-question-id="' + condition.question_id + '"]')
+      else
+        $question = $('[data-question-id="' + condition.question_id + '"]')
+
       elementType = $question.data('element-type')
       columnName = $question.data('api-column-name')
       $repeater = $question.closest(".form-container-repeater")

-      if $repeater.length > 0
+      if $repeater.length > 0 && $targetRepeater.length == 0
         entries = []
         $.each $question, (index, entry) ->
           value = self.getNativeValue($(entry).find('.form-control'), elementType)


Add '(1)' to duplicate nodes name

Add '(1)' to duplicate nodes name

In order to avoid nodes with the same name

--- app/frontend/javascripts/views/visualization-table-nodes.js.coffee
+++ app/frontend/javascripts/views/visualization-table-nodes.js.coffee
@@ -101,7 +101,7 @@
       # set duplicated values
       if @duplicate
         if @duplicate.attributes.name
-          @table.setDataAtRowProp index, 'name', @duplicate.attributes.name
+          @table.setDataAtRowProp index, 'name', @duplicate.attributes.name+' (1)'
         if @duplicate.attributes.node_type
           @table.setDataAtRowProp index, 'node_type', @duplicate.attributes.node_type
         if @duplicate.attributes.description


US5053 - Self rights will not show up as a possibility if they've already been added to the general context.

--- sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
@@ -148,7 +148,7 @@
       $("#addRightUi select").append($("<option></option>").val(right).text(right))

   for right in selfRights
-    if (curSelfRights.indexOf(right) < 0)
+    if (curSelfRights.indexOf(right) < 0 and curRights.indexOf(right) < 0)
       $("#addSelfRightUi select").append($("<option></option>").val(right).text(right))

 wrapInputWithDeleteButton = (input, type, name) ->


adding changes to future main

adding changes to future main

--- src/main.coffee
+++ src/main.coffee
@@ -11,14 +11,38 @@
       @endpoints = new Endpoint()
       @stub = http.createServer(new Stub(@endpoints).server)
       @admin = http.createServer(new Admin(@endpoints).server)
+      CLI.mute = true

    start: (options, callback) ->
+      options ?= {}
+      options.stub ?= CLI.defaults.stub
+      options.admin ?= CLI.defaults.admin
+      options.location ?= CLI.defaults.location
+      options.data ?= []
+
+      @endpoints.create options.data, ->
+
+      @stub.listen options.stub, options.location
+      @admin.listen options.admin, options.location
+
    stop: ->
+      @stub.close()
+      @admin.close()
+
+   mute: (mute) -> CLI.mute = mute ? true

    create: (data) ->
-   read: (id) ->
+   read: (id, callback) ->
+      fn = => @endpoints.retrieve id, callback, callback
+      setTimeout fn, 1
    readAll: ->
+      fn = => @endpoints.gather callback, callback
+      setTimeout fn, 1
    update: (id, data) ->
-   destroy: (id) ->
+   destroy: (id, callback) ->
+      fn = => @endpoints.delete id, callback, -> callback true
+      setTimeout fn, 1
    destroyAll: ->
+      delete @endpoints.db
+      @endpoints.db = {}


Allow chaining from all NumberFormatter setters.

--- src/number_formatter.coffee
+++ src/number_formatter.coffee
@@ -204,6 +204,7 @@

   setNegativeInfinitySymbol: (negativeInfinitySymbol) ->
     @_negativeInfinitySymbol = negativeInfinitySymbol
+    return this

   negativePrefix: ->
     @_get 'negativePrefix'
@@ -224,12 +225,14 @@

   setNotANumberSymbol: (notANumberSymbol) ->
     @_notANumberSymbol = notANumberSymbol
+    return this

   nullSymbol: ->
     @_get 'nullSymbol'

   setNullSymbol: (nullSymbol) ->
     @_nullSymbol = nullSymbol
+    return this

   numberStyle: ->
     @_numberStyle
@@ -287,12 +290,14 @@

   setUsesGroupingSeparator: (usesGroupingSeparator) ->
     @_usesGroupingSeparator = usesGroupingSeparator
+    return this

   zeroSymbol: ->
     @_get 'zeroSymbol'

   setZeroSymbol: (zeroSymbol) ->
     @_zeroSymbol = zeroSymbol
+    return this

   _get: (attr) ->
     value = this["_#{attr}"]


Fix failing BOM ng-spec

Fix failing BOM ng-spec

--- spec/javascripts/unit/bulk_order_management_spec.js.coffee
+++ spec/javascripts/unit/bulk_order_management_spec.js.coffee
@@ -29,9 +29,9 @@
       scope.initialise "api_key"
       httpBackend.flush()

-      expect(scope.suppliers).toEqual [{ id : '', name : 'All' }, 'list of suppliers']
-      expect(scope.distributors).toEqual [ { id : '', name : 'All' }, 'list of distributors' ]
-      expect(scope.orderCycles).toEqual [ { id : '', name : 'All' }, 'oc1', 'oc2', 'oc3' ]
+      expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers']
+      expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ]
+      expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ]

       expect(scope.initialiseVariables.calls.length).toBe 1
       expect(scope.fetchOrders.calls.length).toBe 1


whoops...

whoops...

--- src/grunt-mailgun.coffee
+++ src/grunt-mailgun.coffee
@@ -12,10 +12,10 @@

     if @files.length > 0

-      @files.forEach (file) ->
+      @filesSrc.forEach (filePath) ->

-        body = grunt.file.read(file.src) # janksauce
+        body = grunt.file.read(filePath)

         raw = "From: #{sender}\nTo: #{recipient}\nContent-Type: text/html; charset=utf-8\nSubject: #{subject}\n\n #{body},"


add tests for pagination-links usage

add tests for pagination-links usage

--- test/flexirails/plugin.test.coffee
+++ test/flexirails/plugin.test.coffee
@@ -65,6 +65,7 @@
   describe "flexirails-navigation", ->
     beforeEach ->
       initFlexirails data, view
+      instance.adapter.perPage 2

     afterEach ->
       destroyFlexirails()
@@ -76,12 +77,23 @@
       expect($(".fr-total-pages").html()).toBe instance.adapter.totalPages().toString()

     it "should display the correct page count after perPage is called", ->
-      instance.adapter.perPage 2
       instance.adapter.paginate 2
       expect($(".fr-current-page").html()).toBe '2'

     it "should display the correct total pages counter after perPage is called", ->
-      instance.adapter.perPage 2
+      instance.adapter.perPage 3
       expect($(".fr-total-pages").html()).toBe '2'

-      +    it "should paginate when next link is clicked", ->
+      $(".fr-next-page").click()
+      expect(instance.adapter.options.currentPage).toBe 2
+
+    it "should paginate to last page when last page is clicked", ->
+      $(".fr-last-page").click()
+      expect(instance.adapter.options.currentPage).toBe instance.adapter.totalPages()
+
+    it "should paginate to currentPage when clicking next-prev", ->
+      page = instance.adapter.options.currentPage
+      $(".fr-next-page").click()
+      $(".fr-prev-page").click()
+      expect(instance.adapter.options.currentPage).toBe page


undebug

undebug

--- test/basic_usage.coffee
+++ test/basic_usage.coffee
@@ -11,7 +11,6 @@
 	before ->
 		matchesBlanket = (path) -> path.match /node_modules\/blanket/
 		runningTestCoverage = Object.keys(require.cache).filter(matchesBlanket).length > 0
-		console.log 'xxxx', runningTestCoverage
 		if runningTestCoverage
 			require('require-dir')("#{__dirname}/../lib", {recurse: true, duplicates: true})


upgrade header case sensitive

upgrade header case sensitive

--- src/server.iced
+++ src/server.iced
@@ -29,7 +29,8 @@
     server = http.createServer(app).listen 4002, () ->
       console.log "HTTP Launched, Listening on 4002"

-    server.on 'upgrade', (request, socket, body) ->
+    server.on 'Upgrade', (request, socket, body) ->
+      console.log "Upgrade"
       if not websocket.isWebSocket request
         return console.error "Bad Websocket Upgrade"
       driver = websocket.http request


Remove @notmac

Remove @notmac

--- test/integration/free_trial.coffee
+++ test/integration/free_trial.coffee
@@ -22,7 +22,7 @@
           url.should.equal "#{base_url}/pricing"
           done()

-    context 'when I click the super duper ScraperWiki digger @notmac', ->
+    context 'when I click the super duper ScraperWiki digger', ->
       before (done) ->
         wd40.click '#logo', done

@@ -31,7 +31,7 @@
           url.should.equal "#{base_url}/datasets"
           done()

-    context 'when I click the $9 upgrade button @notmac', (done) ->
+    context 'when I click the $9 upgrade button', (done) ->
       before (done) ->
         browser.get base_url + '/pricing/expired', ->
           wd40.click '.plan.explorer .upgrade', done
@@ -75,7 +75,7 @@
           url.should.equal "#{base_url}/help"
           done()

-    context 'when I click the super duper ScraperWiki digger @notmac', ->
+    context 'when I click the super duper ScraperWiki digger', ->
       before (done) ->
         wd40.click '#logo', done

@@ -84,7 +84,7 @@
           url.should.equal "#{base_url}/pricing/expired"
           done()

-    context 'when I click the $9 upgrade button @notmac', (done) ->
+    context 'when I click the $9 upgrade button', (done) ->
       before (done) ->
         wd40.click '.plan.explorer .upgrade', done


Add another test case for Combine.XOR

Add another test case for Combine.XOR

...to assert that order of the ciphers matters.

--- test/files/combine.iced
+++ test/files/combine.iced
@@ -1,6 +1,7 @@

 hmac = require '../../lib/hmac'
 {SHA3} = require '../../lib/sha3'
+{SHA512} = require '../../lib/sha512'
 {Concat,XOR} = require '../../lib/combine'
 {data} = require '../fixed-data/combine'
 {WordArray} = require '../../lib/wordarray'
@@ -21,6 +22,10 @@
   m3 = (new hmac.HMAC d.key, SHA3).update(new WordArray [1]).finalize(d.msg)
   m5.xor m3, {}
   T.equal m5.to_hex(), x1.to_hex(), "XOR works"
+  x2 = (new XOR d.key, [ SHA3, SHA512]).finalize d.msg
+  # The order matters in Combine.XOR since we prepend with the index
+  # of the HMAC to avoid 00s in the case using the same HMAC in both places.
+  T.assert not (x1.to_hex() is x2.to_hex())

 exports.test_vectors = (T, cb) ->
   for d in data


Pass the callback to the internal method

--- src/teak.coffee
+++ src/teak.coffee
@@ -154,7 +154,7 @@
       params['object_instance_id'] = objectInstanceId if objectInstanceId
       @postSignedRequest("/me/feed_post.json", params, (jqXHR) =>
         carrotResponse = $.parseJSON(jqXHR.responseText)
-        @internal_directFeedPost(carrotResponse)
+        @internal_directFeedPost(carrotResponse, callback, postMethod)
       )

   internal_directFeedPost: (carrotResponse, callback, postMethod) ->


More fixed unit tests

More fixed unit tests

--- test/xml2js.test.coffee
+++ test/xml2js.test.coffee
@@ -123,13 +123,14 @@
     equ r, null)

   'test parse with custom char and attribute object keys': skeleton(attrkey: 'attrobj', charkey: 'charobj', (r) ->
-    assert.equal r['chartest']['attrobj']['desc'], 'Test for CHARs'
-    assert.equal r['chartest']['charobj'], 'Character data here!'
-    assert.equal r['cdatatest']['attrobj']['desc'], 'Test for CDATA'
-    assert.equal r['cdatatest']['attrobj']['misc'], 'true'
-    assert.equal r['cdatatest']['charobj'], 'CDATA here!'
-    assert.equal r['nochartest']['attrobj']['desc'], 'No data'
-    assert.equal r['nochartest']['attrobj']['misc'], 'false')
+    console.log 'Result object: ' + util.inspect r, false, 10
+    equ r.sample.chartest[0].attrobj.desc, 'Test for CHARs'
+    equ r.sample.chartest[0].charobj, 'Character data here!'
+    equ r.sample.cdatatest[0].attrobj.desc, 'Test for CDATA'
+    equ r.sample.cdatatest[0].attrobj.misc, 'true'
+    equ r.sample.cdatatest[0].charobj, 'CDATA here!'
+    equ r.sample.nochartest[0].attrobj.desc, 'No data'
+    equ r.sample.nochartest[0].attrobj.misc, 'false')

   'test child node without explicitArray': skeleton(explicitArray: false, (r) ->
     assert.equal r['arraytest']['item'][0]['subitem'], 'Baz.'


Fix switch of language for group-label (#29)

--- packages/custom/transparency/coffee/public/controllers/flowController.coffee
+++ packages/custom/transparency/coffee/public/controllers/flowController.coffee
@@ -202,6 +202,8 @@
         $scope.typesText.forEach (t) -> t.text = gettextCatalog.getString TPAService.decodeType t.type
         $scope.mediaLabel = gettextCatalog.getString 'Media'
         $scope.organisationsLabel = gettextCatalog.getString 'Organisations'
+        $scope.organisationGroupLabel = gettextCatalog.getString 'Organisation Group'
+        $scope.mediaGroupLabel = gettextCatalog.getString 'Media Group'
         setIntroOptions()


Please confirm your email to continue…

Please confirm your email to continue…

--- client/app/Applications/Account.kdapplication/AppController.coffee
+++ client/app/Applications/Account.kdapplication/AppController.coffee
@@ -178,7 +178,7 @@
       """

     modal = new KDModalView
-      title            : "You must confirm your email address!"
+      title            : "Please confirm your email to continue…"
       width            : 500
       overlay          : yes
       cssClass         : "new-kdmodal"


login: failure notice section

login: failure notice section

--- client/Login/AppView.coffee
+++ client/Login/AppView.coffee
@@ -181,6 +181,9 @@
       domId    : "invite-recovery-notification-bar"
       cssClass : "invite-recovery-notification-bar hidden"
       partial  : "..."
+
+    @failureNotice = new KDCustomHTMLView
+      cssClass     : "failure-notice hidden"

     KD.getSingleton("mainController").on "landingSidebarClicked", => @unsetClass 'landed'

@@ -255,6 +258,7 @@

         {{> @resendForm}}

+      {{> @failureNotice}}

{{> @goToRecoverLink}}

{{> @goToRegisterLink}}•{{> @backToLoginLink}}

@@ -551,6 +555,10 @@
         when "resendEmail"
           @$('.flex-wrapper').addClass 'one'
           @resendForm.usernameOrEmail.input.setFocus()
+        when "failureNotice"
+          @$('.flex-wrapper').addClass 'one'
+          @failureNotice.show()
+          @github.hide()

   getRouteWithEntryPoint:(route)->
     {entryPoint} = KD.config
@@ -575,3 +583,11 @@
       new KDNotificationView
         title   : err.message
         duration: 1000
+
+  setFailureNotice: ({cssClass, title, message}) ->
+    @failureNotice.setClass cssClass  if cssClass
+    @failureNotice.updatePartial \
+      """
+      #{title}
+
#{message}

+      """


analytics: don't track pages if they've no params, query

--- client/Main/analytic.coffee
+++ client/Main/analytic.coffee
@@ -1,8 +1,9 @@
 do->
   KD.singleton('router').on "RouteInfoHandled", (argsForMixpanel)->
-    KD.mixpanel "Visit page, success", argsForMixpanel
+    _gaq.push ['_trackPageview', argsForMixpanel.path]

-    _gaq.push ['_trackPageview', argsForMixpanel?.path]
+    if argsForMixpanel and (Object.keys(argsForMixpanel)).length is not 0
+      KD.mixpanel "Visit page, success", argsForMixpanel

     argsForMixpanel.username = KD.whoami()?.profile?.nickname
     KD.remote.api.JPageHit.create argsForMixpanel, ->


Done cleaning up / refactorign calendar controller

--- app/assets/javascripts/farmbot_app/controllers/schedule_controller.js.coffee
+++ app/assets/javascripts/farmbot_app/controllers/schedule_controller.js.coffee
@@ -1,46 +1,37 @@
 controller = ($scope, Data, Calendar) ->
   nope = (e) -> alert 'Doh!'; console.error e
-  clear = -> $scope.form = {} and $scope.drawCalendar()
+  clear = -> $scope.form = {}; drawCalendar()
   $scope.repeats = [{show: 'Minutes', value: 'minutely'},
                     {show: 'Hours',   value: 'hourly'},
                     {show: 'Days',    value: 'daily'},
                     {show: 'Weeks',   value: 'weekly'},
                     {show: 'Months',  value: 'monthly'},
                     {show: 'Years',   value: 'yearly'}]
-
   Data.findAll('sequence', {}).catch(nope)
   Data.findAll('schedule', {}).catch(nope)
   Data.bindAll('sequence', {}, $scope, 'sequences')
   Data.bindAll('schedule', {}, $scope, 'schedules')

-  $scope.submit = ->
-    # :(? Why is this? This might be a JSData bug?
-    class ScheduleFormAdapter
-      constructor: ({@_id, @repeat, @time_unit,
-                     @start_time, @end_time, @sequence_id}) ->
-    Data
-      .create('schedule', (new ScheduleFormAdapter($scope.form)))
-      .catch(nope)
-      .then(clear)
+  $scope.prettyDates = []
+  drawCalendar = -> $scope.prettyDates = Calendar.draw($scope.schedules)
+  $scope.$watchCollection 'schedules', drawCalendar
+
+  $scope.submit = -> Data.create('schedule', $scope.form).catch(nope).then clear

   $scope.destroy = ->
     if !!$scope.form._id
-      Data
-        .destroy('schedule', $scope.form._id)
-        .catch(nope)
-        .then(clear)
+      Data.destroy('schedule', $scope.form._id).catch(nope).then clear
     else
       clear()

   $scope.edit = (sched) -> $scope.form = sched
-  $scope.prettyDates = []
-  drawCalendar = -> $scope.prettyDates = Calendar.draw($scope.schedules)
-  $scope.$watchCollection 'schedules', drawCalendar

   previousSchedule = (indx) ->
     $scope.prettyDates[indx - 1] || {next_time: new Date(0)}
   currentSchedule = (indx) -> $scope.prettyDates[indx]
+
   $scope.showDate = (indx) ->
+    return yes if $scope.prettyDates.length is 1
     before = previousSchedule(indx).next_time.getDay()
     after = currentSchedule(indx).next_time.getDay()
     if before is after then no else yes


Issue #1547 'use strict' Python-style octal literal notation 0o777

Allows octals in the form '0o777' and '0O777'

Case insensitive

Disallows decimals prefixed with '0'

--- lexer.coffee
+++ lexer.coffee
@@ -134,8 +134,12 @@
     return 0 unless match = NUMBER.exec @chunk
     number = match[0]
     lexedLength = number.length
-    if octalLiteral = /^0[0-7]+$/.test number
-      @error "octal literals \"#{number}\" are not allowed"
+    if nonStrictOctalLiteral = /^0\d+/.test number
+      dec = if /[89]/.test number then "\"#{number}\" " else ''
+      oct = if dec then '' else "\"#{number}\" "
+      @error "decimal literals #{dec}must not be prefixed with '0'; octal literals #{oct}must be prefixed with '0o'"
+    if octalLiteral = /0o([0-7]+)/i.exec number
+      number = (parseInt octalLiteral[1], 8).toString()
     if binaryLiteral = /0b([01]+)/i.exec number
       number = (parseInt binaryLiteral[1], 2).toString()
     @token 'NUMBER', number
@@ -592,8 +596,9 @@
 ///

 NUMBER     = ///
-  ^ 0x[\da-f]+ |                              # hex
-  ^ 0b[01]+ |                              # binary
+  ^ 0x[\da-f]+ |              # hex
+  ^ 0b[01]+    |              # binary
+  ^ 0o[0-7]+   |              # octal
   ^ \d*\.?\d+ (?:e[+-]?\d+)?  # decimal
 ///i


StackTemplateListController: Show inUseView if its item is started to used by user

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -73,7 +73,7 @@
         [item] = listItems.filter (i) -> i.getData()._id is stack.baseStackId
         if item
           item.getData().inUse = yes
-          item.updateLabels()
+          item.inUseView.show()

   applyToTeam: (item) ->


added Content-Length header

added Content-Length header

--- modules/http/platforms/node/lib/Server.coffee
+++ modules/http/platforms/node/lib/Server.coffee
@@ -149,6 +149,8 @@
                         ## EXPERIMENTAL enable streaming for bigger files
                         ## bypass the send method here :-(

+                        res.setHeader 'Content-Length', stats.size
+
                         if stats.size > 512
                             res.compress()


Fix disappearing relationship type in inspector

* Set default caption in saved grass style
* Set default color in saved grass style

--- community/browser/app/scripts/services/GraphStyle.coffee
+++ community/browser/app/scripts/services/GraphStyle.coffee
@@ -146,10 +146,9 @@
         @calculateStyle(selector)

       forRelationship: (rel) ->
-        style_element = @calculateStyle(@relationshipSelector(rel))
-        if not style_element.props.caption
-          style_element.props.caption = '{type}'
-        style_element
+        selector = @relationshipSelector(rel)
+        @setDefaultRelationshipStyling(selector, rel)
+        @calculateStyle(selector)

       findAvailableDefaultColor: () ->
         usedColors = {}
@@ -163,6 +162,18 @@

         return provider.defaultColors[0]

+      setDefaultRelationshipStyling: (selector, relationship) ->
+        rule = @findRule(selector)
+
+        if not rule?
+          rule = new StyleRule(selector, angular.extend({color: provider.defaultStyle.relationship.color}, @getDefaultRelationshipCaption()))
+          @rules.push(rule)
+          @persist()
+        if not rule.props.caption?
+          default_caption = @getDefaultRelationshipCaption()
+          angular.extend(rule.props, default_caption)
+          @persist()
+
       setDefaultStyling: (selector, item) ->
         rule = @findRule(selector)

@@ -180,6 +191,9 @@
         return {caption: '{id}'} if not item or not item.propertyList?.length > 0
         default_caption = {caption: "{#{item.propertyList?[0].key}}"}
         default_caption
+
+      getDefaultRelationshipCaption: (item) ->
+        return {caption: '{type}'}

       #
       # Methods for getting and modifying rules


added sanitizer

added sanitizer

--- modules/gui/tools/md.coffee
+++ modules/gui/tools/md.coffee
@@ -1,5 +1,6 @@

 md = require 'markdown'
+sanitizer = require 'sanitizer'

 ALLOWEDTAGS = ['a', 'b', 'table', 'tr', 'th', 'td', 'br', 'span', 'div', 'header', 'footer', 'section', 'article', 'nav', 'address']

@@ -68,6 +69,9 @@
     else
         html = ''

+    html = sanitizer.sanitize html, (uri)->
+        return uri
+
     #console.log html

     fn null, html


"Move around li test precondition"

"Move around li test precondition"

--- test/li.coffee
+++ test/li.coffee
@@ -1,15 +1,17 @@
 sinon = require 'sinon'

 li = require 'li'
+should = require 'should'

 describe 'li', ->
   mock = null

-  beforeEach ->
-    mock = sinon.mock process.stdout
-    mock.expects 'write'
+  describe 'help', ->

-  describe 'help', ->
+    beforeEach ->
+      mock = sinon.mock process.stdout
+      mock.expects 'write'
+
     it 'offers help', ->
       li.main ['li', 'help']
       mock.verify()


[Bugfix] Fix pagination

[Bugfix] Fix pagination

--- src/desktop/components/artwork_filter_2/models/filter.coffee
+++ src/desktop/components/artwork_filter_2/models/filter.coffee
@@ -29,9 +29,9 @@
       return if @get 'allFetched'

     variables = _.extend(
+      @params.defaultParams,
+      @params.mapped(),
       { @artist_id, @size, @page },
-      @params.defaultParams,
-      @params.mapped()
     )
     send = { query, variables }
     @set isLoading: true


Atom: save files atomically

Atom: save files atomically

--- atom/config.cson
+++ atom/config.cson
@@ -14,8 +14,8 @@
       "*.pyc"
     ]
     themes: [
+      "one-dark-ui"
       "spacegray-dark-syntax"
-      "one-dark-ui"
     ]
   "tree-view":
     hideIgnoredNames: true
@@ -30,6 +30,7 @@
     fontSize: 12
     showInvisibles: true
     showIndentGuide: true
+    backUpBeforeSaving: true
   "autocomplete-python": {}
   whitespace:
     ignoreWhitespaceOnCurrentLine: false


Clean out weird Python entries

Clean out weird Python entries

--- atom/config.cson
+++ atom/config.cson
@@ -77,19 +77,46 @@
   "bug-report": {}
   "asciidoc-preview": {}
   feedback: {}
+".html.php.text":
+  editor:
+    preferredLineLength: 100
 ".basic.html.text":
   editor:
     preferredLineLength: 100
-".python.regexp.source":
+".git-commit.text":
+  editor:
+    preferredLineLength: 72
+".gfm.source":
+  editor:
+    showInvisibles: false
+    softWrap: true
+    tabLength: 4
+  "wrap-guide":
+    enabled: false
+".shell.source":
+  editor:
+    tabLength: 4
+".java.source":
+  editor:
+    tabLength: 4
+".c.source":
+  editor:
+    tabLength: 4
+".json.source":
   editor:
     preferredLineLength: 100
-".python.text.traceback":
+  "wrap-guide":
+    enabled: false
+".python.source":
+  editor:
+    tabLength: 4
+    preferredLineLength: 100
+".hamlc.text":
   editor:
     preferredLineLength: 100
-".console.python.text":
-  editor:
-    preferredLineLength: 100
-".html.php.text":
+".haml.text":
+  "wrap-guide":
+    enabled: false
   editor:
     preferredLineLength: 100
 ".asciidoc.source":
@@ -97,39 +124,3 @@
     enabled: false
   editor:
     softWrap: true
-".haml.text":
-  "wrap-guide":
-    enabled: false
-  editor:
-    preferredLineLength: 100
-".hamlc.text":
-  editor:
-    preferredLineLength: 100
-".python.source":
-  editor:
-    tabLength: 4
-    preferredLineLength: 100
-".json.source":
-  editor:
-    preferredLineLength: 100
-  "wrap-guide":
-    enabled: false
-".c.source":
-  editor:
-    tabLength: 4
-".java.source":
-  editor:
-    tabLength: 4
-".shell.source":
-  editor:
-    tabLength: 4
-".gfm.source":
-  editor:
-    showInvisibles: false
-    softWrap: true
-    tabLength: 4
-  "wrap-guide":
-    enabled: false
-".git-commit.text":
-  editor:
-    preferredLineLength: 72


Always take minimum space for Linter panel

--- atom/config.cson
+++ atom/config.cson
@@ -159,6 +159,7 @@
       "W504"
     ]
   "linter-ui-default":
+    alwaysTakeMinimumSpace: true
     hidePanelWhenEmpty: true
     showPanel: true
     showTooltip: false