Relevance judgment performance feedback fetching

--- getFeedback.coffee
+++ getFeedback.coffee
@@ -1,4 +1,5 @@
 mongoose = require 'mongoose'
+async = require 'async'
 _ = require 'lodash'

 TaskSet = require './models/taskset'
@@ -8,12 +9,81 @@
 getFeedback = (obj, callback) ->
   switch obj.hit.type
     when 'relevance judgments'
-      console.log "Should be here"
+      return getRelevanceFeedback(obj, callback)
     when 'image tagging'
       return callback("No image tagging feedback yet")
     else
       return callback("No feedback logic for type: #{obj.hit.type}", null)
-  console.log obj.hit.type
   callback("No Feedback yet", null)

+getRelevanceFeedback = (obj, callback) ->
+  # This part of the aggregation pipeline will return unwound rows of
+  # _id/user/judgment/query/item_id
+  unwindJudgments = [
+    {$match:{hit_id:{$in:obj.allHitType}}},
+    {$unwind:'$tasks'},
+    {$project:{
+      user:1,
+      judgment:'$tasks.contribution.relevance',
+      query:'$facet.meta.query'
+      item_id:'$tasks.item.id'
+    }},
+    {$unwind: '$judgment'}]
+
+  # This will return all the judgments per result
+  # i.e. {_id:QUERY,ITEM_ID, judgments;['Not', 'Somewhat','Not', ...]}
+  byItemQuery = unwindJudgments.concat([
+    {$group:{
+      _id:{query:'$query', item:'$item_id'},
+      judgments:{$push:'$judgment'}
+    }}
+  ])
+
+  ## This will return all the judgments by worker; i.e.
+  # {"_id": USER, "judgments" :[{"doc":{"query","item"}, "judgment"},...]}
+  byWorkerQuery = unwindJudgments.concat([
+    {$group:{
+      _id:'$user',
+      judgments:{
+        $push:{doc:{query:'$query', item:'$item_id'}, judgment:'$judgment'}
+      }
+    }}
+  ])
+
+  async.parallel({
+    byWorker: (cb) -> TaskSet.aggregate(byWorkerQuery).exec(cb)
+    byItem: (cb) -> TaskSet.aggregate(byItemQuery).exec(cb)
+  }, (err, results) ->
+    byItem = results.byItem.reduce(((map, doc)->
+      id = "#{doc._id.query}-#{doc._id.item}"
+      judgeCounts = _.countBy(doc.judgments)
+      judgeProbs = _.mapValues(judgeCounts, (val)-> val/doc.judgments.length)
+      map[id] = {counts:judgeCounts, probs:judgeProbs}
+      map
+    ),{})
+    byWorker = _.map(results.byWorker, (worker) ->
+      user = worker._id
+      scores = _.map(worker.judgments,(tuple)->
+        id = "#{tuple.doc.query}-#{tuple.doc.item}"
+        score = byItem[id].probs[tuple.judgment]
+        {id: id, score: score}
+      )
+      rawScores =_.map(scores, (v)->v.score)
+      avgScore = _.sum(rawScores)/rawScores.length
+      {user:user, scores: scores, avgScore:avgScore}
+    )
+    allScores = _.map(byWorker, (w) -> w.avgScore).sort((a,b)-> a-b)
+    worker = _.find(byWorker, (w) -> w.user is "PETER")
+    performanceFeedback =
+      numWorkers: allScores.length
+      min: _.min(allScores)
+      max: _.max(allScores)
+      worker:
+        performance: worker.avgScore
+        rank: allScores.indexOf(worker.avgScore)
+
+    callback(null, performanceFeedback)
+  )
+
+
 module.exports = getFeedback


Dont need this false

Dont need this false

--- lib/provider-manager.coffee
+++ lib/provider-manager.coffee
@@ -49,7 +49,7 @@
       {provider} = providerMetadata
       if providerMetadata.matchesScopeChain(scopeChain)
         matchingProviders.push(provider)
-        if provider.excludeLowerPriority ? false
+        if provider.excludeLowerPriority
           lowestIncludedPriority = Math.max(lowestIncludedPriority, provider.inclusionPriority ? 0)
         if providerMetadata.shouldDisableDefaultProvider(scopeChain)
           disableDefaultProvider = true


Individual panels are collapsible.

Individual panels are collapsible.

--- picoCTF-web/web/coffee/management-problems.coffee
+++ picoCTF-web/web/coffee/management-problems.coffee
@@ -70,7 +70,7 @@
   render: ->
     categories = _.groupBy @props.problems, "category"
     categoryData = _.map categories, (problems, category) ->
-      name: "Only #{category} problems"
+      name: "Only #{category}"
       size: problems.length
       classifier: (problem) ->
         problem.category == category
@@ -114,7 +114,7 @@
   render: ->
     glyph = <Glyphicon glyph="ok"/>

-    <ListGroupItem onClick={@handleClick}>
+    <ListGroupItem onClick={@handleClick} className="classifier-item">
         {@props.name} {if @props.active then glyph}
<Badge>{@props.size}</Badge>

     </ListGroupItem>

@@ -162,10 +162,13 @@
       problemFooter = @props.tags.map (tag, i) ->
         {tag}

+    #TEMP
+    problemFooter = ""
+

     panelStyle = if @props.disabled then "default" else "info"

-    <Panel bsStyle={panelStyle} header={problemHeader} footer={problemFooter}>
+    <Panel bsStyle={panelStyle} header={problemHeader} footer={problemFooter} collapsible>

Score: {@props.score}

     </Panel>

@@ -178,7 +181,7 @@
       return
No problems have been loaded. Click here to get started.

     problemComponents = @props.problems.map ((problem, i) ->
-      <Col xs={6} lg={4} key={i}>
+      <Col xs={12} lg={6} key={i}>
         <Problem onProblemChange={@props.onProblemChange} {...problem}/>
       </Col>
     ).bind this


factored out common code

factored out common code

--- core/app/backbone/controllers/channels_controller.coffee
+++ core/app/backbone/controllers/channels_controller.coffee
@@ -2,30 +2,23 @@
     username = user.get('username')
     window.Channels.setUsernameAndRefreshIfNeeded(username)

-    if user.is_current_user()
-      channelCollectionView = new TopicSidebarView
-        collection: channels
-        model: user
-        showStream: showStream
-      FactlinkApp.leftMiddleRegion.show(channelCollectionView)
-      if currentChannel?
-        channelCollectionView.setActiveChannel(currentChannel)
-      else if activepage?
-        channelCollectionView.setActive(activepage)
-      else
-        channelCollectionView.unsetActive()
+    channelCollectionView = if user.is_current_user()
+                              new TopicSidebarView
+                                collection: channels
+                                model: user
+                                showStream: showStream
+                            else
+                              new ChannelsView
+                                collection: channels
+                                model: user
+                                showStream: showStream
+    FactlinkApp.leftMiddleRegion.show(channelCollectionView)
+    if currentChannel?
+      channelCollectionView.setActiveChannel(currentChannel)
+    else if activepage?
+      channelCollectionView.setActive(activepage)
     else
-      channelCollectionView = new ChannelsView
-        collection: channels
-        model: user
-        showStream: showStream
-      FactlinkApp.leftMiddleRegion.show(channelCollectionView)
-      if currentChannel?
-        channelCollectionView.setActiveChannel(currentChannel)
-      else if activepage?
-        channelCollectionView.setActive(activepage)
-      else
-        channelCollectionView.unsetActive()
+      channelCollectionView.unsetActive()


Use 0 column as default.

Use 0 column as default.

--- src/processors/stack.coffee
+++ src/processors/stack.coffee
@@ -91,7 +91,7 @@
       if i == 0
         column = e.columnNumber or 0
       else
-        column = -1
+        column = 0
       backtrace.push({
         function: m[1]
         file: m[2]


a more suitable name

a more suitable name

--- library/exposure/publish.coffee
+++ library/exposure/publish.coffee
@@ -46,7 +46,7 @@
 # encoded with JSON. Please refer to the implementation for info.
 # In addition to the documentation it also exposes a complement
 # set of service related data, such as health status and so on.
-module.exports.Publish = class Publish extends Barebones
+module.exports.PublishDocs = class PublishDocs extends Barebones

     # These invocations establish the parameters which are going
     # to be used for matching HTTP requests against this service.


JNewApp: When approving apps make sure there is a JName for it so users can reach it by using shortcuts like /Kodepad

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -310,7 +310,32 @@

     success: (client, state = yes, callback)->

-      @update $set: approved: state, callback
+      JName.one {@name}, (err, jname)=>
+
+        return callback err  if err
+
+        if state is no and jname
+          if @slug is jname.slugs[0].slug
+            jname.remove (err)->
+              console.error "Failed to remove JName: ", err  if err
+
+        else
+
+          unless jname
+
+            name = new JName
+              name  : @name
+              slugs : [
+                constructorName : 'JNewApp'
+                collectionName  : 'jNewApps'
+                slug            : @slug
+                usedAsPath      : 'slug'
+              ]
+
+            name.save (err) ->
+              console.error "Failed to save JName: ", err  if err
+
+        @update $set: approved: state, callback

       # identifier = @getAt 'identifier'


Moderation: delegate.checkFlag is used instead of fetching roles

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -345,21 +345,23 @@
     ]
     success: (client, callback)->
       {delegate} = client.connection
-      delegate.fetchRole client, (err, role)=>
-        if err
-          callback err
-        else unless role is 'super-admin'
-          callback new KodingError 'Access denied'
-        else
-          if (@status is 'deleted' or @status is 'synonym')
-            return callback new KodingError "Topic is already set as #{@status}!"
-          tagId = @getId()
-          @update {$set: status: "deleted"}, (err)=>
-            if err
-              callback err
-            else
-              @constructor.emit 'TagIsDeleted', {tagId}
-              callback null
+
+      deleteTag = (err) =>
+        tagId = @getId()
+        @update {$set: status: "deleted"}, (err)=>
+          return callback err if err
+          @constructor.emit 'TagIsDeleted', {tagId}
+          callback null
+
+      unless delegate.checkFlag('super-admin')
+        callback new KodingError 'Access denied'
+      else
+        if @status is 'deleted'
+          return callback new KodingError "Topic is already deleted!"
+
+        if @status is 'synonym'
+          return Relationship.remove {sourceId: @getId(), as: "synonymOf"}, deleteTag
+        deleteTag null

   @fetchSkillTags:(selector, options, callback)->


fix soft to hard tab conversion

fix soft to hard tab conversion

--- lib/format.coffee
+++ lib/format.coffee
@@ -76,7 +76,7 @@
     editorSettings = atom.config.get('editor')

     opts = atom.config.get('jsformat')
-    opts.indent_with_tabs = editor.getSoftTabs()
+    opts.indent_with_tabs = !editor.getSoftTabs()
     opts.indent_size = editorSettings.tabLength
     opts.wrap_line_length = editorSettings.preferredLineLength


created nodes for parser

created nodes for parser


Change logic to exclude token users

Change logic to exclude token users

--- services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
+++ services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
@@ -44,7 +44,7 @@
 	getInvitedMemberIds: (project_id, callback = (error, member_ids) ->) ->
 		CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
 			return callback(error) if error?
-			return callback null, members.filter((m) -> m.source == Sources.INVITE).map((m) -> m.id)
+			return callback null, members.filter((m) -> m.source != Sources.TOKEN).map((m) -> m.id)

 	USER_PROJECTION: {
 		_id: 1,
@@ -76,7 +76,7 @@
 	getInvitedMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
 		CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
 			return callback(error) if error?
-			members = members.filter((m) -> m.source == Sources.INVITE)
+			members = members.filter((m) -> m.source != Sources.TOKEN)
 			CollaboratorsHandler._loadMembers members, (error, users) ->
 				callback error, users

@@ -93,7 +93,7 @@
 	getInvitedMemberCount: (project_id, callback = (error, count) ->) ->
 		CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
 			return callback(error) if error?
-			return callback null, (members or []).filter((m) -> m.source == Sources.INVITE).length
+			return callback null, (members or []).filter((m) -> m.source != Sources.TOKEN).length

 	getInvitedCollaboratorCount: (project_id, callback = (error, count) ->) ->
 		CollaboratorsHandler.getInvitedMemberCount project_id, (error, count) ->
@@ -104,7 +104,7 @@
 		CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
 			return callback(error) if error?
 			for member in members
-				if member.id.toString() == user_id.toString() and member.source == Sources.INVITE
+				if member.id.toString() == user_id.toString() and member.source != Sources.TOKEN
 					return callback null, true, member.privilegeLevel
 			return callback null, false, null


Sprite export fix.

Sprite export fix.

--- packages/artificialengines/mummification/embeddedimagedata.coffee
+++ packages/artificialengines/mummification/embeddedimagedata.coffee
@@ -6,6 +6,10 @@
 if Meteor.isServer
   TextDecoder = require('text-encoder-lite').TextDecoderLite
   TextEncoder = require('text-encoder-lite').TextEncoderLite
+
+else
+  TextDecoder = window.TextDecoder
+  TextEncoder = window.TextEncoder

 compressionOptions =
   level: Pako.Z_BEST_COMPRESSION


Fix invalid hex character escape.

Fix invalid hex character escape.

--- lib/loader.coffee
+++ lib/loader.coffee
@@ -14,8 +14,8 @@

 # make sure the separators in a file name sort before any other chars
 fileNameCompare= (a,b)->
-  a= a.replace('/','\x1','g')
-  b= b.replace('/','\x1','g')
+  a= a.replace('/','\x01','g')
+  b= b.replace('/','\x01','g')
   return (b < a) - (a < b)

 isFileSync= (name)->


TESTS UPDATED

TESTS UPDATED

--- test/tests/Caffeine.Eight/FailingParseInfo/CouldHaveContinued.coffee
+++ test/tests/Caffeine.Eight/FailingParseInfo/CouldHaveContinued.coffee
@@ -115,7 +115,7 @@

         # log myParser.nonMatches
         assert.eq Object.keys(myParser.nonMatches).sort(), [
-            "FooRuleFooVariant: 'foo'"
+            "BarRuleBarVariant: /bar/"
           ]

   couldMatchKeys: ->
@@ -137,7 +137,8 @@

         # log myParser.nonMatches
         assert.eq Object.keys(myParser.nonMatches).sort(), [
-            "FooRuleFooVariant: 'foo'"
+           "BarRuleBarVariant: /bar/"
+           "BazRuleBazVariant: /baz/"
           ]

     test "partial match", ->


program lazily but tender to human brain

Just set big number and truncate when it is actually used.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -553,26 +553,22 @@
 class MoveToFirstLine extends Motion
   @extend()
   wise: 'linewise'
-  defaultCount: null
+
+  getCount: ->
+    super - 1

   moveCursor: (cursor) ->
     cursor.setBufferPosition(@getPoint())
     cursor.autoscroll(center: true)

   getPoint: ->
-    getFirstCharacterPositionForBufferRow(@editor, @getRow())
-
-  getRow: ->
-    if (count = @getCount()) then count - 1 else @getDefaultRow()
-
-  getDefaultRow: ->
-    0
+    row = getValidVimBufferRow(@editor, @getCount())
+    getFirstCharacterPositionForBufferRow(@editor, row)

 # keymap: G
 class MoveToLastLine extends MoveToFirstLine
   @extend()
-  getDefaultRow: ->
-    @getVimLastBufferRow()
+  defaultCount: Infinity

 # keymap: N% e.g. 10%
 class MoveToLineByPercent extends MoveToFirstLine


no util

no util

--- lib/transforms/split.coffee
+++ lib/transforms/split.coffee
@@ -1,7 +1,6 @@
 _      = require 'underscore'
 debug  = require('debug') 'us:split'
 Transform = require 'readable-stream/transform'
-util = require 'util'

 class Split extends Transform
   constructor: (@stream_opts, @options) ->


fixing menu deprecation

fixing menu deprecation

--- menus/supercollider.cson
+++ menus/supercollider.cson
@@ -1,23 +1,24 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  '.overlayer':
-    'SuperCopair: Open help file': 'supercopair:open-help-file'
-    'SuperCopair: Evaluate selection': 'supercopair:eval'
+  '.overlayer': [
+    {label: 'SuperCopair: Open help file', command: 'supercopair:open-help-file'}
+    {label: 'SuperCopair: Evaluate selection', command: 'supercopair:eval'}
+  ]

 'menu': [
   {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'SuperCopair'
-      'submenu': [
-        { 'label': 'Open post window', 'command': 'supercopair:open-post-window' },
-        { 'label': 'Evaluate selection', 'command': 'supercopair:eval' },
-        { 'label': 'Broadcast evaluation', 'command': 'supercopair:broadcast-eval' },
-        { 'label': 'Broadcast evaluation exclusively', 'command': 'supercopair:broadcast-eval-exclusively' },
-        { 'label': 'Broadcast cmd-period', 'command': 'supercopair:broadcast-cmd-period' },
-        { 'label': 'Broadcast cmd-period exclusively', 'command': 'supercopair:broadcast-cmd-period-exclusively' },
-        { 'label': 'Open help file', 'command': 'supercopair:open-help-file' }
+    label: 'Packages',
+    submenu: [{
+      label: 'SuperCopair',
+      submenu: [
+        {label: 'Open post window', command: 'supercopair:open-post-window' },
+        {label: 'Evaluate selection', command: 'supercopair:eval' },
+        {label: 'Broadcast evaluation', command: 'supercopair:broadcast-eval' },
+        {label: 'Broadcast evaluation exclusively', command: 'supercopair:broadcast-eval-exclusively' },
+        {label: 'Broadcast cmd-period', command: 'supercopair:broadcast-cmd-period' },
+        {label: 'Broadcast cmd-period exclusively', command: 'supercopair:broadcast-cmd-period-exclusively' },
+        {label: 'Open help file', command: 'supercopair:open-help-file' }
       ]
-    ]
+    }]
   }
 ]


Fixing optional payload in message

Fixing optional payload in message

--- client/components/player.cjsx
+++ client/components/player.cjsx
@@ -31,7 +31,7 @@
   componentDidMount: ->
     @connection.on 'message', (message) =>
       console.log 'message', message
-      audioUrl = message.payload.audioUrl || message.audioUrl
+      audioUrl = message.payload?.audioUrl || message.audioUrl?
       @playAudio(audioUrl)

   playAudio:  (audioUrl) =>


correct element name

correct element name

--- lib/runner.coffee
+++ lib/runner.coffee
@@ -35,7 +35,7 @@
       @innerHTML = "regular<b>bold<i>italic<i></b><i>italic<i>"
       return

-    fixer = document.registerElement("font-fixer",
+    fixer = document.registerElement("fonts-fixer",
       prototype: fixerProto
     )


Create new branch if there are none to choose

--- lib/advanced-web-editor.coffee
+++ lib/advanced-web-editor.coffee
@@ -297,14 +297,17 @@
     @lifeCycle.getYourBranches()
       .then (branches) =>
         # console.log branches
-        @branchView = new BranchView(
-          branches,
-          (branch) => @answerUseBranch branch,
-          () => @answerCreateNewBranch()
-        )
-        @modalPanel = atom.workspace.addModalPanel
-          item: @branchView
-          visible: true
+        if branches.length > 0
+          @branchView = new BranchView(
+            branches,
+            (branch) => @answerUseBranch branch,
+            () => @answerCreateNewBranch()
+          )
+          @modalPanel = atom.workspace.addModalPanel
+            item: @branchView
+            visible: true
+        else
+          @answerCreateNewBranch()

   answerUseBranch: (branch) ->
     @lifeCycle.isBranchRemote(branch).then (isRemote) =>


call destroyOverlay if active pane item change

--- lib/atom-ternjs-manager.coffee
+++ lib/atom-ternjs-manager.coffee
@@ -96,6 +96,7 @@
       @disposables.push editor.getBuffer().onDidSave (event) =>
         @client?.update(editor.getURI(), editor.getText())
     @disposables.push atom.workspace.onDidChangeActivePaneItem (item) =>
+      @type?.destroyOverlay()
       @provider?.clearSuggestionsAndHide()
       @rename?.hide()
       if !@isValidEditor(item)


cleaned up interface, exported open() function

--- lib/stream.coffee
+++ lib/stream.coffee
@@ -31,10 +31,12 @@
       res.close()

     # Pass the stream to the bootstrap script.
+    # TODO: shadow all globals to force explicit use of window object
     bootstrap = new Function("(#{options.bootstrap}).apply({}, arguments)")
-    bootstrap(stream, require("webpage"))
+    bootstrap(stream, phantom, require, window)

-    # Indicate to node that the phantomjs process is ready.
+    # Indicate to node that the phantomjs process is ready by writing
+    # some data to it.
     stream.write("READY")

   # Serialize the options for `func`, which will be stringified and opened
@@ -46,15 +48,19 @@
       return value
   return "(#{func})(JSON.parse(#{JSON.stringify(optionsString)}))"

-create = (options, bootstrap) ->
-  logger = options?.logger or {}
+open = (options, bootstrap) ->
+  options or= {}
+  if typeof options is "function" and not bootstrap
+    bootstrap = options
+    options = {}
+  logger = options.logger or {}
   # TODO: just find an open port instead
-  phantomServerPort = options?.phantomServerPort or PHANTOMJS_SERVER_PORT
+  phantomServerPort = PHANTOMJS_SERVER_PORT
   stdoutDataPrefix = "DATA#{Math.random().toString().slice(2)}:"
   phantomOptions =
     port: phantomServerPort
     stdoutDataPrefix: stdoutDataPrefix
-    bootstrap: options?.bootstrap or bootstrap or options
+    bootstrap: bootstrap

   # Build a tempfile that contains our phantomjs script.
   temp.open {prefix: "phantomstream", suffix: ".js"}, (err, info) ->
@@ -132,6 +138,7 @@
     on: (event, callback)->
       if event is "data"
         emitter.on "data", (buffer) ->
+          # TODO: implement real encoding
           if encoding
             callback(buffer.toString())
           else
@@ -153,8 +160,10 @@
           performWrite()
   return stream

+exports.open = open
+
 if module is require.main
-  stream = create {logger: console}, (stream, webpage) ->
+  stream = open {logger: console}, (stream, webpage) ->

     # Right now, just echo back any data received to show that this is working.
     # TODO: use dnode?


Clean up code

Clean up code

--- lib/autoprefixer/binary.coffee
+++ lib/autoprefixer/binary.coffee
@@ -159,6 +159,11 @@
     @waiting -= 1
     @doneCallback() if @waiting <= 0

+  # Write error to stderr and finish work
+  workError: (str) ->
+    @error(str)
+    @endWork()
+
   # Lazy loading for Autoprefixer instance
   compiler: ->
     @compilerCache ||= autoprefixer(@requirements)
@@ -189,10 +194,10 @@
           @outputInited = true
           fs.writeFileSync(@outputFile, '')
         fs.appendFileSync(@outputFile, prefixed)
+        @endWork()

       catch error
-        @error "autoprefixer: #{ error.message }"
-      @endWork()
+        @workError "autoprefixer: #{ error.message }"

     else if file
       fs.writeFile file, prefixed, (error) =>
@@ -218,15 +223,13 @@

       for file in @inputFiles
         unless fs.existsSync(file)
-          @error "autoprefixer: File #{ file } doesn't exists"
-          @endWork()
+          @workError "autoprefixer: File #{ file } doesn't exists"
           continue

         try
           css = fs.readFileSync(file).toString()
         catch error
-          @error "autoprefixer: #{ error.message }"
-          @endWork()
+          @workError "autoprefixer: #{ error.message }"
           continue

         @compileCSS(css, file)


added test for enforced date parsing

added test for enforced date parsing

--- dw.js/test/column-test.coffee
+++ dw.js/test/column-test.coffee
@@ -58,6 +58,16 @@
             'parsed valid value is a Date': (topic) ->
                 assert _.isDate(topic.val(2))

+        'force column type date':
+            'topic': dw.column('', ['1365','1458','1235','1382','1443','1581','1334','1794','3757','1801'])
+            'auto-detected as numbers': (column) ->
+                assert.equal column.type(), 'number'
+            'hard-set type to date': (column) ->
+                column.type('date')
+                assert.equal column.type(), 'date'
+            'values parsed as dates': (column) ->
+                assert.deepEqual column.val(0), new Date(1365, 0, 1)
+
         'API':
             'topic': dw.column('', sample_data)
             'column.raw': (topic) -> assert.deepEqual topic.raw(), sample_data


make drag-handles draggable

make drag-handles draggable

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -40,11 +40,11 @@

   getUpperHandlePosition: ->
     x: @props.ref.props.width/2 - @props.mark.x
-    y: 0
+    y: @props.mark.yUpper - @props.mark.y

   getLowerHandlePosition: ->
     x: @props.ref.props.width/2 - @props.mark.x
-    y: DEFAULT_HEIGHT
+    y: @props.mark.yLower - @props.mark.y

   render: ->
     averageScale = (@props.xScale + @props.yScale) / 2
@@ -99,6 +99,8 @@

   handleLowerResize: (e, d) ->
     console.log 'HANDLE LOWER RESIZE'
+    @props.mark.yLower += d.y / @props.yScale
+    @props.onChange e

   # handleDrag: (e, d) ->


Added the ability to specify aliases to buildTypes

--- src/scripts/teamcity.coffee
+++ src/scripts/teamcity.coffee
@@ -38,6 +38,9 @@

   buildTypes = []

+  aliases =
+    poapi: "bt2693"
+
   getAuthHeader = ->
     return Authorization: "Basic #{new Buffer("#{username}:#{password}").toString("base64")}", Accept: "application/json"

@@ -98,7 +101,12 @@
         builds = JSON.parse(body).build.splice(0, amount) unless err
         callback err, msg, builds

+
   mapNameToIdForBuildType = (msg, project, name, callback) ->
+
+    if aliases[name]
+      callback(msg, aliases[name])
+      return

     execute = (buildTypes) ->
       buildType =  _.find buildTypes, (bt) -> return bt.name == name and (not project? or bt.projectName == project)
@@ -186,6 +194,7 @@
         return

       url = "#{base_url}/httpAuth/action.html?add2Queue=#{buildType}"
+
       msg.http(url)
         .headers(getAuthHeader())
         .get() (err, res, body) ->


adding definition for each solid principle.

--- scripts/solid.coffee
+++ scripts/solid.coffee
@@ -9,6 +9,11 @@
 #
 # Commands:
 #   hubot define solid
+#   hubot define SRP
+#   hubot define OCP
+#   hubot define LSP
+#   hubot define ISP
+#   hubot define DIP
 #
 # Author:
 #   Mateusz Kopij <sagasu>
@@ -22,4 +27,19 @@
 2) Open/closed principle \n
 3) Liskov substitution principle \n
 4) Interface segregation principle \n
-5) Dependency inversion principle"+5) Dependency inversion principle"
+
+  robot.respond /define SRP/i, (msg)->
+    msg.send "Single responsibility principle. A class should have only a single responsibility (i.e. changes to only one part of the software's specification should be able to affect the specification of the class)."
+
+  robot.respond /define OCP/i, (msg)->
+    msg.send "Open/closed principle. Software entities … should be open for extension, but closed for modification."
+
+  robot.respond /define LSP/i, (msg)->
+    msg.send "Liskov substitution principle. Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. See also design by contract."
+
+  robot.respond /define ISP/i, (msg)->
+    msg.send "Interface segregation principle. Many client-specific interfaces are better than one general-purpose interface."
+
+  robot.respond /define DIP/i, (msg)->
+    msg.send "Dependency inversion principle. One should depend upon abstractions, [not] concretions."


Properly stick to the editor line height

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -18,6 +18,11 @@
   constructor: ->
     super
     @bufferChanges = []
+
+  initialize: ->
+    @lines.css 'line-height', atom.config.get('editor.lineHeight') + 'em'
+    atom.config.observe 'editor.lineHeight', =>
+      @lines.css 'line-height', atom.config.get('editor.lineHeight') + 'em'

   destroy: ->
     @unsubscribe()


Default to Basic model in Sharedcollection

--- src/sharedcollection.coffee
+++ src/sharedcollection.coffee
@@ -25,7 +25,7 @@

 class Backbone.SharedCollection extends Backbone.Collection

-  model: Backbone.SharedModel
+  model: Backbone.Model

   @generateGUID = ->
     S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4()


webapp/billing: move FAQ to doc and some tweaks + linking to it

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1466,199 +1466,26 @@

 # ~~~ FAQ START

-# some variables used in the text below
-faq_course_120 = 2 * PROJECT_UPGRADES.subscription.medium_course.benefits.member_host
-faq_academic_students =  PROJECT_UPGRADES.subscription.small_course.benefits.member_host
-faq_academic_nb_standard = Math.ceil(faq_academic_students / PROJECT_UPGRADES.subscription.standard.benefits.member_host)
-faq_academic_full = faq_academic_nb_standard * 4 * PROJECT_UPGRADES.subscription.standard.price.month
-faq_idle_time_free_h = require('smc-util/schema').DEFAULT_QUOTAS.mintime / 60 / 60
-
-# the structured react.js FAQ text
-FAQS =
-    differences:
-        q: What is the difference between free and paid plans?
-        a: The main differences are increased quotas and the quality of hosting; we also
-           prioritize supporting paying users.
-           We very strongly encourage you to make an account and explore our product for free!
-           There is no difference in functionality between the free and for-pay versions of
-           <SiteName/>; everything is still private by default for free users, and you can
-           make as many projects as you want.  You can even fully start teaching a course
-           in <SiteName/> completely for free, then upgrade at any point later so that your students
-           have a much better quality experience (for a small fraction of the cost of
-           their textbook).
-
-    member_hosting:
-        q: What does "member hosting" mean?
-        a:
-            There are two types of projects: "free projects" and "member projects".
-            Free projects run on heavily loaded computers.
-            Quite often, these computers will house over 150 simultaneously running projects!
-            Member-hosted projects are moved to much less loaded machine,
-            which are reserved only for paying customers.

-            Working in member-hosted projects feels much smoother because commands execute
-            more quickly with lower latency,
-            and CPU, memory and I/O heavy operations run more quickly.
-            Additionally, members only projects are always "ready to start".
-            Free projects that are not used for a few weeks are moved to "cold storage",
-            and it can take a while to move them back onto a free machine when you
-            later start them.
-
-    network_access:
-        q: What exactly does the quota "internet access" mean?
-        a:
-            Despite the fact that you are accessing <SiteName/> through the internet,
-            you are actually working in a highly restricted environment.
-            Processes running inside a free project are not allowed to directly
-            access the internet.  (We do not allow such access for free users, since when we did,
-            malicious users launched attacks on other computers from <SiteName/>.)
-            Enable internet access by adding the "internet access" quota.
-
-    idle_timeout:
-        q: What exactly does the quota "idle timeout" mean?
-        a:
-            By default, free projects stop running after {faq_idle_time_free_h} hour of idle time.
-            This makes doing an overnight research computation —
-            e.g., searching for special prime numbers — impossible.
-            With an increased idle timeout, projects are allowed to run longer unattended.
-            Processes might still stop if they use too much memory, crash due to an exception, or if the server they are
-            running on is rebooted.
-            (NOTE: Projects do not normally stop if you are continuously using them, and there are no
-            daily or monthly caps on how much you may use a <SiteName/> project, even a free one.)
-
-    cpu_shares:
-        q: What are "CPU shares" and "CPU cores"?
-        a:
-            All projects on a single server share the underlying resources.
-            These quotas determine how CPU resources are shared between projects.
-            Increasing them increases the priority of a project compared to others on the same host computer.

-            In particular, "shares" determines the amount of relative CPU time you get.
-
-    course120:
-        q:
-            I have a course of {faq_course_120 - 20} students.
-            Which plan should I purchase?
-
-        a:
-            You can combine and add up course subscriptions!
-            By ordering two times the 'medium course plan',
-            you will get {faq_course_120} upgrades covering all your students.
-
-    academic:
-        q: Do you offer academic discounts?
-        a:
-            Our course subscriptions are for academic use, and are already significantly discounted from the standard plans.
-            Please compare our monthly plans with the 4 month course plans.
-            For example, giving {faq_academic_students} students better member hosting and internet access
-            would require subscribing to {faq_academic_nb_standard} "standard plans" for 4 months
-            amounting to ${faq_academic_full}.
-
-    academic_quotas:
-        q: There are no CPU/RAM upgrades for courses. Is this enough?
-        a:
-            From our experience, we have found that for the type of computation used in most courses,
-            the free quotas for memory and disk space are plenty.
-            We do strongly suggest the classes upgrade all projects to "members-only" hosting,
-            since this provides much better computers with higher availability.
-
-    invoice:
-        q: How do I get an invoice with a specific information?
-        a:
-            After purchasing, please email us at <HelpEmailLink />, reference what you bought,
-            and tell us the payer{"'"}s name, contact information and any other specific
-            instructions.   We will then respond with a custom invoice for your purchase that
-            satisfies your unique requirements.
-
-    course_required_plan:
-        q: Am I required to pay for conducting a course?
-        a:
-            No. You can use all course related functionalities under a free plan.
-
-    student_files:
-        q: What happens with the files of my students after the course finishes?
-        a:
-            Students will continue to have access to their files after the course,
-            regardless of running the course under a paid plan or for free.
-            Their projects remain accessible,
-            they can (optionally) upgrade their projects with their own subscriptions,
-            and they can also download all files to their local computer.
-
-    close_browser:
-        q: Can I close my web-browser while I{"'"}m working?
-        a:
-            Yes! When you close your web-browser, all your processes and running sessions continue running.
-            You can start a computation, shut down your computer, go somewhere else,  sign in
-            on another computer, and continue working where you left off.
-            (Note that output from Jupyter notebook computations will be lost, though Sage worksheet output is
-            properly captured.)
-

-            The only reasons why a project or process stops are
-            that it hits its idle timeout, has used too much memory,
-            crashed due to an exception, or the server had to reboot.
-
-    private:
-        q: Which plan offers "private" file storage?
-        a: All our plans (free and paid) host your files privately by default.
-            Please read our Privacy Policy and {" "}
-            Copyright Notice.
-
-    git:
-        q: Can I work with Git — including GitHub, Bitbucket, GitLab, etc.?
-        a:
-            Git and various other source control tools are installed and ready to use via the "Terminal".
-            But, in order to also interoperate with sites hosting Git repositories,
-            you have to purchase a plan giving you "internet upgrades" and then applying this upgrade to your project.
-
-    backups:
-        q: Are my files backed up?
-        a:
-            All files in every project are snapshotted every 5 minutes.  You can browse your snapshots by
-            clicking the "Backups" link to the right of the file listing.   Also, <SiteName/> records
-            the history of all edits you or your collaborators make to most files, and you can browse
-            that history with a slider by clicking on the "History" button (next to save) in files.
-            We care about your data, and also make offsite backups periodically to encrypted USB
-            drives that are not physically connected to the internet.
-
-    download_everything:
-        q: How can I download my project files?
-        a:

-

-                 You can download each file individually via the "Files" interface.
-                 Select the file and click the "Download" button.
-

-

-                 It is also possible to create an archive for a directory or all files.
-                 For that, create a "Terminal"-file and issue one of these commands:
-

-

-                      ZIP archive (Windows): zip -r9 "[filename].zip" [directory-name...]
-

-

-                      Tarball (Unix-like): tar cjvf "[filename].tar.bz2" [directory-name...]
-

-

-                 (Replace [filename] with the actual filename and [directory-name] by one or more filenames or directory names.)
-                 Afterwards, download the archive as explained above.
-

-

 FAQ = exports.FAQ = rclass
     displayName : 'FAQ'
-
-    faq: ->
-        for qid, qa of FAQS
-

-                {qa.q}
-

-                {qa.a}
-

     render: ->

Frequently asked questions

-                {@faq()}
+

+
+                        Questions about billing, quotas, upgrades, ...
+
+

+

+
+                        Project related questions.
+
+

@@ -2072,10 +1899,19 @@
     # the space in "Contact us" below is a Unicode no-break space, UTF-8: C2 A0. " " didn't work there [hal]
     render_help_suggestion: ->

-            <Space/> If you have any questions at all, email <HelpEmailLink /> immediately.
+            <Space/> If you have any questions at all, read the{' '}
+            Billing{"/"}Upgrades FAQ or {' '}
+            email <HelpEmailLink /> immediately.

-                <Space/> <HelpEmailLink text={"Contact us"} /> if you are considering purchasing a course subscription and need a short trial
-                to test things out first.<Space/>
+                <Space/>
+                <HelpEmailLink text={"Contact us"} />{' '}
+                if you are considering purchasing a course subscription and need a short trial
+                to test things out first.
+                <Space/>

                 <Space/> Customized course plans are available.<Space/>
@@ -2094,8 +1930,13 @@
             if subs == 0
                 # no payment sources yet; no subscriptions either: a new user (probably)

-                    If you are teaching a course, choose one of the course packages.
-                    If you need to upgrade your projects, choosing a recurring subscription.
+                    If you are {' '}
+                    teaching a course, choose one of the course packages.
+                    If you need to upgrade your personal projects, choose a recurring subscription.
                     You will not be charged until you explicitly click
                     "Add Subscription or Course Package".
                     {help}
@@ -2114,8 +1955,14 @@
         else if subs == 0
             # have a payment source, but no subscriptions

-                Click "Add Subscription or Course Package...".   If you are teaching a course, choose one of the course packages.
-                    If you need to upgrade your projects, choosing a recurring subscription.
+                Click "Add Subscription or Course Package...".
+                If you are{' '}
+                teaching a course, choose one of the course packages.
+                If you need to upgrade your personal projects, choose a recurring subscription.
                 You will be charged only after you select a specific subscription and click
                 "Add Subscription or Course Package".
                 {help}
@@ -2132,7 +1979,7 @@

                 You may sign up for the same subscription package more than
                 once to increase the number of upgrades that you can use.
-                Past invoices and receipts are also available below.
+                Past invoices and receipts are available below.
                 {help}


Fix the context-menu selectors

Fix the context-menu selectors

--- menus/rails.cson
+++ menus/rails.cson
@@ -1,7 +1,11 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  '.overlayer':
-      'Enable rails': 'rails:toggle'
+  'atom-text-editor': [
+    {
+      'label': 'Enable rails'
+      'command': 'rails:toggle'
+    }
+  ]

 'menu': [
   {


Fix package version misappropriation when reporting deps

--- lib/metrics.coffee
+++ lib/metrics.coffee
@@ -108,24 +108,48 @@
   # TODO: Remove these deprecation tracking methods after we remove 1.0 deprecations
   watchDeprecations: ->
     @deprecationCache = {}
+    @packageVersionCache = {}
+
+    atom.packages.onDidActivateInitialPackages =>
+      packages = atom.packages.getLoadedPackages()
+      for pack in packages
+        @packageVersionCache[pack.name] = pack?.metadata?.version or 'unknown'
+
+      # Reports initial deprecations as deprecations may have happened before metrics activation.
+      setImmediate =>
+        for deprecation in grim.getDeprecations()
+          @reportDeprecation(deprecation)
+        return
+
+      return
+
+    atom.packages.onDidLoadPackage (pack) =>
+      unless @packageVersionCache[pack.name]
+        @packageVersionCache[pack.name] = pack?.metadata?.version or 'unknown'
+
     grim.on 'updated', (deprecation) =>
-      setImmediate =>
-        packageNames = {}
-        message = deprecation.getMessage()[0...500]
+      setImmediate => @reportDeprecation(deprecation)

-        for __, stack of deprecation.stacks
-          packageName = stack.metadata?.packageName ? (@getPackageName(stack) or '').toLowerCase()
-          continue unless packageName
+  reportDeprecation: (deprecation) ->
+    packageNames = {}
+    message = deprecation.getMessage()[0...500]

-          pack = atom.packages.getLoadedPackage(packageName)
-          version = pack?.metadata?.version or 'unknown'
-          nameAndVersion = "#{packageName}@#{version}"
+    for __, stack of deprecation.stacks
+      packageName = stack.metadata?.packageName ? (@getPackageName(stack) or '').toLowerCase()
+      continue unless packageName

-          unless @deprecationCache[nameAndVersion + message]?
-            @deprecationCache[nameAndVersion + message] = true
-            Reporter.sendEvent('deprecation-v2', nameAndVersion, message)
+      unless @packageVersionCache[packageName]
+        pack = atom.packages.getLoadedPackage(packageName)
+        @packageVersionCache[packageName] = pack?.metadata?.version or 'unknown'

-        return
+      version = @packageVersionCache[packageName]
+      nameAndVersion = "#{packageName}@#{version}"
+
+      unless @deprecationCache[nameAndVersion + message]?
+        @deprecationCache[nameAndVersion + message] = true
+        Reporter.sendEvent('deprecation-v2', nameAndVersion, message)
+
+    return

   getFileNameFromCallSite: (callsite) ->
     callsite.fileName ? callsite.getFileName()


Remove focus after items update

Remove focus after items update

--- lib/select-list-element.coffee
+++ lib/select-list-element.coffee
@@ -43,9 +43,6 @@
   itemsChanged: ->
     @selectedIndex = 0
     @renderItems()
-    setTimeout =>
-      @input?.focus()
-    , 0

   moveSelectionUp: () ->
     unless @selectedIndex <= 0


Enhance deliveries.js.coffee to allow flatpickr update

--- app/assets/javascripts/backend/deliveries.js.coffee
+++ app/assets/javascripts/backend/deliveries.js.coffee
@@ -66,8 +66,8 @@

                   if $(this).is ":ui-selector"
                     $(this).selector("value", newVal)
-                  else if $(this)._flatpickr?
-                    $(this).setDate(newVal)
+                  else if this._flatpickr?
+                    this._flatpickr.setDate(newVal)
                   else if $(this).is "input"
                     $(this).val(newVal)
                   else
@@ -119,10 +119,6 @@
           # else
           #   shape.hide()

-        # Overrides flatpickr input
-        flatpickrInput = $('input.datetime.required.flatpickr-input.form-control.input')
-        flatpickrInput.attr("data-when-item", "achieved_at") && flatpickrInput.attr("data-when-set-value", "RECORD_VALUE")
-
         error: (request, status, error) ->
           console.log("Error while retrieving price and tax fields content: #{error}")
     else


SocketHandler comments

SocketHandler comments

--- app/assets/javascripts/pirates/socketHandler.coffee
+++ app/assets/javascripts/pirates/socketHandler.coffee
@@ -1,5 +1,9 @@
+###
+  Module for communication between the Rails app and the client
+  Uses websocket and json datapacket which it also maps to function in the client
+###
 class @SocketHandler
-
+  # map name of operations in packets to specific functions in the client
   # can't be stored directly because the Grid does not exist at loading  _mapping = null
   operationMapping = () ->
     return _mapping if _mapping? && _mapping not null
@@ -15,6 +19,7 @@
     }
     return _mapping

+  # push an incoming packet into the packet queue
   addToQueue = (packet) =>
     return if not Simulation.isInExecutionMode

@@ -34,6 +39,7 @@
       @currentId = id
     @packetQueue.push packet

+  # networking error and success callbacks
   onOpen = (event) ->
     console.log 'Socket connection ready!'
     console.log event
@@ -58,6 +64,8 @@

     # listen to step event which includes all packages
     @webSocket.bind 'step', addToQueue
+
+    # initialize member for the SocketHandler scope
     @lifeTime = 0
     @packetQueue = []
     @usedIDs = []
@@ -68,9 +76,11 @@
   @setStackDeep = () =>
     @stackDeep = 0

+  # sends a serialize state of the simulation to rails to start the simulation
   @startSimulation = (serialized) ->
     @webSocket.trigger "simulateGrid", serialized

+  # notifies the app if that the execution stopped
   @stopSimulation = () ->
     @webSocket.trigger 'stop'

@@ -78,6 +88,7 @@
     @packetQueue = []
     @usedIDs.push @currentId

+  # called every tick
   @update = () =>
     if !Simulation.isInExecutionMode then return

@@ -94,6 +105,7 @@
     throw "#{name} in packet are not an array" unless Array.isArray arr
     throw "#{name} is empty" if arr.length <= 0

+  # simulate the operations part of each packet
   simulateOperation = (packet) =>
     operations = packet.operations
     validateArray "operations", operations
@@ -114,16 +126,19 @@
         console.log "received invalid operation", op
         throw "Packet error: Invalid operation!"

+  # highlights the current line of an simulation
   simulateLine = (packet) ->
     line = packet.line || 0
     throw "Packet error: line '#{line}' is not a valid line" unless line > 0
     CodeGUI.highlightLine line

+  # show variable allocation
   simulateAllocations = (packet) ->
     allocations = packet.allocations
     for name of allocations
       CodeGUI.WatchList.setAllocation name, allocations[name]

+  # simulate console output
   simulateMessages = (packet) ->
     messages = packet.messages
     validateArray "messages", messages
@@ -131,7 +146,7 @@
     for messageObj in messages
       msg = messageObj.message

-      # allows more message types
+      # allows more message types via event handling
       event = new CustomEvent('socketHandlerSimulateMessage', {
         'detail': messageObj
         'cancelable': true
@@ -139,11 +154,13 @@
       canceled = !window.dispatchEvent event
       messageObj = event['detail']
       continue if canceled
+
       switch messageObj.type
           when 'log' then Console.log msg
           when 'warning' then Console.logWarning msg
           when 'error' then Console.logError msg

+  # takes the first packet in queue and simulates each part of it.
   @simulatePacket = () =>
     return if @packetQueue.isEmpty

@@ -172,7 +189,7 @@
     simulateOperation currentPacket if currentPacket.operations?
     breakPoint currentPacket if currentPacket.break?

-
+  # chris comfortable debugging
   breakPoint = (packet) =>
     breaks = packet.break
     for br in breaks


don't validate assignment external tool settings if hidden

test plan:
* open the assignment creation page
* set the submission type to "external tool" but leave
 the url blank
* set the grading type to "Not Graded"
* it should hide the external tool settings
* you should be able to save the assignment
 without getting stuck on a hidden validation error

closes #OUT-1392

Change-Id: I9d480ae2687b8141e8a5a14e7239632dd968507e
Reviewed-on: https://gerrit.instructure.com/125332
Tested-by: Jenkins
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
QA-Review: Andrew Porter <747194079243b0beca9081f8cd559c020b06a314@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/views/assignments/EditView.coffee
+++ app/coffeescripts/views/assignments/EditView.coffee
@@ -560,7 +560,7 @@
       errors

     _validateExternalTool: (data, errors) =>
-      if data.submission_type == 'external_tool' and $.trim(data.external_tool_tag_attributes?.url?.toString()).length == 0
+      if data.submission_type == 'external_tool' && data.grading_type != 'not_graded' && $.trim(data.external_tool_tag_attributes?.url?.toString()).length == 0
         errors["external_tool_tag_attributes[url]"] = [
           message: I18n.t 'External Tool URL cannot be left blank'
         ]


runtime: Make flowhub-registry optional

runtime: Make flowhub-registry optional

superagent, which is a dependency seems to have issues with browserify

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -14,7 +14,11 @@
     uuid = require("node-uuid")
     EventEmitter = require('events').EventEmitter;

-flowhub = require("flowhub-registry")
+try
+  flowhub = require("flowhub-registry")
+catch e
+  #
+
 commandstream = require("./commandstream")
 generate = require("./generate")
 c = require("./componentlib")
@@ -400,6 +404,8 @@
     return

 createFlowhubRuntime = (user, ip, port, label, id, apihost) ->
+    return null if not flowhub
+
     # Unique identifier of the runtime instance
     id = id or uuid.v4()
     label = label or "MicroFlo"
@@ -421,6 +427,8 @@
     return rt

 setupFlowhubRuntimePing = (rt) ->
+    return if not rt
+
     # TODO: handle more sanely
     rtPingInterval = setInterval(->
         rt.ping (err) ->
@@ -429,6 +437,8 @@
     return rtPingInterval

 registerFlowhubRuntime = (rt, callback) ->
+    return if not rt
+
     rt.register callback
     return


use path.basename instead of our own function

--- growlcoffee.coffee
+++ growlcoffee.coffee
@@ -1,18 +1,16 @@
+{basename} = require('path')
 {exec} = require('child_process')
 coffee = require('coffee-script')
-
-filename = (filepath)->
-    filepath.split('/').slice(-1)[0]

 growl = (title, message, imagename)->
     exec("growlnotify '#{title}' -n 'CoffeeScript' -m '#{message}' --image '#{__dirname}/#{imagename}.png'")

 coffee.on('failure', (error, task)->
     issue = error.message.split(',')[1].trim()
-    growl(issue, filename(task.file), 'failure')
+    growl(issue, basename(task.file), 'failure')
     console.error(error.message)
 )

 coffee.on('success', (task)->
-    growl('Compiled', filename(task.file), 'success')
+    growl('Compiled', basename(task.file), 'success')
 )


:boar: Remove mocha task

:boar: Remove mocha task

--- grunt/mocha.coffee
+++ grunt/mocha.coffee
@@ -1,6 +0,0 @@
-module.exports =
-  options:
-    log: true
-    reporter: 'Spec'
-  all:
-    src: ['test/browser/**/*.html']


Passed selector to fetch method of ActivityAppController also added some defensive checks.

--- client/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
+++ client/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
@@ -25,9 +25,9 @@
     @listViews            = []

     @group.on "moreLinkClicked", =>
-      groupLoader = new KDLoaderView size: width: 24
-      @group.addSubView groupLoader
-      groupLoader.show()
+      @groupLoader = new KDLoaderView size: width: 24
+      @group.addSubView @groupLoader
+      @groupLoader.show()

       count = 10

@@ -36,12 +36,16 @@
         @createShowMore count
         @isUserViewCreated = yes

-      options =
-        limit: count
-        from : @lastFetchedDate or @getData().createdAtTimestamps[0]
-        to   : @getData().createdAtTimestamps[1]
-
-      appManager.tell "Activity", "fetchQuery", options, (teasers, activities) =>
+      selector    =
+        type      : { $in : [options.type or= 'CNewMemberBucketActivity'] }
+        createdAt :
+          $gt     : @lastFetchedDate or @getData().createdAtTimestamps[0]
+          $lt     : @getData().createdAtTimestamps[1]
+
+      options     =
+        limit     : count
+
+      appManager.tell "Activity", "fetch", selector, options, (teasers, activities) =>
         @getData().anchors = @getData().anchors.concat teasers.map (item)-> item.anchor
         @group.setData @getData().anchors
         @group.loader.hide()
@@ -57,9 +61,9 @@
         @listViews.push newMembersList
         @group.addSubView newMembersList.getView()
         @lastFetchedItemCount = @lastFetchedItemCount + count
-        @showMore.hide() if @getData().count <= @lastFetchedItemCount
+        @showMore.hide() if @getData().count <= @lastFetchedItemCount and @showMore
         @lastFetchedDate = activities.last.createdAt
-        groupLoader.destroy()
+        @groupLoader.destroy()

   createCloseButton: ->
     @addSubView @closeButton = new KDView
@@ -68,7 +72,8 @@
       click    : =>
         list.getView().destroy() for list in @listViews
         @closeButton.destroy()
-        @showMore.destroy()
+        @showMore?.destroy()
+        @groupLoader?.destroy()
         @lastFetchedItemCount = 0
         @isUserViewCreated    = no
         @lastFetchedDate      = null


Proper engrish

Proper engrish

--- client/app/Applications/Home.kdapplication/ContentDisplays/AboutView.coffee
+++ client/app/Applications/Home.kdapplication/ContentDisplays/AboutView.coffee
@@ -49,7 +49,7 @@

-            We're located at SOMA district in San Francisco, California.
+            We're located in the SOMA district of San Francisco, California.

           <address>


Added "cancel" button to modal, removed focus from "Delete" button

--- client/app/Applications/Inbox.kdapplication/Views/InboxNewMessageBar.coffee
+++ client/app/Applications/Inbox.kdapplication/Views/InboxNewMessageBar.coffee
@@ -59,8 +59,7 @@
         Cancel       :
           style      : "modal-cancel"
           title      : "cancel"
-          callback   : ->
-            modal.destroy()
+          callback   : -> modal.destroy()

     modal.buttons.Delete.blur()


fixed security caveat

fixed security caveat

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -172,7 +172,7 @@
       sort        :
         createdAt : -1

-    KD.remote.api.CActivity[methodName] options, (err, activities)=>
+    KD.remote.api.CActivity.fetchFacets options, (err, activities)=>
       if err then callback err
       else
         KD.remote.reviveFromSnapshots clearQuotes(activities), callback


handle query fixed to warn user

handle query fixed to warn user

--- client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
@@ -29,7 +29,37 @@
     pane = @tabView.getActivePane()
     {webTermView} = pane.getOptions()
     webTermView.once 'WebTermConnected', (remote)->
-      remote.input decodeURIComponent query.command+'\n'
+      if query.command
+        command = decodeURIComponent query.command
+        modal = new KDModalView
+          title   : "Warning!"
+          content : """
+

+

+              If you don't trust this app, or if you clicked on this
+              link not knowing what it would do - be careful it can
+              damage/destroy your Koding VM.
+

+

+

+

+              This URL is set to execute the command below:
+

+

+

+            #{Encoder.XSSEncode command}
+

+          """
+          buttons :
+            "Run" :
+              cssClass: "modal-clean-gray"
+              callback: ->
+                remote.input "#{command}\n"
+                modal.destroy()
+            "Cancel":
+              cssClass: "modal-cancel"
+              callback: ->
+                modal.destroy()

   _windowDidResize:->
     # 10px being the application page's padding


Fixed increasing activity counter for non activity feeds such as followee items.

--- client/landing/app/CommonViews/mainnavigation/navigationactivitylink.coffee
+++ client/landing/app/CommonViews/mainnavigation/navigationactivitylink.coffee
@@ -26,7 +26,7 @@
       @getSingleton('activityController').on "ActivitiesArrived", (activities) =>
         return if @getSingleton('router').currentPath is "Activity"
         myId = KD.whoami().getId()
-        @newActivityCount++ for activity in activities when activity.originId isnt myId
+        @newActivityCount++ for activity in activities when activity.originId and activity.originId isnt myId

         appManager.tell "Activity", "getNewItemsCount", (itemCount) =>
           @updateNewItemsCount itemCount


Use koding.com instead of beta.koding to fix filetree view feature

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -126,7 +126,7 @@
   previewFile:(nodeView, event)->

     file = nodeView.getData()
-    publicPath = file.path.replace /.*\/(.*\.beta.koding.com)\/website\/(.*)/, 'http://$1/$2'
+    publicPath = file.path.replace /.*\/(.*\.koding.com)\/website\/(.*)/, 'http://$1/$2'
     if publicPath is file.path
       {nickname} = KD.whoami().profile
       appManager.notify "File must be under: /#{nickname}/Sites/#{nickname}.#{location.hostname}/website/"


Catch tab (pane) changes and give the focus the editor if the new tab (pane) is Ace editor

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -16,7 +16,7 @@
       @getView().setClass "no-context-menu"

     @getSingleton('mainController').on "NewFileIsCreated", (newFile)=> @navigateToNewFile newFile
-    @getSingleton('mainController').on "SelectedFileChanged", (file)=> @highlightFile file
+    @getSingleton('mainController').on "SelectedFileChanged", (view)=> @highlightFile view

   addNode:(nodeData, index)->

@@ -47,8 +47,16 @@
           @selectNode @nodes[newFile.path]
         @listenedPaths[file.path] = file.path

-  highlightFile:(file)->
-    @selectNode @nodes[file.path]
+  highlightFile:(view)->
+
+    @selectNode @nodes[view.data.path], null, no
+
+    if view.ace?
+      if view.ace.editor?
+        view.ace.editor.focus()
+      else
+        view.ace.on "ace.ready", ->
+          view.ace.editor.focus()

   navigateToNewFile:(newFile)=>

@@ -63,8 +71,6 @@
       #   @addNode newFile, index

       @selectNode @nodes[newFile.path]
-
-

     # #
     # # file.on "fs.saveAs.finished", (newFile, oldFile)=>


Specify base.

Specify base.

--- app/scripts/models/one_dimensional_automaton.coffee
+++ app/scripts/models/one_dimensional_automaton.coffee
@@ -5,7 +5,7 @@
       rule = params.rule

       binary = (numberOrString) ->
-        parseInt(numberOrString).toString 2
+        parseInt(numberOrString, 10).toString 2

       binaryArray = (numberOrString, length) ->
         ($filter('pad'))(binary(numberOrString), length).split ''


Remove old scrollbar measuring code.

Remove old scrollbar measuring code.

--- js-library/app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
+++ js-library/app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
@@ -1,22 +1,3 @@
-scrollbarWidth = 0
-FactlinkJailRoot.loaded_promise.then ->
-  # Create the measurement nod; see http://davidwalsh.name/detect-scrollbar-width
-  scrollDiv = document.createElement("div");
-  $(scrollDiv).css(
-    width: "100px"
-    height: "100px"
-    overflow: "scroll"
-    position: "absolute"
-    top: "-9999px"
-  )
-  document.body.appendChild(scrollDiv)
-
-  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
-
-  document.body.removeChild(scrollDiv)
-  console.log 'scrollbar width: ', scrollbarWidth
-
-
 # To check for scrollbars on the window,  use the slightly unusual window.innerHeight
 # rather than  document.documentElement.clientHeight so it works in css compat mode.
 window_has_scrollbar = -> window.innerHeight < document.documentElement.scrollHeight


Allow urls that already have the ws:// scheme, add it if it's missing. This also allows for wss:// for websockets over ssl.

--- lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee
+++ lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee
@@ -4,7 +4,8 @@
 class WebSocketRails.WebSocketConnection

   constructor: (@url,@dispatcher) ->
-    @_conn           = new WebSocket("ws://#{@url}")
+    @url             = "ws://#{@url}" unless @url.match(/^wss?:\/\//)
+    @_conn           = new WebSocket(@url)
     @_conn.onmessage = @on_message
     @_conn.onclose   = @on_close


Few fixes in RPC generation routines

Few fixes in RPC generation routines

--- lib/session.coffee
+++ lib/session.coffee
@@ -150,11 +150,11 @@
         r.exist = true

       if declaration[2] > current[2]
+        r.object = true
+        r.exist = false
+
+      if declaration[4] > current[4]
         r.writable = true
-        r.exist = false
-
-      if declaration[4] > current[4]
-        r.object = true
         r.exist = false

       if declaration[6] > current[6]
@@ -164,7 +164,7 @@
           r.object = false
           r.exist = false
           gpn = false
-        else if not current[3]?
+        else if not current[2] > 0
           r.object = true

       if declaration[7]? and current[7]?
@@ -254,6 +254,11 @@

       for d, j in descendantLoad when d
         toLoad.push(path.concat(descendantTimestamps[j][0][path.length] ? null))
+
+    if declaration[7]? and not current[4]?
+      # Need writable attribute when declaring a value
+      toLoad.push(path)
+      return

     for i in [0...declaration.length] by 2
       if declaration[i]? and not current[i]?
@@ -343,7 +348,7 @@
             path = common.parsePath(provision[1])
             for i in [path.length...16] by 1
               path.length = i
-              allDeclarations.push([path.slice(), provision[2], null, null, null, null, null, provision[2]])
+              allDeclarations.push([path.slice(), provision[2], null, 1, null, 1, null, provision[2]])
           when 'value'
             allDeclarations.push([[common.parsePath(provision[1]), provision[2], null, null, null, null, null, provision[2]]])
           when 'tag'
@@ -400,7 +405,8 @@
             device.declare(sessionData.deviceData, d[0], d.slice(1), sessionData.timestamp)
       else
         for declaration in decs
-          params = device.getAll(sessionData.deviceData, declaration[0], sessionData.revisions[iter] >> 1)
+          params = device.getAll(sessionData.deviceData, declaration[0], (sessionData.revisions[iter] >> 1) + 1)
+          # TODO move setting tags elsewhere
           if declaration[0][0] == 'Tags' and declaration[0].length == 2
             if not declaration[0][1]?
               continue if declaration[8][0]


fix bugs

fix bugs

--- lib/steedos.coffee
+++ lib/steedos.coffee
@@ -105,7 +105,7 @@
                         for topic in req.body.pushTopics
                             eventName = topic
                             if req.param("steedosId")?
-                                eventName = eventName + "|" + req.param("steedosId").replace("@", "_").replace(".", "_").replace("-", "_")
+                                eventName = eventName + "|" + req.param("steedosId").replace(/[@\-\.]/g, "_")
                             events[eventName] = {}
                         subscriber.addSubscriptions events, (r) ->
                             generateResponse(req, res, subscriber)


Stripped xml tag for SVG twigs

Stripped xml tag for SVG twigs

--- gulp/images.coffee
+++ gulp/images.coffee
@@ -11,9 +11,12 @@
   g.src "images/**/*.svg"
     .pipe g.dest "web/images"
     .pipe g.p.tap (file, t) ->
-      file.contents = new Buffer file.contents.toString().replace(
-        /svg class="([^"]+)"/,
-        'svg class="$1 {%- if size is defined %} $1--{{ size }} {%- endif %}"'
+      file.contents = new Buffer(file.contents.toString()
+        .replace '<?xml version="1.0"?>\n', ''
+        .replace(
+          /svg class="([^"]+)"/,
+          'svg class="$1 {%- if size is defined %} $1--{{ size }} {%- endif %}"'
+        )
       )
     .pipe g.p.rename extname: ".svg.twig"
     .pipe g.dest "views"


renderPasses configuration option now going

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -766,6 +766,10 @@

 		# -----------------------------
 		# Other
+
+		# Render Passes
+		# How many times should we render documents that reference other documents?
+		renderPasses: 1

 		# Check Version
 		# Whether or not to check for newer versions of DocPad
@@ -2244,9 +2248,7 @@
 		# Prepare
 		docpad = @
 		locale = @getLocale()
-		{collection,templateData} = opts
-		total = 0
-		completed = 0
+		{collection,templateData,renderPasses} = opts

 		# Log
 		docpad.log 'debug', util.format(locale.renderingFiles, collection.length)
@@ -2260,46 +2262,51 @@
 				docpad.log 'debug', util.format(locale.renderedFiles, collection.length)
 				return next()

-		# Set progress indicator
-		opts.setProgressIndicator? -> ['renderFiles',completed,total]
-
 		# Render File
-		renderFile = (file,next) ->
+		renderFile = (fileToRender,next) ->
 			# Skip?
-			dynamic = file.get('dynamic')
-			render = file.get('render')
-			relativePath = file.get('relativePath')
+			dynamic = fileToRender.get('dynamic')
+			render = fileToRender.get('render')
+			relativePath = fileToRender.get('relativePath')

 			# Render
-			if dynamic or (render? and !render) or !relativePath or file.render? is false
+			if dynamic or (render? and !render) or !relativePath or fileToRender.render? is false
 				next()
 			else
-				file.render({templateData},next)
-			return file
+				fileToRender.render({templateData},next)
+			return fileToRender

 		# Render Collection
-		renderCollection = (collection) ->
-			total += collection.length
-			tasks.push (next) ->
-				# Cycle
-				subTasks = new balUtil.Group(next)
-				collection.forEach (file) -> subTasks.push (complete) -> renderFile file, (err) ->
-					++completed
-					complete(err)
-
-				# Fire
-				subTasks.async()
-			return collection
-
-		# Prepare the collections
-		standaloneCollection = collection.findAll('referencesOthers':false)
-
-		# Render the collections
-		renderCollection standaloneCollection
-		tasks.push ->
-			renderCollection renderCollection collection.findAll('referencesOthers':true)
-
-		# Start rendering
+		renderCollection = (collectionToRender,_opts={},next) ->
+			# Prepare
+			_opts.total ?= collectionToRender.length
+			_opts.renderPass ?= 1
+			_opts.offset ?= 0
+			subTasks = new balUtil.Group(next)
+			opts.setProgressIndicator? -> ['renderFiles'+(if _opts.renderPass > 1 then " (pass #{_opts.renderPass})" else ''),_opts.offset+subTasks.completed,_opts.total]
+
+			# Cycle
+			collectionToRender.forEach (file) ->  subTasks.push (complete) ->  renderFile(file,complete)
+
+			# Fire
+			subTasks.async()
+			return collectionToRender
+
+		# Queue the initial render
+		initialCollection = collection.findAll('referencesOthers':false)
+		subsequentCollection = null
+		tasks.push (complete) ->
+			renderCollection initialCollection, null, (err) ->
+				return complete(err)  if err
+				subsequentCollection = collection.findAll('referencesOthers':true)
+				renderCollection(subsequentCollection, {offset:collection.length-subsequentCollection.length, total:collection.length}, complete)
+
+		# Queue the subsequent renders
+		if renderPasses > 1
+			balUtil.each [2..renderPasses], (renderPass) ->  tasks.push (complete) ->
+				renderCollection(subsequentCollection, {renderPass}, complete)
+
+		# Fire the queue
 		docpad.emitSync 'renderBefore', {collection,templateData}, (err) =>
 			return next(err)  if err
 			tasks.sync()
@@ -2534,15 +2541,16 @@
 		# Prepare
 		[opts,next] = balUtil.extractOptsAndCallback(opts,next)
 		docpad = @
-		templateData = opts.templateData or @getTemplateData()
-		collection = opts.collection or @getDatabase()
-		setProgressIndicator = opts.setProgressIndicator or null
+		opts.templateData or= @getTemplateData()
+		opts.collection or= @getDatabase()
+		opts.renderPasses or= @getConfig().renderPasses
+		opts.setProgressIndicator or= null

 		# Contextualize the datbaase, perform two render passes, and perform a write
 		balUtil.flow(
 			object: docpad
 			action: 'contextualizeFiles renderFiles writeFiles'
-			args: [{collection,templateData,setProgressIndicator}]
+			args: [opts]
 			next: (err) ->
 				return next(err)
 		)


fix code indentation - closes #2454

fix code indentation - closes #2454

--- packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee
+++ packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee
@@ -51,15 +51,15 @@
 				timer: 1000
 				showConfirmButton: false

-				# Check if the upload message for this file is currently loaded
-				msg = ChatMessage.findOne { file: { _id: self._id } }
-				RocketChat.models.Uploads.remove self._id, () ->
-					if msg
-						chatMessages[Session.get('openedRoom')].deleteMsg(msg);
-					else
-						Meteor.call 'deleteFileMessage', self._id, (error, result) ->
-							if error
-								return Errors.throw error.reason
+			# Check if the upload message for this file is currently loaded
+			msg = ChatMessage.findOne { file: { _id: self._id } }
+			RocketChat.models.Uploads.remove self._id, () ->
+				if msg
+					chatMessages[Session.get('openedRoom')].deleteMsg(msg);
+				else
+					Meteor.call 'deleteFileMessage', self._id, (error, result) ->
+						if error
+							return Errors.throw error.reason

 	'scroll .content': _.throttle (e, t) ->
 		if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight


added #iceTotal to Bandango.Item and refactored #totalPrice

--- app/assets/javascripts/models/concerns/item/item_base_mixin.js.coffee
+++ app/assets/javascripts/models/concerns/item/item_base_mixin.js.coffee
@@ -9,9 +9,16 @@
     @get("base") * (@get("ivaTarifa") / 100)
   ).property("base", "iva", "ivaTarifa")

+  iceTotal: (->
+    return 0 unless @get("ice")
+    @get("base") * (@get("iceTarifa") / 100)
+  ).property("base", "ice", "iceTarifa")
+
   totalPrice: (->
+    price = @get("base")
     if @get("iva")
-      @get("base") + @get("ivaTwelve")
-    else
-      @get("base")
+      price += @get("ivaTwelve")
+    if @get("ice")
+      price += @get("iceTotal")
+    price
   ).property("base", "iva", "ivaTarifa", "ice", "iceTarifa")


Added to IdeaViewModel unit tests

Added to IdeaViewModel unit tests

--- spec/javascripts/models/idea_view_model.js.coffee
+++ spec/javascripts/models/idea_view_model.js.coffee
@@ -15,8 +15,38 @@

   describe "#edit", ->
     beforeEach ->
+      spyOn(@subject, "toggleEditing")
       @subject.edit()

     it "should have new title as title",             -> expect(@subject.newTitle()).toBe(@subject.title())
     it "should have new description as description", -> expect(@subject.newDescription()).toBe(@subject.description())
-    # it "should have toggled to edit mode",           -> expect(@form_vis).toBe(true)+    it "should have toggled to edit mode",           -> expect(@subject.toggleEditing).toHaveBeenCalledWith("content", "form")
+
+  describe "#cancel", ->
+    beforeEach ->
+      spyOn(@subject, "toggleEditing")
+      @subject.cancel()
+
+    it "should have toggled to content mode", -> expect(@subject.toggleEditing).toHaveBeenCalledWith("form", "content")
+
+  describe "#save", ->
+    beforeEach ->
+      spyOn(@subject, "toggleEditing")
+      spyOn(@subject, "update")
+      @subject.save()
+
+    it "should have a title as new title",             -> expect(@subject.title()).toBe(@subject.newTitle())
+    it "should have a description as new description", -> expect(@subject.description()).toBe(@subject.newDescription())
+    it "should have updated",                          -> expect(@subject.update).toHaveBeenCalled()
+    it "should have toggled to content mode",          -> expect(@subject.toggleEditing).toHaveBeenCalledWith("form", "content")
+
+  describe "#toggleEditing", ->
+    beforeEach ->
+      spyOn($.fn, "fadeOut")
+      spyOn($.fn, "fadeIn")
+      @subject.toggleEditing("from", "to")
+      @fadedOut = $.fn.fadeOut.callCount is 1
+
+    it "should toggle", ->
+      expect(@fadedOut).toEqual(true)
+      # fadeIn never triggered due to invalid $elem target


add a couple of ignores for errors that dont matter

--- vue/src/shared/helpers/boot.coffee
+++ vue/src/shared/helpers/boot.coffee
@@ -24,6 +24,8 @@
             "NotFoundError: Failed to execute 'removeChild' on 'Node'",
             "NotFoundError: The object can not be found here",
             "NotFoundError: Node was not found",
+            "null is not an object (evaluating 'r.addEventListener')",
+            "Cannot read property 'addEventListener' of null",
             "ResizeObserver loop limit exceeded",
             "MetaMask detected another web3",
             "AbortError: The operation was aborted",


Change timing of updating of buttons' color

--- views/2574.coffee
+++ views/2574.coffee
@@ -116,15 +116,15 @@
   btn.innerHTML = '取得中'
   btn.style.background = '#1F8A70'
   navigator.geolocation.getCurrentPosition (position) ->
-    btn = document.getElementById('locate-button')
-    btn.innerHTML = '現在地'
-    btn.style.background = '#ee8a65'
     coords = [position.coords.longitude, position.coords.latitude]
     target = new mapboxgl.LngLat.convert(coords).wrap().toArray()
     map.flyTo {
       center: target
       zoom: 14
     }
+    btn = document.getElementById('locate-button')
+    btn.innerHTML = '現在地'
+    btn.style.background = '#ee8a65'

 naviEaseLocate = ->
   return if not navigator.geolocation


ask for name

ask for name

--- views/game.coffee
+++ views/game.coffee
@@ -35,6 +35,7 @@
     @game.world.bringToTop padImg
     @setupFullscreen()
     @player_name = 'Player' + Math.floor(Math.random()*1000)
+    @player_name = prompt("Please enter your name.", @playerName ) || @playerName
     @player = @createPlayerGfx(@player_name, 96, 96)
     @game.camera.follow(@player)
     @websocket.send 'player_create',


save web hook url

save web hook url

--- server.coffee
+++ server.coffee
@@ -60,7 +60,18 @@
           if err
             callback err
             return
-          callback undefined, repo
+          hook = {
+            "name": "web",
+            "active": true,
+            "config": {
+              "url": "http://gitcommunities.com/webhook"
+            }
+          }
+          ghAdmin.post "/repos/communities/#{repo.name}/hooks", hook, (err) ->
+            if err
+              callback err
+              return
+            callback undefined, repo

 createGitRepo = (repo, callback) ->


up to date

up to date

--- server.coffee
+++ server.coffee
@@ -75,8 +75,8 @@
 app.post('/startGame', (req,res)->
   console.log("Start Game!")
   address = '/startGame'
-  client.send(address, req.body.levelSelect)
-  log(address, req.body.levelSelect)
+  client.send(address)
+  log(address)
   res.redirect('/')
 )
 app.post('/endGame', (req,res)->


server: Eliminate error when requesting /2013/05/27/

--- server.coffee
+++ server.coffee
@@ -326,7 +326,7 @@
     logMessage "done (#{elapsedTime} ms)", noTime:true

   # Single entry (e.g. /2013/05/26/how_i_learned_javascript)
-  app.get /^\/(\d{4})\/(\d{2})\/(\d+)(-\d+)?\/(.*)$/, (req, res) ->
+  app.get /^\/(\d{4})\/(\d{2})\/(\d+)(-\d+)?\/(.*)$/, (req, res, next) ->
     year  = req.params[0]
     month = req.params[1]
     date  = req.params[2]
@@ -338,11 +338,13 @@
       if not exists
         # Find static file that matches URL
         staticPath = "#{basedir}/#{year}/#{month}/#{slug}"
-        fs.exists staticPath, (exists) ->
-          if exists
+        fs.stat staticPath, (err, stats) ->
+          if err
+            respondWithNotFound res
+          else if stats.isDirectory()
+            next()  # Will match the next route
+          else
             res.sendfile staticPath
-          else
-            respondWithNotFound res
         return
       fs.readFile filepath, {encoding:'utf8'}, (err, markdown) ->
         if err
@@ -363,6 +365,12 @@
           res.setHeader 'Content-Type', 'text/html; charset=utf-8'
           res.send html

+  # Redirect /2013/05/27/ to /2013/05/ (after matching against the previous route)
+  app.get /^\/(\d{4})\/(\d{2})\/[\d-]+\/?/, (req, res) ->
+    year  = req.params[0]
+    month = req.params[1]
+    res.redirect "/#{year}/#{month}/"
+
   # List of years
   app.get '/archives', (req, res) ->
     years = listYears()
@@ -440,12 +448,6 @@
         return
       res.setHeader 'Content-Type', 'text/html; charset=utf-8'
       res.send html
-
-  # Redirect /2013/05/27/ to /2013/05/
-  app.get /^\/(\d{4})\/(\d{2})\/[\d-]+\/?/, (req, res) ->
-    year  = req.params[0]
-    month = req.params[1]
-    res.redirect "/#{year}/#{month}/"

   # Recent entries
   app.get '/recents', (req, res) ->


Wire up Comic.archive to the feed

Wire up Comic.archive to the feed

--- server.coffee
+++ server.coffee
@@ -24,10 +24,13 @@
 app.get '/feed.xml', (request, response) ->
   # TODO: Abstract out /archive/* into Comic so I can call it with a callback,
   #       reieve a list and just render the feed view
-  response.set 'Content-Type', 'application/rss+xml'
-  return response.render 'feed',
-    layout: null
-    archive: [{time: 123}]
+  Comic.archive 'latest', (err, archive) ->
+    return response.status(500).send "Sorry, my programming broke building the feed" if err
+
+    response.set 'Content-Type', 'application/rss+xml'
+    return response.render 'feed',
+      layout: null
+      archive: archive.archive

 app.get '/archive/:start?', (request, response) ->


Modify server.coffee to catch exeption raised from websocket.io#write

--- server.coffee
+++ server.coffee
@@ -41,26 +41,29 @@

 ws.attach server
   .on 'connection', (sock)->
-    console.log 'CONNECTED'
+    console.log 'CONNECTED: WS'

     sock
       .on 'message', (data)->
         console.log 'REQUEST: ' + data
         target.write data
       .on 'close', (data)->
-        console.log 'DISCONNECTED'
+        console.log 'DISCONNECTED: WS'
         target.end()
       .on 'error', (error)->
-        console.error 'ERROR: ' + error
+        console.error 'ERROR: WS: ' + error

     target = new net.Socket()
       .on 'data', (data)->
         #console.log 'RESPONSE: ' + data
-        sock.write data
+        try
+          sock.write data
+        catch e
+          console.error 'ERROR: WS: ' + e
       .on 'close', (data)->
-        console.log 'CLOSED'
+        console.log 'CLOSED: TRACKER SERVER'
         sock.end()
       .on 'error', (error)->
-        console.error 'ERROR: ' + error
+        console.error 'ERROR: TRACKER SERVER: ' + error
       .connect TARGET_PORT, TARGET_HOST, ->
-        console.log 'OPEN'
+        console.log 'OPENED: TRACKER SERVER'


Change some things to .peek() instead of .get()

Fix problem with capture/neutralize.

--- server.coffee
+++ server.coffee
@@ -25,12 +25,12 @@
 #========== Client calls ==========
 # Add a beacon (during setup phase)
 exports.client_addMarker = (client, location) ->
-	if Db.shared.get('gameState') isnt 0
+	if Db.shared.peek('gameState') isnt 0
 		log Plugin.userName(client), ' (id=', client, ') tried to add a marker while game is not in setup phase!'
 	else
 		log 'Adding marker: lat=', location.lat, ', lng=', location.lng
 		nextNumber = 0
-		while Db.shared.get('game', 'beacons', nextNumber)?
+		while Db.shared.peek('game', 'beacons', nextNumber)?
 			nextNumber++

 		Db.shared.set 'game', 'beacons', nextNumber, {location: location}
@@ -42,11 +42,11 @@

 exports.client_deleteBeacon = (client, location) ->
 	#Finding the right beacon
-	if Db.shared.get('gameState') isnt 0
+	if Db.shared.peek('gameState') isnt 0
 		log Plugin.userName(client), ' (id=', client, ') tried to delete a beacon while game is not in setup phase!'
 	else
 		Db.shared.iterate 'game', 'beacons', (beacon) !->
-			if beacon.get('location', 'lat') == location.lat and beacon.get('location', 'lng') == location.lng
+			if beacon.peek('location', 'lat') == location.lat and beacon.peek('location', 'lng') == location.lng
 				log 'Deleted beacon: key='+beacon.key()+', lat=', location.lat, ', lng=', location.lng
 				Db.shared.remove 'game', 'beacons', beacon.key()

@@ -73,7 +73,7 @@
 exports.client_startGame = ->
 	setTimer()
 	userIds = Plugin.userIds()
-	teams = Db.shared.get('game','numberOfTeams')
+	teams = Db.shared.peek('game','numberOfTeams')
 	team = 0
 	while(userIds.length > 0)
 		randomNumber = Math.floor(Math.random() * userIds.length)
@@ -94,7 +94,7 @@

 # Checkin location for capturing a beacon
 exports.client_checkinLocation = (client, location) ->
-	if Db.shared.get 'gameState' is not 1
+	if Db.shared.peek 'gameState' is not 1
 		log 'Client ', client, ' (', Plugin.userName(client), ') tried to capture beacon while game is not running!'
 	else
 		log 'checkinLocation() client: ', client, ', location: lat=', location.lat, ', lng=', location.lng
@@ -102,7 +102,7 @@
 		beacons = Db.shared.ref('game', 'beacons')
 		beaconRadius = Db.shared.peek('game', 'beaconRadius')
 		beacons.iterate (beacon) ->
-			current = beacon.get('inRange', client)?
+			current = beacon.peek('inRange', client)?
 			beaconDistance = distance(location.lat, location.lng, beacon.peek('location', 'lat'), beacon.peek('location', 'lng'))
 			newStatus = beaconDistance < beaconRadius
 			if newStatus != current
@@ -111,7 +111,7 @@
 				Timer.cancel 'onNeutralize', {beacon: beacon.key(), players: getInrangePlayers(beacon.key())}
 				removed = undefined
 				if newStatus
-					owner = beacon.get 'owner'
+					owner = beacon.peek 'owner'
 					log 'Added to inRange: id=', client, ', name=', Plugin.userName(client)
 					beacon.set 'inRange', client, 'true'
 					# Start takeover
@@ -120,12 +120,12 @@
 					# clean takeover
 					beacon.remove 'inRange', client
 					removed = client
-				log 'removed=', removed
+				#log 'removed=', removed
 				# ========== Handle changes for inRange players ==========
 				# Determine members per team
 				teamMembers = (0 for team in [0..5])
 				beacon.iterate 'inRange', (player) !->
-					if (not removed?) or player.key() isnt removed
+					if parseInt(player.key()) != parseInt(removed)
 						team = getTeamOfUser(player.key())
 						teamMembers[team] = teamMembers[team]+1
 				#log 'teamMembers count: ', teamMembers
@@ -141,6 +141,7 @@
 					else if teamMembers[team] == max
 						competing.push team
 				# Update percentage taken for current time
+				log 'competing=', competing
 				updateBeaconPercentage(beacon)

 				# Check if there should be progress
@@ -149,7 +150,7 @@
 					activeTeam = competing[0]
 					if activeTeam != owner
 						beacon.set 'nextOwner', activeTeam
-						percentage = beacon.get 'percentage'
+						percentage = beacon.peek 'percentage'
 						if owner == -1
 							# Capturing
 							log 'Team ', activeTeam, ' is capturing beacon ', beacon.key()
@@ -169,7 +170,7 @@
 					else
 						log 'activeteam already has the beacon, ', activeTeam, '=', owner

-				else if competing.length > 1 or competing.length is 0
+				else
 					# No progess, stand-off
 					beacon.set 'actionStarted', new Date()/1000
 					beacon.set 'action', 'none'
@@ -180,9 +181,9 @@

 # Update the takeover percentage of a beacon depening on current action and the passed time
 updateBeaconPercentage = (beacon) !->
-	currentPercentage = beacon.get 'percentage'
-	action = beacon.get 'action'
-	actionStarted = beacon.get 'actionStarted'
+	currentPercentage = beacon.peek 'percentage'
+	action = beacon.peek 'action'
+	actionStarted = beacon.peek 'actionStarted'
 	if action is 'capture'
 		time = (new Date()/1000)-actionStarted
 		newPercentage = currentPercentage+(time/30*100)
@@ -198,7 +199,7 @@
 # Called by the beacon capture timer
 exports.onCapture = (args) !->
 	beacon = Db.shared.ref 'game', 'beacons', args.beacon
-	nextOwner = beacon.get('nextOwner')
+	nextOwner = beacon.peek('nextOwner')
 	log 'Team ', nextOwner, ' has captured beacon ', beacon.key(), ', players: ', args.players
 	beacon.set 'percentage', 100
 	beacon.set 'owner', nextOwner
@@ -221,7 +222,7 @@
 	# Handle points and statistics
 	client = args.players.split(', ')[0]
 	log "[onCapture()] " + client
-	beaconValue = beacon.get('captureValue')
+	beaconValue = beacon.peek('captureValue')
 	modifyScore client, beaconValue
 	for player in args.players.split(', ')
 		Db.shared.modify 'game', 'teams', getTeamOfUser(player), 'users', player, 'captured', (v) -> v+1
@@ -232,7 +233,7 @@
 # Called by the beacon neutralize timer
 exports.onNeutralize = (args) !->
 	beacon = Db.shared.ref 'game', 'beacons', args.beacon
-	neutralizer = beacon.get('nextOwner')
+	neutralizer = beacon.peek('nextOwner')
 	log 'Team ', neutralizer, ' has neutralized beacon ', beacon.key(), ', players: ', args.players
 	beacon.set 'percentage', 0
 	beacon.set 'owner', -1
@@ -244,7 +245,7 @@

 	# Handle capturing
 	updateBeaconPercentage(beacon)
-	percentage = beacon.get 'percentage'
+	percentage = beacon.peek 'percentage'
 	log 'Team ', neutralizer, ' is capturing beacon ', beacon.key(), ' (after neutralize)'

 	beacon.set 'action', 'capture'
@@ -298,12 +299,12 @@

 #game timer
 setTimer = !->
-	if Db.shared.get('game', 'roundTimeUnit') is 'Months'
-		seconds = Db.shared.get('game', 'roundTimeNumber')*2592000
-	else if Db.shared.get('game', 'roundTimeUnit') is 'Days'
-		seconds = Db.shared.get('game', 'roundTimeNumber')*86400
-	else if Db.shared.get('game', 'roundTimeUnit') is 'Hours'
-		seconds = Db.shared.get('game', 'roundTimeNumber')*3600
+	if Db.shared.peek('game', 'roundTimeUnit') is 'Months'
+		seconds = Db.shared.peek('game', 'roundTimeNumber')*2592000
+	else if Db.shared.peek('game', 'roundTimeUnit') is 'Days'
+		seconds = Db.shared.peek('game', 'roundTimeNumber')*86400
+	else if Db.shared.peek('game', 'roundTimeUnit') is 'Hours'
+		seconds = Db.shared.peek('game', 'roundTimeNumber')*3600
 	end = Plugin.time()+seconds #in seconds
 	Db.shared.set 'game', 'endTime', end
 	Timer.cancel
@@ -336,9 +337,9 @@
 	maxScore = -1
 	newLead = false
 	Db.shared.iterate 'game', 'teams', (team) !->
-		if maxScore < team.get('teamScore')
+		if maxScore < team.peek('teamScore')
 			teamMax = team.key()
-			maxScore = team.get('teamScore')
+			maxScore = team.peek('teamScore')

 	log "TeamMax: " + teamMax + " maxScore: " + maxScore + " (1)"
 	# modify user and team scores
@@ -348,9 +349,9 @@
 	Db.shared.modify 'game', 'teams', teamClient, 'teamScore', (v) -> v + points

 	Db.shared.iterate 'game', 'teams', (team) !->
-		if maxScore < team.get('teamScore') and teamMax isnt team.key()
+		if maxScore < team.peek('teamScore') and teamMax isnt team.key()
 			teamMax = team.key()
-			maxScore = team.get('teamScore')
+			maxScore = team.peek('teamScore')
 			newLead = true

 	log "TeamMax: " + teamMax + " maxScore: " + maxScore + " newLead: " + newLead + " (2)"


Move some comments out of loadConfig.coffee

--- src/loadConfig.coffee
+++ src/loadConfig.coffee
@@ -1,65 +1,6 @@
 # Functions for loading a transform's configuration.
 #
-# ## Finding package.json
-#
-# The default behavior when looking for package.json is to start from the CWD, walk up the
-# directory tree until package.json is found, then load the configuration from that file.
-#
-# If `options.fromSourceFileDir` is set, then instead the behavior is to start at the source file,
-# walk up the directory tree until package.json is found, and try to load configration from that
-# file.  If configuration is not found, then we will continue to walk up the tree until we find
-# another package.json and will try again, recursively.  This is similar to how
-# [browserify-shim](https://github.com/thlorenz/browserify-shim) looks for configuration, and was
-# the default in v1.x.  See
-# [this issue](https://github.com/benbria/browserify-transform-tools/issues/1) for further details.
-#
-# ## Loading configuration
-#
-# Once package.json is found, we will look for a key in package.json with same name as your
-# transform.  Suppose you write a transform called "soupify".  In your transform, you'd do
-# something like:
-#
-#     browserifyTransformTools.loadTransformConfig "soupify",
-#         "/Users/jwalton/project/foo.js", (err, configData) ->
-#             ....
-#
-# This will find package.json and look for a key called "soupify".  Once the "soupify" key is
-# found, if the value of the key is an object, the loaded configuration will be that object.  If
-# the value of the key is a string, this will load the JSON or js file referenced by the string,
-# and return its contents instead.
-#
-# ## Common Configuration
-#
-# All modules that rely on browserify-transform-tools can contain a configuration item called
-# 'appliesTo'.  This will configure what files the transform will be applied to.  For example,
-# users of the "soupify" transform we defined above might add the following to their package.json:
-#
-#     "soupify": {
-#         "appliesTo": {"includeExtensions": [".js"]}
-#     }
-#
-# `appliesTo` should include exactly one of the following:
-#
-# * `appliesTo.includeExtensions` - A list of extensions to process.  If this option is not
-#   specified, then all extensions will be processed.  If this option is specified, then
-#   any file with an extension not in this list will skipped.
-#
-# * `appliesTo.excludeExtensions` - A list of extensions which will not be processed.  e.g.
-#   "['.coffee', '.jade']"
-#
-# * `appliesTo.files` - A list of paths, relative to the configuration file, of files which
-#   should be transformed.  Only these files will be transformed.  This is handy for transforms
-#   like [includify](https://github.com/benbria/includify) which you typically only apply to one
-#   or two files in your project; defining this will typically speed up your bundle time, since
-#   you no longer are running the transform on all the files in your project.
-#
-# * `appliesTo.regex` - A regex or a list of regexes.  If any regex matches the full path of the
-#   file, then the file will be processed, otherwise not.
-#
-# The `appliesTo` key will be stripped from the configuration before being passed to your transform,
-# although it is available in the `configData` if you need it for some reason.  Note that
-# `appliesTo` will override the `includeExtensions` and `excludeExtensions` provided to any of the
-# `make*Transform()` functions.
+# More details are available in [the transform configuration documentation](https://github.com/benbria/browserify-transform-tools/wiki/Transform-Configuration).
 #
 # ## Config Object
 #
@@ -75,6 +16,34 @@
 #   data is cached using the location of the package.json file as the key.  If this value is true,
 #   it means that data was loaded from the cache.
 # * `configData.appliesTo` - The `appliesTo` from the configuration, if one was present.
+#
+# ## Common Configuration
+#
+# All modules that rely on browserify-transform-tools can contain a configuration item called
+# 'appliesTo'.  This will configure what files the transform will be applied to.
+#
+# `appliesTo` should include exactly one of the following:
+#
+# * `appliesTo.includeExtensions` - A list of extensions to process.  If this option is not
+#   specified, then all extensions will be processed.  If this option is specified, then
+#   any file with an extension not in this list will skipped.
+#
+# * `appliesTo.excludeExtensions` - A list of extensions which will not be processed.  e.g.
+#   "['.coffee', '.jade']"
+#
+# * `appliesTo.files` - A list of paths, relative to the configuration file, of files which
+#   should be transformed.  Only these files will be transformed.  This is handy for transforms
+#   like [includify](https://github.com/benbria/includify) which you typically only apply to one
+#   or two files in your project; defining this will typically speed up your bundle time, since
+#   you no longer are running the transform on all the files in your project.
+#
+# * `appliesTo.regex` - A regex or a list of regexes.  If any regex matches the full path of the
+#   file, then the file will be processed, otherwise not.
+#
+# The `appliesTo` key will be stripped from the configuration before being passed to your transform,
+# although it is available in the `configData` if you need it for some reason.  Note that
+# `appliesTo` will override the `includeExtensions` and `excludeExtensions` provided to any of the
+# `make*Transform()` functions.
 #

 path    = require 'path'


Inject v1 flag into view model

Inject v1 flag into view model

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -144,6 +144,7 @@
 		timer = new metrics.Timer("project-list")
 		user_id = AuthenticationController.getLoggedInUserId(req)
 		currentUser = AuthenticationController.getSessionUser(req)
+		isV1 = Settings.brandPrefix == "ol-"
 		async.parallel {
 			tags: (cb)->
 				TagsHandler.getAllTags user_id, cb
@@ -152,7 +153,7 @@
 			projects: (cb)->
 				ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref tokens', cb
 			olProjects: (cb) ->
-				if Settings.brandPrefix == "ol-"
+				if isV1
 					OlProjectGetter.findAllUsersProjects user_id, cb
 				else
 					cb()
@@ -181,6 +182,7 @@
 						notifications: notifications or []
 						user: user
 						hasSubscription: results.hasSubscription[0]
+						isV1: isV1
 					}

 					if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?


changed api-key to read-only-api-key for angolia

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -145,7 +145,7 @@

 					if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
 						viewModel.showUserDetailsArea = true
-						viewModel.algolia_api_key = Settings.algolia.api_key
+						viewModel.algolia_api_key = Settings.algolia.read_only_api_key
 						viewModel.algolia_app_id = Settings.algolia.app_id
 					else
 						viewModel.showUserDetailsArea = false


Ensure that jQuery UI is loaded on top of the correct jQuery

Additionally, I was seeing an issue where Bokeh.Backbone.$.ui was empty,
setting it here to ensure that it's been set.

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -16,9 +16,11 @@

 # binding the libs that bokeh uses so others can reference them
 Bokeh._                 = require("underscore")
-Bokeh.$                 = require("jquery")
+Bokeh.$ = $ = jQuery    = require("jquery")
+require "jquery-ui"
 Bokeh.Backbone          = require("backbone")
 Bokeh.Backbone.$        = Bokeh.$
+Bokeh.Backbone.$.ui     = jQuery.ui

 # common
 Bokeh.Collections       = require("./common/base").Collections


Remove spare newlines

Remove spare newlines

--- review_front/app/router.coffee
+++ review_front/app/router.coffee
@@ -14,6 +14,4 @@
   @resource 'admin', ->
     @route 'users'

-
 `export default Router`
-


globalize avg

globalize avg

--- atmospheric/002-accumulated-heat/accumulator.coffee
+++ atmospheric/002-accumulated-heat/accumulator.coffee
@@ -33,6 +33,12 @@
         for j in [0..256]
             arr.push 0
         data.push arr
+    avg = []
+    for i in [0..256]
+        arr = []
+        for j in [0..256]
+            arr.push 0
+        avg.push arr

     input = (lng, lat, time, light1, light2) ->
         ltime = wau.fromAU_T(time)
@@ -47,12 +53,9 @@
             for j in [0..256]
                 energyIn = sc * input(lng(i), lat(j), time, light1, light2) * tao
                 data[i][j] = data[i][j] + energyIn
-        avg = []
         for i in [0..256]
-            arr = []
             for j in [0..256]
                 arr[j] = data[i][j] / lday
-            avg.push arr

         avg


Fix issue #19

Fix issue #19

--- src/ubret/statistics.coffee
+++ src/ubret/statistics.coffee
@@ -41,7 +41,11 @@
       .data(@statistics)
       .enter().append('li')
       .attr('data-stat', (d) -> d[0])
-      .html((d) => "#{@formatKey(d[0])}: #{d3.format(@opts.displayFormat)(d[1])}")
+      .html((d) =>
+        if isNaN(d[1]) or d[1] is Infinity
+          "#{@formatKey(d[0])}: &nbsp"
+        else
+          "#{@formatKey(d[0])}: #{d3.format(@opts.displayFormat)(d[1])}")

   # Statistics
   mean: =>


Change keymap selector from `.workspace` to `.editor`

--- keymaps/latex.cson
+++ keymaps/latex.cson
@@ -1,11 +1,2 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
+'.editor':
   'ctrl-alt-b': 'latex:build'


Flatten attributes that are arrays

Flatten attributes that are arrays

--- backbone.typeahead.coffee
+++ backbone.typeahead.coffee
@@ -8,7 +8,7 @@
   _tokenizeModel: (model) ->
     throw new Error('Missing typeaheadAttributes value') unless @typeaheadAttributes?

-    _.uniq(@_tokenize(_.map(@typeaheadAttributes, (att) -> model.get(att)).join(' ')))
+    _.uniq(@_tokenize(_.flatten(_.map(@typeaheadAttributes, (att) -> model.get(att))).join(' ')))

   _addToIndex: (models) ->
     if _.isArray(models) then models.slice() else [models]