:new: Wire up indie registry into main constructor

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -5,6 +5,7 @@
 EditorRegistry = require './editor-registry'
 EditorLinter = require './editor-linter'
 LinterRegistry = require './linter-registry'
+IndieRegistry = require './indie-registry'
 Helpers = require './helpers'
 Commands = require './commands'

@@ -19,13 +20,19 @@
     # Private Stuff
     @emitter = new Emitter
     @linters = new LinterRegistry
+    @indieLinters = new IndieRegistry()
     @editors = new EditorRegistry
     @messages = new MessageRegistry()
     @views = new LinterViews(state.scope, @editors)
     @commands = new Commands(this)

-    @subscriptions = new CompositeDisposable(@views, @editors, @linters, @messages, @commands)
+    @subscriptions = new CompositeDisposable(@views, @editors, @linters, @messages, @commands, @indieLinters)

+    @indieLinters.observe (indieLinter) =>
+      indieLinter.onDidDestroy =>
+        @messages.deleteMessages(indieLinter)
+    @indieLinters.onDidUpdateMessages (param) => # param = {linter, messages}
+      @messages.set(param)
     @linters.onDidUpdateMessages ({linter, messages, editor}) =>
       @messages.set({linter, messages, editorLinter: @editors.ofTextEditor(editor)})
     @messages.onDidUpdateMessages (messages) =>


[logger] Removed levels code ( for now )

--- lib/logger.coffee
+++ lib/logger.coffee
@@ -23,28 +23,8 @@
   help: 'cyan'
 }

-# TODO - consider using something like https://github.com/quirkey/node-logger
-levels_enabled = {}
-
-module.exports.set_levels = (log_env) ->
-  enable_all_levels = (x) -> Object.keys(levels).forEach (level) -> levels_enabled[level] = x
-  if log_env is 'development'
-    enable_all_levels true
-  else if log_env is 'test'
-    enable_all_levels false
-  else if log_enf is 'production'
-    levels_enabled =
-      info: false
-      data: false
-      warn: true
-      error: true
-      exec: false
-      help: false
-
 Object.keys(levels).forEach (level) ->
   module.exports[level] = () ->
-    if levels_enabled[level]
-      args = [].slice.call(arguments)
-      console.log.apply this, [colors[levels[level]](level + ':')].concat(args)
+    args = [].slice.call(arguments)
+    console.log.apply this, [colors[levels[level]](level + ':')].concat(args)

-    module.exports.set_levels(process.env.NODE_ENV || 'development')


More useful if not linewise for MoveTo(Previous/Next)FoldStart

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -1040,7 +1040,7 @@
 class MoveToPreviousFoldStart extends Motion
   @extend()
   @description: "Move to previous fold start"
-  linewise: true
+  linewise: false
   which: 'start'
   direction: 'prev'


#225 cursor is state, point is not so using point over cursor make behavior more predictable.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -204,21 +204,24 @@
   @description: "Move cursor up to **edge** char at same-column"

   moveCursor: (cursor) ->
-    @countTimes =>
-      @setScreenPositionSafely(cursor, @getPoint(cursor))
-
-  getPoint: (cursor) ->
-    column = cursor.getScreenColumn()
-    for row in @getScanRows(cursor)
-      if @isMovablePoint(point = new Point(row, column))
+    point = cursor.getScreenPosition()
+    @countTimes ({stop}) =>
+      if (newPoint = @getPoint(point))
+        point = newPoint
+      else
+        stop()
+    @setScreenPositionSafely(cursor, point)
+
+  getPoint: (fromPoint) ->
+    for row in @getScanRows(fromPoint)
+      if @isMovablePoint(point = new Point(row, fromPoint.column))
         return point

-  getScanRows: (cursor) ->
-    cursorRow = cursor.getScreenRow()
+  getScanRows: ({row}) ->
     validRow = getValidVimScreenRow.bind(null, @editor)
     switch @direction
-      when 'up' then [validRow(cursorRow - 1)..0]
-      when 'down' then [validRow(cursorRow + 1)..getVimLastScreenRow(@editor)]
+      when 'up' then [validRow(row - 1)..0]
+      when 'down' then [validRow(row + 1)..getVimLastScreenRow(@editor)]

   isMovablePoint: (point) ->
     if @isStoppablePoint(point)
@@ -489,7 +492,6 @@
   defaultCount: 0
   getCount: -> super - 1

-# keymap: gg
 class MoveToFirstLine extends Motion
   @extend()
   linewise: true


:art: hover icon and emoji should be consistent.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -568,7 +568,7 @@
   complete: false
   requireInput: true
   hoverText: ":round_pushpin:`"
-  hoverIcon: ":move-to-mark:"
+  hoverIcon: ":move-to-mark:`"
   # hoverChar: '`'

   constructor: ->


:bug: ? where this behavior come from? comment out.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -358,7 +358,7 @@
   moveCursor: (cursor) ->
     cursor.setBufferPosition([@getDestinationRow(@getCount()), Infinity])
     cursor.moveToFirstCharacterOfLine()
-    cursor.moveToEndOfLine() if cursor.getBufferColumn() is 0
+    # cursor.moveToEndOfLine() if cursor.getBufferColumn() is 0

 # keymap: gg
 class MoveToStartOfFile extends MoveToLineBase


renamed to OAuth1ResponseParser

renamed to OAuth1ResponseParser

--- lib/oauth1.coffee
+++ lib/oauth1.coffee
@@ -27,7 +27,7 @@
 dbapps = require './db_apps'
 config = require './config'

-OAuth10aResponseParser = require './oauth1-response-parser'
+OAuth1ResponseParser = require './oauth1-response-parser'

 ksort = (w) ->
 	r = {}
@@ -92,7 +92,7 @@
 		# do request to request_token
 		request options, (e, r, body) ->
 			return callback(e) if e
-			responseParser = new OAuth10aResponseParser(r, body, request_token.format)
+			responseParser = new OAuth1ResponseParser(r, body, request_token.format)
 			return callback(responseParser.error) if responseParser.error

 			dbstates.setToken state.id, responseParser.oauth_token_secret, (e, r) ->


if an assigned job has not been ackowledged in the past 5min re-dispatch it

--- lib/server.coffee
+++ lib/server.coffee
@@ -150,6 +150,10 @@
 								if act._otherhost?
 									status_c["duplicates"] ||= 0
 									status_c["duplicates"] += Object.keys(act._otherhost).length
+								#remove from working if no update from status has been seen in the last 5min
+								if act._lastseen? and ~~((new Date).getTime() / 1000) - act._lastseen > 300
+									act._status = "tosend"
+									act._lastchange = ~~((new Date).getTime() / 1000)
 							when "fetching"
 								if getDocsCargo.length() is 0 and !getDocsCargo.running() and !resentdocs # nothing in the cargo.. but on the wue, re-queing ALL
 									resentdocs = true
@@ -535,6 +539,7 @@
 									currentjob._takenby = host
 									currentjob._status = "working"
 									currentjob._lastchange = ~~((new Date).getTime() / 1000)
+									currentjob._lastseen = ~~((new Date).getTime() / 1000)
 									delete currentjob._otherhost?[host]
 							else # create a ghosth job
 								console.log "creating ghost job",jobId,"for", host
@@ -543,8 +548,11 @@
 									_id : jobId,
 									_rev : jobRv,
 									_status : "working",
-									_lastchange : ~~((new Date).getTime() / 1000)
+									_lastchange : ~~((new Date).getTime() / 1000),
+									_lastseen = ~~((new Date).getTime() / 1000)
 								}
+						else
+							findinque(item)._lastseen = ~~((new Date).getTime() / 1000)

 					# Assigned jobs but not working
 					for item in assigned
@@ -560,6 +568,7 @@
 							else # kill the ghost job
 								workque = workque.filter (obj)->
 									return obj._id isnt item
+
 					resyncQueue() if sendsync
 				return
 			return r


Fix bug where picam will not be killed by stop.sh

Fixes #20

--- lib/server.coffee
+++ lib/server.coffee
@@ -2,7 +2,7 @@
 fs = require 'fs'
 url = require 'url'
 path = require 'path'
-spawn = require('child_process').spawn
+child_process = require 'child_process'

 PICAM_UID = 1000
 PICAM_CWD = path.normalize "#{__dirname}/.."
@@ -32,7 +32,7 @@
 startStreamProcess = ->
   console.log "spawning picam"
   # ionice: -c1 (realtime) -n0 (highest priority)
-  picam = spawn 'nice', [
+  picam = child_process.spawn 'nice', [
     '-n', '-20', 'ionice', '-c1', '-n0', 'sudo', '-u', "##{PICAM_UID}",
     # picam command options
     PICAM_PATH, '--rtspout', '--alsadev', 'hw:1,0',
@@ -57,7 +57,13 @@
 process.on 'SIGINT', ->
   console.log "Got SIGINT. Sending SIGINT to picam"
   if DO_SPAWN_PICAM and picam?
-    picam.kill 'SIGINT'
+    child_process.exec 'pidof picam', (err, stdout, stderr) ->
+      if err
+        console.error "pidof command error: #{err}"
+        return
+      picamPid = parseInt(stdout, 10)
+      console.log "picam pid: #{picamPid}"
+      process.kill picamPid, 'SIGINT'
   else
     process.kill process.pid, 'SIGTERM'


rewriting writer

rewriting writer

--- lib/writer.coffee
+++ lib/writer.coffee
@@ -119,6 +119,7 @@
                 next()
           , (err) ->
             archive.finalize (written) ->
+                console.log "written", written, err
                 callback(written)


`Iterator::update()` fixes

`Iterator::update()` fixes

--- iterator.coffee
+++ iterator.coffee
@@ -55,7 +55,7 @@

 		# stop if no data found
 		if not isArray(each) and not (each instanceof List)
-			node.visible = false
+			@node.visible = false
 			return

 		# stop if nothing changed


Fixed Contact List in inbox

Fixed Contact List in inbox

--- areas/inbox/client/inbox.coffee
+++ areas/inbox/client/inbox.coffee
@@ -38,11 +38,11 @@

 UI.registerHelper "unread", (id)-> not Messages.findOne(id).read

-UI.registerHelper "cleanup", (timeStamp)->
-  clean(timeStamp)
+UI.registerHelper "cleanup", (timeStamp)-> clean(timeStamp)
+
+UI.registerHelper "contactExists", (id)-> userExists id

 Template.chat.helpers
-  contactExists: (contactId)-> userExists contactId
   contactSelected: ()-> Session.get("currentContact")?
   currentConversation: ()->
     pair = getPair()


only show discussion starters in user filter

--- resources/assets/coffee/react/beatmap-discussions/header.coffee
+++ resources/assets/coffee/react/beatmap-discussions/header.coffee
@@ -106,7 +106,7 @@
             className: "#{bn}__filter-group #{bn}__filter-group--stats"
             el BeatmapDiscussions.UserFilter,
               selectedUser: if @props.selectedUserId? then @props.users[@props.selectedUserId] else null
-              users: _.sortBy(_.pickBy(@props.users, (value) -> value.id?), 'username')
+              users: _.sortBy(@discussionUsers(), 'username')

             div
               className: "#{bn}__stats"
@@ -119,6 +119,11 @@
             el BeatmapBasicStats,
               beatmapset: @props.beatmapset
               beatmap: @props.currentBeatmap
+
+
+  discussionUsers: =>
+    userIds = _.uniq(_.map(_.values(@props.discussions), 'user_id'))
+    _.pickBy(@props.users, (value) -> _.includes(userIds, value.id))

   setFilter: (e) =>


Remove console.log

Remove console.log

--- assets/scripts/app/store.coffee
+++ assets/scripts/app/store.coffee
@@ -62,7 +62,6 @@
       { id: id, clientId: clientId }

   receive: (event, data) ->
-    console.log event, data
     [name, type] = event.split(':')

     mappings = @adapter.get('mappings')


Fix QueryView category ordering

Fix QueryView category ordering

--- cilantro/static/cilantro/scripts/coffeescript/views/queryviews.coffee
+++ cilantro/static/cilantro/scripts/coffeescript/views/queryviews.coffee
@@ -153,7 +153,7 @@
         render: ->
             @$el.empty()

-            for model in @collection.models
+            for model in @collection.sortBy((model) -> (category = model.get('category'))?.order)
                 if not model.get 'queryview' then continue
                 category = model.get('category')
                 categoryName = if category then category.name else 'Other'


Use createSpinner in question attachment thumbnail


running all tests at once breaks browserstack

--- tests/karma/browserstack.conf.coffee
+++ tests/karma/browserstack.conf.coffee
@@ -14,8 +14,8 @@
   'windows-7-ie-9'    : ['Windows', '7', 'ie', '9.0']
   'windows-7-ie-8'    : ['Windows', '7', 'ie', '8.0']

-  'iphone'  : ['ios', '7.0', 'iPhone 5S']
-  'ipad'    : ['ios', '7.0', 'iPad 3rd (7.0)']
+  #'iphone'  : ['ios', '7.0', 'iPhone 5S']
+  #'ipad'    : ['ios', '7.0', 'iPad 3rd (7.0)']
   #'android' : ['android', '4.2', 'LG Nexus 4']       # Tests take wayyy too long
 browserList = []
 customLaunchers = _.reduce(browsers, (memo, browser, name) ->


Update GDPR countries

Update GDPR countries

--- app/core/utils.coffee
+++ app/core/utils.coffee
@@ -31,7 +31,7 @@
   {country: 'russia', countryCode: 'RU'}
   {country: 'australia', countryCode: 'AU'}
   {country: 'canada', countryCode: 'CA'}
-  {country: 'france', countryCode: 'FR', inEU: true, ageOfConsent: 16}
+  {country: 'france', countryCode: 'FR', inEU: true, ageOfConsent: 15}
   {country: 'taiwan', countryCode: 'TW'}
   {country: 'ukraine', countryCode: 'UA'}
   {country: 'poland', countryCode: 'PL', inEU: true, ageOfConsent: 13}
@@ -52,15 +52,15 @@
   {country: 'colombia', countryCode: 'CO'}
   {country: 'india', countryCode: 'IN'}
   {country: 'thailand', countryCode: 'TH'}
-  {country: 'belgium', countryCode: 'BE', inEU: true}
+  {country: 'belgium', countryCode: 'BE', inEU: true, ageOfConsent: 13}
   {country: 'sweden', countryCode: 'SE', inEU: true, ageOfConsent: 13}
   {country: 'denmark', countryCode: 'DK', inEU: true, ageOfConsent: 13}
-  {country: 'czech-republic', countryCode: 'CZ', inEU: true, ageOfConsent: 13}
+  {country: 'czech-republic', countryCode: 'CZ', inEU: true, ageOfConsent: 15}
   {country: 'hong-kong', countryCode: 'HK'}
-  {country: 'italy', countryCode: 'IT', inEU: true}
-  {country: 'romania', countryCode: 'RO', inEU: true}
+  {country: 'italy', countryCode: 'IT', inEU: true, ageOfConsent: 16}
+  {country: 'romania', countryCode: 'RO', inEU: true, ageOfConsent: 16}
   {country: 'belarus', countryCode: 'BY'}
-  {country: 'norway', countryCode: 'NO'}
+  {country: 'norway', countryCode: 'NO', inEU: true, ageOfConsent: 13}  # GDPR applies to EFTA
   {country: 'philippines', countryCode: 'PH'}
   {country: 'lithuania', countryCode: 'LT', inEU: true, ageOfConsent: 16}
   {country: 'argentina', countryCode: 'AR'}
@@ -69,29 +69,31 @@
   {country: 'serbia', countryCode: 'RS'}
   {country: 'greece', countryCode: 'GR', inEU: true, ageOfConsent: 15}
   {country: 'israel', countryCode: 'IL', inEU: true}
-  {country: 'portugal', countryCode: 'PT', inEU: true}
+  {country: 'portugal', countryCode: 'PT', inEU: true, ageOfConsent: 13}
   {country: 'slovakia', countryCode: 'SK', inEU: true, ageOfConsent: 16}
-  {country: 'ireland', countryCode: 'IE', inEU: true, ageOfConsent: 13}
-  {country: 'switzerland', countryCode: 'CH'}
+  {country: 'ireland', countryCode: 'IE', inEU: true, ageOfConsent: 16}
+  {country: 'switzerland', countryCode: 'CH', inEU: true, ageOfConsent: 16}  # GDPR applies to EFTA
   {country: 'peru', countryCode: 'PE'}
-  {country: 'bulgaria', countryCode: 'BG', inEU: true}
+  {country: 'bulgaria', countryCode: 'BG', inEU: true, ageOfConsent: 14}
   {country: 'venezuela', countryCode: 'VE'}
   {country: 'austria', countryCode: 'AT', inEU: true, ageOfConsent: 14}
-  {country: 'croatia', countryCode: 'HR', inEU: true}
+  {country: 'croatia', countryCode: 'HR', inEU: true, ageOfConsent: 16}
   {country: 'saudia-arabia', countryCode: 'SA'}
   {country: 'chile', countryCode: 'CL'}
   {country: 'united-arab-emirates', countryCode: 'AE'}
   {country: 'kazakhstan', countryCode: 'KZ'}
-  {country: 'estonia', countryCode: 'EE', inEU: true}
+  {country: 'estonia', countryCode: 'EE', inEU: true, ageOfConsent: 13}
   {country: 'iran', countryCode: 'IR'}
   {country: 'egypt', countryCode: 'EG'}
   {country: 'ecuador', countryCode: 'EC'}
-  {country: 'slovenia', countryCode: 'SI', inEU: true}
+  {country: 'slovenia', countryCode: 'SI', inEU: true, ageOfConsent: 15}
   {country: 'macedonia', countryCode: 'MK'}
-  {country: 'cyprus', countryCode: 'CY', inEU: true}
+  {country: 'cyprus', countryCode: 'CY', inEU: true, ageOfConsent: 14}
   {country: 'latvia', countryCode: 'LV', inEU: true, ageOfConsent: 13}
   {country: 'luxembourg', countryCode: 'LU', inEU: true, ageOfConsent: 16}
-  {country: 'malta', countryCode: 'MT', inEU: true}
+  {country: 'malta', countryCode: 'MT', inEU: true, ageOfConsent: 16}
+  {country: 'lichtenstein', countryCode: 'LI', inEU: true}  # GDPR applies to EFTA
+  {country: 'iceland', countryCode: 'IS', inEU: true}  # GDPR applies to EFTA
 ]

 inEU = (country) -> !!_.find(countries, (c) => c.country is slugify(country))?.inEU


make homepage tabs headers

make homepage tabs headers

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -402,7 +402,6 @@
             tab_style = _.defaults {}, (@props.tab_style or {}),
               cursor: 'pointer'
               position: 'relative'
-              display: 'inline-block'
               fontSize: 16
               fontWeight: 600
               color: 'white'
@@ -437,7 +436,7 @@
                 color: if current then 'black' else if hovering then '#F8E71C' else 'white'
                 borderRadius: '16px 16px 0 0'
             else
-              _.extend tab_style,
+              _.defaults tab_style,
                 padding: '10px 20px 4px 20px'
                 backgroundColor: if current then 'rgba(255,255,255,.2)'

@@ -452,7 +451,9 @@
             LI
               tabIndex: 0
               role: 'tab'
-              style: tab_style
+              #style: tab_style
+              style:
+                display: 'inline-block'
               'aria-controls': 'homepagetab'
               'aria-selected': current

@@ -473,7 +474,9 @@
                 save homepage_tabs
                 document.activeElement.blur()

-              filter
+              H4
+                style: tab_style
+                filter


Remove old checks for old pagination

Remove old checks for old pagination

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -121,10 +121,10 @@
         </div>
     """
     events:
-        "keypress .search-query": "updateSearchOnEnter"
-        "click    .pager-prev":   "prevPage"
-        "click    .pager-next":   "nextPage"
-        "click    th":            "toggleSort"
+        "keypress .search-query":              "updateSearchOnEnter"
+        "click    th":                         "toggleSort"
+        "click    .pager-prev:not(.disabled)": "prevPage"
+        "click    .pager-next:not(.disabled)": "nextPage"

     # Binds the collection update event for rendering
     initialize: ->
@@ -254,17 +254,11 @@

     # Go to the previous page in the collection
     prevPage: =>
-        if @data.page > 1
-            $(".page", @$el).html @data.page - 1
-            @setData "page", @data.page - 1
+        @setData "page", @data.page - 1

     # Go to the next page in the collection
     nextPage: =>
-        # Since we don't have a collection count, for now we use the size of
-        # the last GET as an heuristic to limit the use of nextPage
-        if @collection.length == @data.size
-            $(".page", @$el).html @data.page + 1
-            @setData "page", @data.page + 1
+        @setData "page", @data.page + 1

     # Toggle/Select sort column and direction, and update table accodingly
     toggleSort: (e) =>


Restart URL TODO

Restart URL TODO

--- lib/actions/app.coffee
+++ lib/actions/app.coffee
@@ -4,6 +4,7 @@
 server = require('../server/server')
 applicationModel = require('../models/application')
 authHooks = require('../hooks/auth')
+config = require('../config')

 exports.list = authHooks.failIfNotLoggedIn ->
 	applicationModel.getAll().then (applications) ->
@@ -33,5 +34,7 @@
 		throw error

 exports.restart = authHooks.failIfNotLoggedIn (id) ->
+
+	# TODO: Move this URL to config
 	server.post "/application/#{id}/restart", (error) ->
 		throw error if error?


Rename getEslintrcOptions to readEslintrcOptions.

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -45,11 +45,12 @@
     # regex to search for tag open/close tag and close tag
     @JSXREGEXP = /(<)([$_A-Za-z](?:[$_.:\-A-Za-z0-9])*)|(\/>)|(<\/)([$_A-Za-z](?:[$._:\-A-Za-z0-9])*)(>)|(>)|({)|(})|(\?)|(:)/g

-    @autoJsx = atom.config.get('language-babel').autoIndentJSX
     @mouseUp = true
     @multipleCursorTrigger = 1

     @disposables = new CompositeDisposable()
+
+    @autoJsx = atom.config.get('language-babel').autoIndentJSX

     @disposables.add atom.commands.add 'atom-text-editor',
       'language-babel:auto-indent-jsx-on': (event) =>  @autoJsx = true
@@ -66,11 +67,11 @@

     if @eslintrcFilename = @getEslintrcFilename()
       @eslintrcFilename = new File(@eslintrcFilename)
-      @getEslintrcOptions(@eslintrcFilename.getPath())
+      @readEslintrcOptions(@eslintrcFilename.getPath())
       # watch eslintrc for changes
       @disposables.add @eslintrcFilename.onDidChange =>
         return unless @autoJsx
-        @getEslintrcOptions(@eslintrcFilename.getPath())
+        @readEslintrcOptions(@eslintrcFilename.getPath())

   destroy: () ->
     @disposables.dispose()
@@ -538,7 +539,7 @@
   # use eslint react format described at http://tinyurl.com/p4mtatv
   # to create indents. We can read .eslintrc here but don't parse strictly
   # turn spaces into tab dimensions which can be decimal
-  getEslintrcOptions: (eslintrcFile) ->
+  readEslintrcOptions: (eslintrcFile) ->
     # get local path overides
     if fs.existsSync eslintrcFile
       fileContent = stripJsonComments(fs.readFileSync(eslintrcFile, 'utf8'))


Show x installed y activities

Show x installed y activities

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemFollow.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemFollow.coffee
@@ -3,21 +3,26 @@
   cssClassMap = ->
     JTag      : "topic"
     JAccount  : "account"
+    JApp      : "application" # We can use this in style

   constructor:(options,data)->
     options = $.extend options,
       cssClass : "follow bucket #{cssClassMap()[data.sourceName]}"
     super options,data

-    @anchor = new ProfileLinkView {
-      origin: data.anchor
-    }
-
-    @group = new LinkGroup {
-      group         : data.group
-      itemsToShow   : 3
-      subItemClass  : options.subItemLinkClass
-    }
+    @action = "followed"
+    if data.anchor?.constructorName is "JApp"
+      @anchor = new ProfileLinkView
+        origin: data.group[0]
+      @group = new AppLinkView origin: data.anchor
+      @action = "installed"
+    else
+      @anchor = new ProfileLinkView
+        origin: data.anchor
+      @group = new LinkGroup
+        group         : data.group
+        itemsToShow   : 3
+        subItemClass  : options.subItemLinkClass

     #@getData().on 'ItemWasAdded', -> log 'heres the event, sinan', arguments

@@ -25,7 +30,7 @@
     """

     {{> @anchor}}
-    followed
+    #{@action}
     {{> @group}}
     """

@@ -62,12 +67,14 @@
     became a member.
     """

+
 class AccountFollowBucketItemView extends FollowBucketItemView

   constructor:(options, data)->
     options.subItemLinkClass or= ProfileLinkView
     options.subItemCssClass or= 'profile'
     super
+

 class TagFollowBucketItemView extends FollowBucketItemView

@@ -76,6 +83,7 @@
     options.subItemCssClass or= 'topic'
     super

+
 class AppFollowBucketItemView extends FollowBucketItemView

   constructor:(options, data)->


Log out a message if a required plugin dependency wasn't found.

--- lib/base_plugin.coffee
+++ lib/base_plugin.coffee
@@ -45,7 +45,9 @@
     @emitter.on listener.event, (args...) =>
       scope = app: @app
       for pluginId in listener.plugins
-        scope[pluginId] = @app.getPlugin pluginId
+        plugin = @app.getPlugin pluginId
+        unless plugin then @log 'error', "plugin.#{@getId()}", "The plugin #{pluginId} could not be found."
+        scope[pluginId] = plugin
       listener.fn.apply scope, args

   logState: (level, newState, oldState) ->


Cleaned up grunt logging.

Cleaned up grunt logging.

--- tasks/stassets.coffee
+++ tasks/stassets.coffee
@@ -13,7 +13,7 @@
             done = @async()
             options = @options
                 stassets:
-                    verbose: no // TODO Tie to Grunt
+                    verbose: grunt.option('verbose')
                     root: Path.resolve './test/assets'
                     vendors:
                         prefix: Path.resolve './bower_components'
@@ -44,7 +44,6 @@
                 grunt.verbose.writeln "Starting request for #{file}..."
                 request(app).get("/#{file}")
                 .end (err, res)->
-                    grunt.verbose.writeln "Finished request for #{file}..."
                     defer.reject err if err
                     defer.reject res if res.status isnt 200
                     grunt.file.write "#{options.dest}/#{file}", res.text


core/object: bind console.error instead of KD.error onError :hatched_chick:

--- lib/core/object.coffee
+++ lib/core/object.coffee
@@ -20,7 +20,7 @@
     if options.testPath
       KD.registerInstanceForTesting this

-    @on 'error', KD.error
+    @on   'error', console.error.bind console
     @once 'ready', => @readyState = READY


Convert seconds to milliseconds for setTimeout + do not emit two errors when the handshake fails

--- drivers/javascript/src/net.coffee
+++ drivers/javascript/src/net.coffee
@@ -13,7 +13,7 @@
     DEFAULT_HOST: 'localhost'
     DEFAULT_PORT: 28015
     DEFAULT_AUTH_KEY: ''
-    DEFAULT_TIMEOUT: 20
+    DEFAULT_TIMEOUT: 20 # In seconds

     constructor: (host, callback) ->
         if typeof host is 'undefined'
@@ -275,12 +275,12 @@
                         @buffer = bufferSlice(@buffer, i)
                         status_str = String.fromCharCode.apply(null, new Uint8Array status_buf)

+                        handshake_complete = true
                         if status_str == "SUCCESS"
                             # We're good, finish setting up the connection
                             @rawSocket.on 'data', (buf) =>
                                 @_data(toArrayBuffer(buf))

-                            handshake_complete = true
                             @emit 'connect'
                             return
                         else
@@ -297,7 +297,7 @@
             if not handshake_complete
                 @rawSocket.destroy()
                 @emit 'error', new RqlDriverError "Handshake timedout"
-        ), @timeout)
+        ), @timeout*1000)

     close: () ->


REFACTOR change to cover only html elements rather than every node

--- src/modules/controller.coffee
+++ src/modules/controller.coffee
@@ -100,7 +100,7 @@
         stop = true if item.controller.prototype.template isnt undefined
         new item.controller el: el, parent: @, mainAttr: item.value

-    @_parseNode child for child in node.childNodes unless stop or not node
+    @_parseNode child for child in node.children unless stop or not node
     undefined

   _watch: (key, callback, context = @)->


Fix sidebar height growing forever when loading more pages, and also jumping to top when loading more pages.

--- lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -112,13 +112,15 @@

       @$(".post-list").html(rendered.html())
       @renderMorePages()
+      @updateSidebar()
       @trigger "threads:rendered"

     renderMorePages: ->
       if @displayedCollection.hasMorePages()
         @$(".post-list").append("
Load more
")

-    loadMorePages: ->
+    loadMorePages: (event) ->
+      event.preventDefault()
       @$(".more-pages").html('

')
       @$(".more-pages").addClass("loading")
       @collection.retrieveAnotherPage(@current_search, @discussionIds, @sortBy)


new 'fortune' command for oldschool BSD fotrune

--- evilbot.coffee
+++ evilbot.coffee
@@ -175,6 +175,13 @@
   get url, (body) ->
     message.say body

+desc 'fortune'
+hear /fortune/, (message) ->
+  url = "http://www.fortunefortoday.com/getfortuneonly.php"
+
+  get url, (body) ->
+    message.say body
+
 desc 'weather in PLACE'
 hear /weather in (.+)/i, (message) ->
   place = message.match[1]


Fix markdown command target in Atom beta version

--- spec/fonts-cjk-spec.coffee
+++ spec/fonts-cjk-spec.coffee
@@ -1,7 +1,7 @@
 fonts_cjk = require('../lib/fonts-cjk')

 describe 'Fonts CJK', ->
-  [workspaceElement] = []
+  [textEditorElement, workspaceElement] = []

   getFontFamily = (selector) ->
     element = if selector then workspaceElement.querySelector(selector) else workspaceElement
@@ -14,6 +14,7 @@
       atom.workspace.open('file.txt')
     runs ->
       fonts_cjk.run()
+      textEditorElement = atom.workspace.getActiveTextEditor().getElement()
       workspaceElement = atom.views.getView(atom.workspace)
       jasmine.attachToDOM(workspaceElement)

@@ -34,7 +35,7 @@
       waitsForPromise ->
         atom.packages.activatePackage('markdown-preview')
       runs ->
-        atom.commands.dispatch(workspaceElement, 'markdown-preview:toggle')
+        atom.commands.dispatch(textEditorElement, 'markdown-preview:toggle')
       waitsFor 'markdown preview to be created', ->
         preview = atom.workspace.getPanes()[1].getActiveItem()
       runs ->


Remove outdated example.coffee

Remove outdated example.coffee

--- example.coffee
+++ example.coffee
@@ -1,35 +0,0 @@
-fs = require('fs')
-imageWrite = require('./lib/write')
-
-image = process.argv[2]
-drive = process.argv[3]
-
-if not image? or not drive?
-	console.error('Usage: coffee example.coffee <image> <drive>')
-	process.exit(1)
-
-console.log("Writing #{image} to #{drive}\n")
-
-getImageStream = ->
-	stream = fs.createReadStream(image)
-	stream.length = fs.statSync(image).size
-	return stream
-
-emitter = imageWrite.write(drive, getImageStream())
-
-emitter.on 'progress', (state) ->
-	console.log("#{Math.floor(state.percentage)}% (#{state.transferred}/#{state.length}) eta #{state.eta}s")
-
-emitter.on 'error', (error) ->
-	console.error(error)
-	process.exit(1)
-
-emitter.on 'done', ->
-	console.log('Checking...')
-
-	imageWrite.check(drive, getImageStream()).then (success) ->
-		if success
-			console.log('The write was successful!')
-		else
-			console.error('The write failed!')
-			process.exit(1)


Do not create a new mixpanel instance in every track request

--- client/app/MainApp/analytic.coffee
+++ client/app/MainApp/analytic.coffee
@@ -1,8 +1,9 @@
 do->
+
+  KD.kdMixpanel = new KDMixpanel
   KD.track = (rest...)->
     logToGoogle rest...
-    kdMixpanel = new KDMixpanel
-    kdMixpanel.createEvent rest...
+    KD.kdMixpanel.createEvent rest...

   logToGoogle = (rest...)->
     category = action = rest.first


Don't alert on successful down or up vote of idea.

--- app/assets/javascripts/voluntary_brainstorming/components/brainstorming_idea_vote_component.js.coffee
+++ app/assets/javascripts/voluntary_brainstorming/components/brainstorming_idea_vote_component.js.coffee
@@ -16,7 +16,6 @@
           @set 'vote', false
         else
           @sendAction 'reloadAction'
-          alert Volontariat.t('brainstorming_idea_votes.create.successfully')
       ).fail((data) =>
         alert "#{Volontariat.t('brainstorming_idea_votes.create.failed')}!"
         @set 'count', @get('count') - 1
@@ -34,7 +33,6 @@

       $.ajax("/api/v1/brainstorming_idea_votes/0", type: 'DELETE', data: { idea_id: @get('ideaId') }).done((data) =>
         @sendAction 'reloadAction'
-        alert Volontariat.t('brainstorming_idea_votes.destroy.successful')
       ).fail((data) =>
         alert Volontariat.t('activerecord.errors.models.brainstorming_idea_vote.attributes.base.deletion_failed')
         @set 'count', @get('count') + 1


remove inline requires from ProjectEntityHandler

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -16,7 +16,7 @@
 DocstoreManager = require "../Docstore/DocstoreManager"
 ProjectGetter = require "./ProjectGetter"
 CooldownManager = require '../Cooldown/CooldownManager'
-
+documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')

 module.exports = ProjectEntityHandler =
 	getAllFolders: (project_id,  callback) ->
@@ -106,7 +106,6 @@
 	flushProjectToThirdPartyDataStore: (project_id, callback) ->
 		self = @
 		logger.log project_id:project_id, "flushing project to tpds"
-		documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
 		documentUpdaterHandler.flushProjectToMongo project_id, (error) ->
 			return callback(error) if error?
 			ProjectGetter.getProject project_id, {name:true}, (error, project) ->
@@ -181,7 +180,6 @@
 							doc: doc
 							path: result?.path?.fileSystem
 							docLines: docLines.join('\n')
-						documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
 						documentUpdaterHandler.updateProjectStructure project_id, null, [], [newDoc], [], [], (error) ->
 							return callback(error) if error?
 							callback null, doc, folder_id
@@ -220,7 +218,6 @@
 							file: fileRef
 							path: result?.path?.fileSystem
 							url: fileStoreUrl
-						documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
 						documentUpdaterHandler.updateProjectStructure project_id, userId, [], [], [], [newFile], (error) ->
 							return callback(error) if error?
 							callback null, fileRef, folder_id
@@ -398,7 +395,6 @@
 								self.getAllEntitiesFromProject newProject, (error, newDocs, newFiles
 								) =>
 									return callback(error) if error?
-									documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
 									documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback

 	_checkValidMove: (project, entityType, entityPath, destFolderId, callback = (error) ->) ->
@@ -456,7 +452,6 @@
 						return callback(error) if error?
 						ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) =>
 							return callback(error) if error?
-							documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
 							documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback

 	_cleanUpEntity: (project, entity, entityType, callback = (error) ->) ->
@@ -480,7 +475,7 @@

 		unsetRootDocIfRequired (error) ->
 			return callback(error) if error?
-			require('../../Features/DocumentUpdater/DocumentUpdaterHandler').deleteDoc project_id, doc_id, (error) ->
+			documentUpdaterHandler.deleteDoc project_id, doc_id, (error) ->
 				return callback(error) if error?
 				ProjectEntityHandler._insertDeletedDocReference project._id, doc, (error) ->
 					return callback(error) if error?


Disable run command on terminal test

Disable run command on terminal test

--- client/test/lib/ide/general.coffee
+++ client/test/lib/ide/general.coffee
@@ -6,6 +6,8 @@

   runCommandOnTerminal: (browser) ->
+
+    return browser.end()

     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)


refactored examples-gen.coffee

refactored examples-gen.coffee

--- web-src/examples-gen.coffee
+++ web-src/examples-gen.coffee
@@ -1,23 +1,17 @@
 #!/usr/bin/env coffee
-
 # Collect data from ../examples and generate examples.js for inclusion into index.html
-
 fs = require 'fs'
-
-names = for name in fs.readdirSync "#{__dirname}/../examples" when name.match /.*\.apl/ then name
-names.sort()
-
+glob = require 'glob'
+{basename} = require 'path'
 fs.writeFileSync "#{__dirname}/examples.js", """
   // Generated code, do not edit
   window.examples = [
-  #{(
-    for name in names
-      '  ' + JSON.stringify [
-        name.replace(/(^\d+-|\.apl$)/g, '')
-        fs.readFileSync("#{__dirname}/../examples/#{name}").toString()
-          .replace(/(^#!.*\n+|\n+$)/g, '')
-          .replace(/\n *⎕ *← *(.*)$/, '\n$1')
-      ]
-  ).join ',\n'}
+    #{(
+      for f in glob.sync "#{__dirname}/../examples/*.apl"
+        '  ' + JSON.stringify [
+          basename(f).replace(/^\d+-|\.apl$/g, '')
+          fs.readFileSync(f, 'utf8').replace(/^#!.*\n+|\n+$/g, '').replace(/\n* *⎕ *← *(.*)$/, '\n$1')
+        ]
+    ).join ',\n'}
   ];
 """


test stubs for calendar API

test stubs for calendar API

--- test/spec/GAPISpec.coffee
+++ test/spec/GAPISpec.coffee
@@ -376,3 +376,48 @@
     it 'should list moments written by the app'
     it 'should delete a moment written by the app'

+
+  describe 'Calendar', ->
+
+    it 'should delete an acl'
+    it 'should get an acl'
+    it 'should insert an acl'
+    it 'should list an acl'
+    it 'should update an acl'
+    it 'should patch an acl'
+
+    it 'should delete a calendar list'
+    it 'should get a calendar list'
+    it 'should insert a calendar list'
+    it 'should list a user\'s calendar lists'
+    it 'should update a calendar list'
+    it 'should patch a calendar list'
+
+    it 'should clear a calendar'
+    it 'should delete a secondary calendar'
+    it 'should get calendar metadata'
+    it 'should insert a secondary calendar'
+    it 'should update metadata for a calendar'
+    it 'should patch a metadata for a calendar'
+
+    it 'should get colors'
+
+    it 'should delete an event'
+    it 'should get an event'
+    it 'should import an event'
+    it 'should insert an event'
+    it 'should list instances of a recurring event'
+    it 'should list events on a specified calendar'
+    it 'should move an event to another calendar'
+    it 'should create an event based on a simple text string'
+    it 'should update an event'
+    it 'should patch an event'
+    it 'should watch for changes to events'
+
+    it 'should get free/busy for a set of calendars'
+
+    it 'should get a user setting'
+    it 'should list user settings for the authenticated user'
+
+    it 'should stop a channel'
+


Use node's require instead of internal require

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -1,4 +1,4 @@
-_ = nodeRequire 'underscore'
+_ = require 'underscore'
 {$$} = require 'space-pen'
 Range = require 'range'


When Leaving game, scroll position on game selector is maintained

--- web/scripts/codeland.coffee
+++ web/scripts/codeland.coffee
@@ -4,6 +4,7 @@
 root.UIcont = null

 root.initialize = (UIcont) ->
+    root.gameSelectionScrollPosition = 0
     root.loadJSONConfigs()
     root.initializeDoppio()
     root.UIcont = UIcont
@@ -32,6 +33,9 @@
         # console.log "Game: #{game}"
         sel.buildDiv(game, descriptions[game], player.games[game], root.canPlay(game), codeland)
     addGameToMap game for game in gameSequence
+    $('#gameSelection').animate {
+        scrollTop: root.gameSelectionScrollPosition
+    }, 0
     #TODO FADE IN
     return

@@ -41,6 +45,7 @@

     gamediv = $(root.UIcont)
     tmp1 = document.getElementById("gameSelection")
+    root.gameSelectionScrollPosition = tmp1.scrollTop
     root.UIcont.removeChild(tmp1)

     #Todo FADE IN


Remove not necessary component

Remove not necessary component

--- smsgw/static/src/components/pages/app/directory/contacts/tags.coffee
+++ smsgw/static/src/components/pages/app/directory/contacts/tags.coffee
@@ -1,32 +0,0 @@
-###
-See for more information
-http://arcturo.github.io/library/coffeescript/07_the_bad_parts.html
-@cjsx React.DOM
-###
-"use strict"
-
-React = require 'react'
-Router = require 'react-router'
-# components
-Subheader = require './../../components/sub-header.coffee'
-
-module.exports = React.createClass
-
-    getInitialState: ->
-        tags: []
-        menu: [
-            label: 'Contacts'
-            route: 'contacts'
-        ,
-            label: 'Tags'
-            route: 'tags'
-        ]
-
-    render: ->
-

-

-

-                    Tags
-

-

-


use onChange event to update google analytics enable.

--- coffee/controllers/mainCtrl.coffee
+++ coffee/controllers/mainCtrl.coffee
@@ -20,21 +20,10 @@
     deferred = $q.defer()
     deferred.promise
       .then(_initializeEvents)
+      .then(_initializeGoogleAnalytics)
       .then(_initializeDataFromChrome)
       .then(_setDataSyncAlarms)
     deferred.resolve()
-
-  ###
-   initialize GoogleAnalytics.
-  ###
-  _initializeGoogleAnalytics = () ->
-    Analytics.init {
-      serviceName:   "RedmineTimeTracker"
-      analyticsCode: "UA-32234486-7"
-    }
-    Analytics.setPermission Option.getOptions().reportUsage
-    Analytics.sendView("/app/")
-    Log.info("GoogleAnalytics enable: " + Option.getOptions().reportUsage)

   ###
    initialize events.
@@ -44,6 +33,9 @@
     DataAdapter.addEventListener DataAdapter.ACCOUNT_ADDED, _loadRedmine
     DataAdapter.addEventListener DataAdapter.TICKETS_CHANGED, () ->
       Ticket.set(DataAdapter.tickets)
+    Option.onChanged('reportUsage', (e) ->
+      Analytics.setPermission e
+      Log.info("GoogleAnalytics enable: " + Option.getOptions().reportUsage))
     Log.debug "finish initialize Event."

   ###
@@ -174,12 +166,21 @@
     , 2500

   ###
+   initialize GoogleAnalytics.
+  ###
+  _initializeGoogleAnalytics = () ->
+    Analytics.init {
+      serviceName:   "RedmineTimeTracker"
+      analyticsCode: "UA-32234486-7"
+    }
+    Analytics.sendView("/app/")
+
+  ###
    initialize Data from chrome storage.
   ###
   _initializeDataFromChrome = () ->
     Log.debug "start initialize data."
     Option.loadOptions()
-      .then(_initializeGoogleAnalytics)
       .then(_initializeAccount)
       .then(_initializeProject)
       .then(_initializeIssues)


wip on doAction

wip on doAction

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -692,12 +692,30 @@
     .then (body) ->
       body.result.object.id

-  doActions: (user, id, commands, comment) ->
+  doActions: (user, id, commandString, comment) ->
     @getBotPHID()
-    .then (bot_phid) ->
-      console.log commands
-      bot_phid
-
+    .then (bot_phid) =>
+      commands = @parseAction commandString
+      [ bot_phid, [] ]
+
+  parseAction: (str, res = []) ->
+    p = new RegExp("^(in|not in|on|is|to) ([^ ]*)")
+    r = str.trim().match p
+    switch r[1]
+      when 'in'
+        res.push({ type: 'projects.add', value: r[2] })
+      when 'not in'
+        res.push({ type: 'projects.remove', value: r[2] })
+      when 'on'
+        res.push({ type: 'owner', value: r[2] })
+      when 'to'
+        res.push({ type: 'column', value: r[2] })
+      when 'is'
+        res.push({ type: 'status', value: r[2] })
+    next = str.trim().replace(p, '')
+    if next.trim() isnt ''
+      res = @parseAction(next, res)
+    res

   listTasks: (projphid) ->
     query = {


Sometimes intercom fails. Tell them to email us instead

--- js/inner.coffee
+++ js/inner.coffee
@@ -821,7 +821,11 @@
     $.get "/assets/js/tests/inner-tests.dieter", (code) =>
       eval code

-  raiseIntercomDialog: (message=null) =>
+  raiseIntercomDialog: (message) =>
+    unless intercomJQuery?
+      notifyError "Uh-oh, our Help system isn't available. Please email us instead, at sayhi@circleci.com!"
+      return
+
     jq = intercomJQuery
     jq("#IntercomDefaultWidget").click()
     unless jq('#IntercomNewMessageContainer').is(':visible')


fix coffeescript indenting issue

fix coffeescript indenting issue

--- app/assets/javascripts/domready.js.coffee
+++ app/assets/javascripts/domready.js.coffee
@@ -56,9 +56,9 @@
     $(this).closest('.row').find('.success-criteria').toggleClass('success-criteria-show')

   # Show ratings for students
-   $('.choices-toggle').on 'click', (e) ->
-      e.preventDefault()
-      $(this).closest('li').find('.choices').toggleClass('show')
+  $('.choices-toggle').on 'click', (e) ->
+    e.preventDefault()
+    $(this).closest('li').find('.choices').toggleClass('show')

   # Sidebar mobile select menu
   if CurriUiOptions.mobileScreen()
@@ -82,7 +82,7 @@
       window.location = $(this).find("option:selected").val()

   # Show students that haven't voted
- $('.students-toggle').on 'click', (e) ->
+  $('.students-toggle').on 'click', (e) ->
     e.preventDefault()
     $('.hasnt-voted').toggleClass('hasnt-voted-show')


placehoder for stop job

placehoder for stop job

--- app/assets/javascripts/osc_jobs.js.coffee
+++ app/assets/javascripts/osc_jobs.js.coffee
@@ -22,14 +22,22 @@

 @update_copy_button = (id) ->
   if id?
-    $("#copy_button").attr("href", '.' + Routes.copy_osc_job_path(id))
-    $("#copy_button").data("method", "put")
+    $("#copy_button").attr('href', '.' + Routes.copy_osc_job_path(id))
+    $("#copy_button").data('method', 'put')
   else
-    $("#copy_button").attr("href", '#')
-    $("#copy_button").data("method", "get")
+    $("#copy_button").attr('href', '#')
+    $("#copy_button").data('method', 'get')

 @show_job_panel = (id) ->
   if id?
     $("#jobDetailsPanel").fadeIn(200)
   else
     $("#jobDetailsPanel").fadeOut(200)
+
+
+@update_stop = (running) ->
+  if running?
+    # TODO Create a route that will stop a running job.
+    $("stop_button").attr('href', ' TODO ')
+  else
+    $("stop_button").removeAttr('href')


disable rias

disable rias

--- app/assets/javascripts/priority.js.coffee
+++ app/assets/javascripts/priority.js.coffee
@@ -1,7 +1,7 @@
 #= require modernizr-custom

 #= require lazysizes
-#= require lazysizes/plugins/rias/ls.rias
+#  require lazysizes/plugins/rias/ls.rias
 #= require lazysizes/plugins/bgset/ls.bgset
 #= require lazysizes/plugins/respimg/ls.respimg
 #  require lazysizes/plugins/custommedia/ls.custommedia


small but _important js improvement

small but _important js improvement

--- app/assets/javascripts/services.js.coffee
+++ app/assets/javascripts/services.js.coffee
@@ -38,15 +38,10 @@
   $('.save_button').click ->
        c = $("[contenteditable]")
        data1 = {}
-       c.each ->
-          data1[$(this).attr('data-tag')] = $(this).text()
-
-
+       c.each -> data1[$(this).attr('data-tag')] = $(this).text()
        $.post(
           "/content_update"
           {title: 'Save_data', data: data1}
-          'json'
           -> $(".save_button").fadeOut(1500)
        )
-       $(".save_button").fadeOut(1500)
        0


fix #1206

fix #1206

--- app/coffee/modules/kanban/sortable.coffee
+++ app/coffee/modules/kanban/sortable.coffee
@@ -37,45 +37,49 @@

 KanbanSortableDirective = ($repo, $rs, $rootscope) ->
     link = ($scope, $el, $attrs) ->
-        oldParentScope = null
-        newParentScope = null
-        itemEl = null
-        tdom = $el
+        bindOnce $scope, "project", (project) ->
+            if not (project.my_permissions.indexOf("modify_us") > -1)
+                return

-        deleteElement = (itemEl) ->
-            # Completelly remove item and its scope from dom
-            itemEl.scope().$destroy()
-            itemEl.off()
-            itemEl.remove()
+            oldParentScope = null
+            newParentScope = null
+            itemEl = null
+            tdom = $el

-        tdom.sortable({
-            handle: ".kanban-task-inner"
-            dropOnEmpty: true
-            connectWith: ".kanban-uses-box"
-            revert: 400
-        })
+            deleteElement = (itemEl) ->
+                # Completelly remove item and its scope from dom
+                itemEl.scope().$destroy()
+                itemEl.off()
+                itemEl.remove()

-        tdom.on "sortstop", (event, ui) ->
-            parentEl = ui.item.parent()
-            itemEl = ui.item
-            itemUs = itemEl.scope().us
-            itemIndex = itemEl.index()
-            newParentScope = parentEl.scope()
+            tdom.sortable({
+                handle: ".kanban-task-inner"
+                dropOnEmpty: true
+                connectWith: ".kanban-uses-box"
+                revert: 400
+            })

-            newStatusId = newParentScope.status.id
-            oldStatusId = oldParentScope.status.id
+            tdom.on "sortstop", (event, ui) ->
+                parentEl = ui.item.parent()
+                itemEl = ui.item
+                itemUs = itemEl.scope().us
+                itemIndex = itemEl.index()
+                newParentScope = parentEl.scope()

-            if newStatusId != oldStatusId
-                deleteElement(itemEl)
+                newStatusId = newParentScope.status.id
+                oldStatusId = oldParentScope.status.id

-            $scope.$apply ->
-                $rootscope.$broadcast("kanban:us:move", itemUs, newStatusId, itemIndex)
+                if newStatusId != oldStatusId
+                    deleteElement(itemEl)

-            ui.item.find('a').removeClass('noclick')
+                $scope.$apply ->
+                    $rootscope.$broadcast("kanban:us:move", itemUs, newStatusId, itemIndex)

-        tdom.on "sortstart", (event, ui) ->
-            oldParentScope = ui.item.parent().scope()
-            ui.item.find('a').addClass('noclick')
+                ui.item.find('a').removeClass('noclick')
+
+            tdom.on "sortstart", (event, ui) ->
+                oldParentScope = ui.item.parent().scope()
+                ui.item.find('a').addClass('noclick')

         $scope.$on "$destroy", ->
             $el.off()


bugfix options-menu-display. Added mouse-leave-autoclose of options-menu. added menu-header (to be styled)

--- lib/go-outline-view.coffee
+++ lib/go-outline-view.coffee
@@ -25,6 +25,7 @@
           @div class: "go-outline-options", =>
             @button class: "btn", outlet: 'btnOptions', "options"
             @div class: "go-outline-options-popover select-list popover-list hidden", outlet: "menu", =>
+              @h3 class:"block test-highlight", "Display Options"
               @ol class: "list-group", =>
                 @li class: "", "show variables", outlet:"btnShowVariables"
                 @li class: "", "show private symbols", outlet: 'btnShowPrivate'
@@ -130,13 +131,21 @@

     @showMenu = false

+    updateMenu = =>
+      if @showMenu
+        $(@menu[0]).removeClass('hidden')
+      else
+        $(@menu[0]).addClass('hidden')
+
+
     @subscribeTo(@btnOptions[0], {'click': (e) =>
       @showMenu = !@showMenu
-      if @showMenu
-        $(@menu[0]).addClass('hidden')
-      else
-        $(@menu[0]).removeClass('hidden')
-
+      updateMenu()
+    })
+
+    @subscribeTo(@menu[0], {'mouseleave': (e) =>
+      @showMenu = false
+      updateMenu()
     })

     @subscribeTo(@btnShowVariables[0], { 'click': (e) =>


Fix bug where incorrect variable was used

--- services/web/test/unit/coffee/Uploads/ProjectUploadControllerTests.coffee
+++ services/web/test/unit/coffee/Uploads/ProjectUploadControllerTests.coffee
@@ -102,7 +102,7 @@
 			@project_id = "project-id-123"
 			@folder_id = "folder-id-123"
 			@path = "/path/to/file/on/disk.png"
-			@filename = "filename.png"
+			@name = "filename.png"
 			@req.files =
 				qqfile:
 					path: @path


Somewhat better linter integration

Somewhat better linter integration

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -159,15 +159,18 @@
           (m) -> m
       upi.setMessages messages.map(setHighlighter), types

+    unless atom.config.get 'haskell-ghc-mod.useLinter'
+      @disposables.add atom.commands.add 'atom-text-editor[data-grammar~="haskell"]',
+        'haskell-ghc-mod:check-file': ({target}) =>
+          editor = target.getModel()
+          @process.doCheckBuffer(editor.getBuffer()).then (res) ->
+            setMessages res, ['error', 'warning']
+        'haskell-ghc-mod:lint-file': ({target}) =>
+          editor = target.getModel()
+          @process.doLintBuffer(editor.getBuffer()).then (res) ->
+            setMessages res, ['lint']
+
     @disposables.add atom.commands.add 'atom-text-editor[data-grammar~="haskell"]',
-      'haskell-ghc-mod:check-file': ({target}) =>
-        editor = target.getModel()
-        @process.doCheckBuffer(editor.getBuffer()).then (res) ->
-          setMessages res, ['error', 'warning']
-      'haskell-ghc-mod:lint-file': ({target}) =>
-        editor = target.getModel()
-        @process.doLintBuffer(editor.getBuffer()).then (res) ->
-          setMessages res, ['lint']
       'haskell-ghc-mod:show-type': ({target, detail}) ->
         upi.showTooltip
           editor: target.getModel()
@@ -261,11 +264,12 @@
         @process.doLintBuffer(buffer, fast).then (res) ->
           setMessages res, ['lint']

-    @disposables.add upi.onDidSaveBuffer (buffer) ->
-      checkLint buffer, 'Save'
-
-    @disposables.add upi.onDidStopChanging (buffer) ->
-      checkLint buffer, 'Change', true
+    unless atom.config.get 'haskell-ghc-mod.useLinter'
+      @disposables.add upi.onDidSaveBuffer (buffer) ->
+        checkLint buffer, 'Save'
+
+      @disposables.add upi.onDidStopChanging (buffer) ->
+        checkLint buffer, 'Change', true

     @disposables.add @process.onBackendActive ->
@@ -274,9 +278,17 @@
     @disposables.add @process.onBackendIdle ->
       upi.setStatus status: 'ready'

+    unless atom.config.get 'haskell-ghc-mod.useLinter'
+      upi.setMenu 'ghc-mod', [
+        {label: 'Check', command: 'haskell-ghc-mod:check-file'}
+        {label: 'Lint', command: 'haskell-ghc-mod:lint-file'}
+      ]
+    else
+      upi.setMenu 'ghc-mod', [
+        {label: 'Check', command: 'linter:lint'}
+      ]
+
     upi.setMenu 'ghc-mod', [
-      {label: 'Check', command: 'haskell-ghc-mod:check-file'}
-      {label: 'Lint', command: 'haskell-ghc-mod:lint-file'}
       {label: 'Stop Backend', command: 'haskell-ghc-mod:shutdown-backend'}
     ]

@@ -307,30 +319,41 @@
     return unless atom.config.get 'haskell-ghc-mod.useLinter'
     [
       func: 'doCheckBuffer'
-      lintOnFly: false
-      scopes: ['source.haskell', 'text.tex.latex.haskell']
+      lintOnFly: 'onChangeCheck'
+      enabledConf: 'onSaveCheck'
     ,
       func: 'doLintBuffer'
-      lintOnFly: true
-      scopes: ['source.haskell']
-    ].map ({func, scopes, lintOnFly}) =>
-      grammarScopes: scopes
+      lintOnFly: 'onChangeLint'
+      enabledConf: 'onSaveLint'
+    ].map ({func, lintOnFly, enabledConf}) =>
+      linter =
+      grammarScopes: ['source.haskell', 'text.tex.latex.haskell']
       scope: 'file'
-      lintOnFly: lintOnFly
+      lintOnFly: false
       lint: (textEditor) =>
         return unless @process?
+        return unless atom.config.get("haskell-ghc-mod.#{enabledConf}") or
+          atom.config.get("haskell-ghc-mod.#{lintOnFly}")
         return if textEditor.isEmpty()
         @process[func](textEditor.getBuffer(), lintOnFly).then (res) ->
           res.map ({uri, position, message, severity}) ->
             [message, messages...] = message.split /^(?!\s)/gm
             {
               type: severity
-              text: message
-              multiline: true
+              text: message.replace(/\n+$/, '')
               filePath: uri
               range: [position, position.translate [0, 1]]
               trace: messages.map (text) ->
                 type: 'trace'
-                text: text
-                multiline: true
+                text: text.replace(/\n+$/, '')
             }
+
+      # NOTE: some pretty gnarly hacks here...
+      disp = atom.config.observe "haskell-ghc-mod.#{lintOnFly}", (value) ->
+        linter.lintOnFly = value
+
+      Object.observe linter, ->
+        if linter.deactivated
+          disp.dispose()
+
+      return linter


Simplify check/lint subscription code

Simplify check/lint subscription code

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -180,35 +180,24 @@
         else
           Promise.reject ignore: true #this won't set backend status

-    @disposables.add upi.onDidSaveBuffer (buffer) =>
-      if atom.config.get('haskell-ghc-mod.onSaveCheck') and
-         atom.config.get('haskell-ghc-mod.onSaveLint')
-        upi.clearMessages ['error', 'warning', 'lint']
-        @process.doCheckBuffer(buffer).then (res) ->
-          upi.addMessages res, ['error', 'warning', 'lint']
-        @process.doLintBuffer(buffer).then (res) ->
-          upi.addMessages res, ['error', 'warning', 'lint']
-      else if atom.config.get('haskell-ghc-mod.onSaveCheck')
-        @process.doCheckBuffer(buffer).then (res) ->
+    checkLint = (buffer, opt, fast) =>
+      if atom.config.get("haskell-ghc-mod.on#{opt}Check") and
+         atom.config.get("haskell-ghc-mod.on#{opt}Lint")
+        @process.doCheckAndLint(buffer, fast).then (res) ->
+          upi.setMessages res, ['error', 'warning', 'lint']
+      else if atom.config.get("haskell-ghc-mod.on#{opt}Check")
+        @process.doCheckBuffer(buffer, fast).then (res) ->
           upi.setMessages res, ['error', 'warning']
-      else if atom.config.get('haskell-ghc-mod.onSaveLint')
-        @process.doLintBuffer(buffer).then (res) ->
+      else if atom.config.get("haskell-ghc-mod.on#{opt}Lint")
+        @process.doLintBuffer(buffer, fast).then (res) ->
           upi.setMessages res, ['lint']

-    @disposables.add upi.onDidStopChanging (buffer) =>
-      if atom.config.get('haskell-ghc-mod.onChangeCheck') and
-         atom.config.get('haskell-ghc-mod.onChangeLint')
-        upi.clearMessages ['error', 'warning', 'lint']
-        @process.doCheckBuffer(buffer, true).then (res) ->
-          upi.addMessages res, ['error', 'warning', 'lint']
-        @process.doLintBuffer(buffer, true).then (res) ->
-          upi.addMessages res, ['error', 'warning', 'lint']
-      else if atom.config.get('haskell-ghc-mod.onChangeCheck')
-        @process.doCheckBuffer(buffer, true).then (res) ->
-          upi.setMessages res, ['error', 'warning']
-      else if atom.config.get('haskell-ghc-mod.onChangeLint')
-        @process.doLintBuffer(buffer, true).then (res) ->
-          upi.setMessages res, ['lint']
+    @disposables.add upi.onDidSaveBuffer (buffer) ->
+      checkLint buffer, 'Save'
+
+    @disposables.add upi.onDidStopChanging (buffer) ->
+      checkLint buffer, 'Change', true
+

     @disposables.add @process.onBackendActive ->
       upi.setStatus status: 'progress'


Upgrade git-show

Upgrade git-show

--- lib/models/git-show.coffee
+++ lib/models/git-show.coffee
@@ -2,7 +2,7 @@
 Path = require 'path'
 fs = require 'fs-plus'

-{$, EditorView, View} = require 'atom'
+{$, TextEditorView, View} = require 'atom-space-pen-views'

 git = require '../git'
 ListView = require '../views/branch-list-view'
@@ -33,19 +33,23 @@

 class InputView extends View
   @content: ->
-    @div class: 'overlay from-top', =>
-      @subview 'objectHash', new EditorView(mini: true, placeholderText: 'Commit hash to show')
+    @div =>
+      @subview 'objectHash', new TextEditorView(mini: true, placeholderText: 'Commit hash to show')

   initialize: (callback) ->
-    atom.workspaceView.append this
+    @panel ?= atom.workspace.addModalPanel(item: this)
+    @panel.show()
     @on 'core:cancel', =>
-      @detach()
+      @destroy()
     @objectHash.focus()
     @objectHash.on 'core:confirm', =>
-      text = $(this).text().split(' ')
+      text = @objectHash.getModel().getText().split(' ')
       name = if text.length is 2 then text[1] else text[0]
       callback text
-      @detach()
+      @destroy()
+
+  destroy: ->
+    @panel.destroy()

 module.exports = (objectHash, file) ->
   if not objectHash?


Add Credential form for various usages

Add Credential form for various usages

--- client/Account/views/credentials.coffee
+++ client/Account/views/credentials.coffee
@@ -136,6 +136,57 @@
             placement : "top"
         , vendorList

+  showAddCredentialFormFor: (vendor)->
+
+    view = @getView().parent
+    view.form?.destroy()
+
+    fields          =
+      title         :
+        label       : "Title"
+        placeholder : "title for this credential"
+
+    Object.keys(Vendors[vendor].credentialFields).forEach (field)->
+      fields[field] = _.clone Vendors[vendor].credentialFields[field]
+      fields[field].required = yes
+
+    view.form      = new KDFormViewWithFields
+      cssClass     : "form-view"
+      fields       : fields
+      buttons      :
+        Save       :
+          title    : "Add credential"
+          type     : "submit"
+          style    : "solid green medium"
+          loader   :
+            color  : "#444444"
+          callback : -> @hideLoader()
+        Cancel     :
+          type     : "cancel"
+          style    : "solid medium"
+          callback : -> view.form.destroy()
+      callback     : (data)=>
+
+        log "Here we go", data
+
+        { Save } = view.form.buttons
+        Save.showLoader()
+
+        { title } = data
+        delete data.title
+
+        KD.remote.api.JCredential.create {
+          vendor, title, meta: data
+        }, (err, credential)=>
+
+          Save.hideLoader()
+
+          unless KD.showError err
+            @loadItems()  unless err?
+            view.form.destroy()
+
+    view.addSubView view.form
+
 class AccountCredentialList extends KDListView

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


activity: don't read from store, use allChannels getter

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -97,7 +97,7 @@
   ChannelThreadsStore
   MessagesStore
   ChannelFlagsStore
-  ChannelsStore
+  allChannels
   ['ChannelMessageLoaderMarkersStore']
   (threads, messages, channelFlags, channels, loaderMarkers) ->
     threads.map (thread) ->


Added prime number sieve as a further test.

--- lib/stream_test.coffee
+++ lib/stream_test.coffee
@@ -6,6 +6,7 @@
       val
   }
   -> cache.force()
+

 class Stream
   constructor: (@first, rest) ->
@@ -102,7 +103,7 @@
 puts fibonacci.take(100)
 puts()

-puts "The Fibonacci numbers between and 1000 and 100000:"
+puts "The Fibonacci numbers between 1000 and 100000:"
 puts fibonacci.drop_while((n) ->  n < 1000).take_while((n) ->  n < 100000)
 puts()

@@ -133,3 +134,11 @@
 puts "The largest Fibonacci number under 1,000,000:"
 puts fibonacci.take_while((n) ->  n < 1000000).last()
 puts()
+
+test   = (p) -> (n) -> n % p != 0
+sieve  = (s) -> new Stream(s.first, -> sieve(s.rest().select(test(s.first))))
+primes = sieve(Stream.from(2))
+
+puts "The prime numbers between 10000 and 10100:"
+puts primes.drop_while((n) ->  n < 10000).take_while((n) ->  n < 10100)
+puts()


:bug: Fix a bug with MessagePanel

:bug: Fix a bug with MessagePanel

--- lib/typechecker.coffee
+++ lib/typechecker.coffee
@@ -40,7 +40,7 @@
       Subscriptions = []
       @RemoveDecorations()
       @RemoveErrors()
-      Main.V.MPI.detach()
+      Main.V.MPI.close()
       ScrollSubscription?.dispose()
     @Lint:->
       Main.V.H.exec(['--json'],null,ActiveFile).then (result)=>
@@ -60,7 +60,7 @@
     @ProcessErrors:->
       @RemoveDecorations()
       @RemoveErrors()
-      return Main.V.MPI.detach() unless Errors.length
+      return Main.V.MPI.close() unless Errors.length
       for Error,I in Errors
         LeFirst = true
         for TraceEntry in Error.message
@@ -78,4 +78,4 @@
         LeErrors.forEach (error)->
           error.Render(RowStart,RowEnd,ActiveFile,Editor,EditorView,LineHeight)
       else
-        Main.V.MPI.detach()+        Main.V.MPI.close()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -402,7 +402,7 @@
             maxLabelWidth = Math.max(maxLabelWidth, bbox.width+10)
             maxLabelHeight = Math.max(maxLabelHeight, bbox.height+10)
         )
-        #.transition()//we can use transitions now!
+        .transition() #we can use transitions now!
         .attr("x", (d) ->
                     return d.x
                 )


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,9 +1,10 @@
 class Dashing.FullpieAgent extends Dashing.Widget

     @accessor 'data'
-    root = exports ? this
-    root.width = 750
-
+
+    ready: ->
+        @width = 750
+
     onData: (data) ->
         #$(@node).fadeOut().fadeIn()
         @update(data.data)
@@ -18,7 +19,7 @@
         # Remove any previous svg
         $(@node).children("svg").remove();

-        #width = @width
+        width = @width
         height = 400
         radius = Math.min(width, height) / 2
         radiuso = 130


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -6,7 +6,8 @@
     ready: ->
         @testData = 123
         @oldPieData = ''
-        @instanceDataId = ''
+        @instanceDataId = $(@node).attr('data-id')
+        console.log('@instanceDataId',@instanceDataId)

     onData: (data) ->
         console.log('testData',@testData)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -17,10 +17,10 @@

     mFontSize = 28

-    width = 500 #width
-    height = 500 #height
+    width = 400 #width
+    height = 400 #height
     radiuso = 200 #outer radius
-    radiusi = 150 #inner radius
+    radiusi = 100 #inner radius

     color = d3.scale.ordinal()
       #.domain([1,10])


Agentpie: code cleanup

Agentpie: code cleanup

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -289,16 +289,13 @@
                 bottomEdge = d3.max(conflicts, (d2) ->
                     d2.b
                 )
+                # Original corrections for all labels
                 #d.l = rightEdge
                 #d.x = d.l + bbox.width / 2 + 2#5
                 #d.r = d.l + bbox.width + 5#10
                 rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle
                 if (rads >= 0 && rads <= 4 * Math.PI / 4)
                     d.t = bottomEdge + 2
-                #else if (rads > 3 * Math.PI / 4 && rads < 4 * Math.PI / 4)
-                #    d.l = rightEdge
-                #    d.x = d.l + bbox.width / 2 + 2
-                #    d.r = d.l + bbox.width + 5
                 else if (rads >= 4 * Math.PI / 4 && rads < 5 * Math.PI / 4)
                     d.r = leftEdge
                     d.x = d.r - bbox.width / 2 - 2


JVM: spring cleanup, remove createFreeVm

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -78,8 +78,6 @@
           (signature String, Function)
         createVmByNonce:
           (signature String, String, Function)
-        createFreeVm:
-          (signature String, Function)
         createSharedVm:
           (signature Function)
         setAlwaysOn:
@@ -275,51 +273,6 @@
         return {groupSlug, prefix, nickname, uid, type:'user', alias}
     return null

-  @createFreeVm = secure (client, stackId, callback)->
-
-    @fetchDefaultVm client, (err, vm)=>
-
-      return callback err  if err
-
-      { delegate: account } = client.connection
-
-      unless vm
-
-        JGroup = require './group'
-        JGroup.one slug:'koding', (err, group)=>
-
-          account.fetchUser (err, user) =>
-            return callback err  if err
-            return callback new Error "user not found" unless user
-
-            nameFactory = (require 'koding-counter')
-              db          : JVM.getClient()
-              offset      : 0
-              counterName : "koding~#{user.username}~"
-
-            nameFactory.next (err, uid)=>
-              return console.error err  if err
-              # Counter created
-
-              @addVm {
-                uid
-                user
-                account
-                stack     : stackId
-                sudo      : yes
-                type      : 'user'
-                target    : account
-                planCode  : 'free'
-                groupSlug : group.slug
-                planOwner : "user_#{account._id}"
-                webHome   : account.profile.nickname
-                groups    : wrapGroup group
-              }, callback
-
-      else
-
-        callback new KodingError('Default VM already exists'), vm
-
   @createVmByNonce = secure (client, nonce, stackId, callback) ->
     JPaymentFulfillmentNonce  = require './payment/nonce'
     JPaymentPack              = require './payment/pack'


virt: bugfix: grant sudo to new members if "member" role has "sudoer" permission

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -56,8 +56,8 @@
         if err then handleError err
         else
           addVm {
+            user
             target  : group
-            user    : user
             sudo    : yes
             name    : group.slug
             groups  : wrapGroup group
@@ -72,19 +72,23 @@
       member.fetchUser (err, user)->
         if err then handleError err
         else if group.slug is 'koding'
+          # TODO: this special case for koding should be generalized for any group.
           addVm {
+            user
             target  : member
-            user    : user
             sudo    : yes
             name    : member.profile.nickname
           }
         else
-          group.fetchVms (err, vms)->
+          member.checkPermission group, 'sudoer', (err, hasPermission)->
             if err then handleError err
-            else vms.forEach (vm)->
-              vm.update {
-                $addToSet: users: { id: user.getId(), sudo: no }
-              }, handleError
+            else
+              group.fetchVms (err, vms)->
+                if err then handleError err
+                else vms.forEach (vm)->
+                  vm.update {
+                    $addToSet: users: { id: user.getId(), sudo: hasPermission }
+                  }, handleError

     JGroup.on 'MemberRemoved', ({group, member})->
       member.fetchUser (err, user)->
@@ -105,7 +109,7 @@
           JVM.update { groups: group.getId() }, { $pull: id: user.getId() }, handleError

     JGroup.on 'MemberRolesChanged', ({group, member})->
-      return  if group.slug 'koding'
+      return  if group.slug 'koding'  # TODO: remove this special case
       member.fetchUser (err, user)->
         if err then handleError err
         else


:bug: need to clear hover when `;` is executed if there is no previous find command.

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -22,6 +22,7 @@
       @processing = true
       @process()
     catch error
+      @vimState.reset()
       throw error unless error.instanceof?('OperationAbortedError')
     finally
       @processing = false


Keep all current parameters

Keep all current parameters

--- resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
@@ -70,7 +70,8 @@
   # don't forget to update BeatmapDiscussionsController@show
   # when changing this.
   @url: ({discussionId, beatmapId, page, filter} = {}) =>
-    params = new URLSearchParams
+    url = new URL(document.location)
+    params = url.searchParams

     if beatmapId?
       params.set 'beatmap_id', beatmapId
@@ -81,11 +82,8 @@
     if filter? && filter != DEFAULT_FILTER
       params.set 'filter', filter

-    hash = if discussionId? then "#/#{discussionId}" else ''
-
-    url = new URL(document.location)
-    url.hash = hash
-    url.search = params.toString()
+    if discussionId?
+      url.hash = "/#{discussionId}"

     url.toString()


add --nomultiline flag for ag to confirm line based search

--- lib/provider/search.coffee
+++ lib/provider/search.coffee
@@ -114,15 +114,16 @@
     else
       args.push('--case-sensitive')

-    # See #176
-    # rg doesn't show filePath on each line when search file was passed explicitly.
-    # Following option make result-output consistent with `ag`.
-    if command is 'rg'
-      args.push(['-H', '--no-heading']...)
-      args.push('--regexp')
-      args.push(unescapeRegExpForRg(@searchRegExp.source))
-    else
-      args.push(@searchRegExp.source)
+    switch command
+      when 'ag'
+        args.push('--nomultiline')
+        args.push(@searchRegExp.source)
+      when 'rg'
+        # See #176
+        # rg doesn't show filePath on each line when search file was passed explicitly.
+        # Following option make result-output consistent with `ag`.
+        args.push(['-H', '--no-heading', '--regexp']...)
+        args.push(unescapeRegExpForRg(@searchRegExp.source))
     args

   searchFilePath: (filePath) ->


Call setModel after fetching model

Call setModel after fetching model

Needed so the commands can notify observers

--- client/lanes/react/mixins/Screen.coffee
+++ client/lanes/react/mixins/Screen.coffee
@@ -1,4 +1,5 @@
 Lanes.React.Mixins.Screen = {
+
     childContextTypes:
         screen: React.PropTypes.object

@@ -12,8 +13,7 @@
             if options.attribute and @props.args?.length
                 model.fetch(_.extend( {}, options.syncOptions, {
                     query: {"#{options.attribute}": @props.args[0]}
-                }))
+                })).then => @state?.commands?.setModel(model)
             model

-
 }


Stagger fade in for content on list pages

--- client/source/views/entries/list.coffee
+++ client/source/views/entries/list.coffee
@@ -12,3 +12,18 @@
   getTemplateData: ->
     _.extend super,
       bucket: @bucket.toJSON()
+
+  render: ->
+    super
+
+    @$('.center, .entry').each (i, el) ->
+
+      TweenLite.from el, .2,
+        opacity: 0
+        # scale: .99
+        delay: i * .05
+        y: 15
+        ease: Cubic.easeInOut
+        # delay: .15
+        # ease: Elastic.easeOut
+        # easeParams: [.5]


throw clearer error message when loading model fails

--- lib/services/loadDb.coffee
+++ lib/services/loadDb.coffee
@@ -37,7 +37,14 @@
           # convert objectIDs to strings
           schema.path('_id').get (_id) -> _id.toString()

-          db.model name, schema
+          try
+            db.model name, schema
+          catch e
+            msg = "Failed loading model '#{name}'.\n"
+            err = new Error msg + e.message
+            err.stack = msg + e.stack
+            throw err
+
           names.push name

       @log.info "Loaded models:", names


Remove semicolons

Remove semicolons

--- coffee/views/game/game_play_view.coffee
+++ coffee/views/game/game_play_view.coffee
@@ -2,12 +2,12 @@
   constructor: ->
     super
     @set('fps', 0)
-    @fpscounter = 0;
+    @fpscounter = 0
     @blockSize = 25

     setInterval =>
       @set('fps', @fpscounter)
-      @fpscounter = 0;
+      @fpscounter = 0
     , 1000

     @shaders = {}
@@ -16,12 +16,12 @@
     @boardHeight = 20
     @board = new Array(@boardWidth * @boardHeight * 4)

-    @playerOneWidth = 2;
-    @playerOneHeight = 3;
+    @playerOneWidth = 2
+    @playerOneHeight = 3
     @playerOne = new Array(@playerOneWidth * @playerOneHeight * 4)

-    @playerTwoWidth = 3;
-    @playerTwoHeight = 3;
+    @playerTwoWidth = 3
+    @playerTwoHeight = 3
     @playerTwo = new Array(@playerTwoWidth * @playerTwoHeight * 4)

     for x in [0...@boardWidth] by 1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -4,6 +4,8 @@
   milliseconds = 300

   setTimeout ->
+    log animation
+
     equals animation.currentSprite(), animation.frames()[0], "Animation should default to initial sprite"

     animation.update()


Fix reject

Fix reject

--- views/services/plugin-manager.coffee
+++ views/services/plugin-manager.coffee
@@ -298,7 +298,7 @@
   # @return {Promise<>}
   updatePlugin: (plugin) ->
     @getMirrors().then =>
-      new Promise (resolve) =>
+      new Promise (resolve, reject) =>
         npm.commands.install ["#{plugin.packageName}@#{plugin.lastestVersion}"], (err) =>
           plugin.isUpdating = false
           if !err then resolve() else reject()
@@ -308,7 +308,7 @@
   # @return {Promise<Plugin>}
   installPlugin: (name) ->
     @getMirrors().then =>
-      new Promise (resolve) =>
+      new Promise (resolve, reject) =>
         npm.commands.install [name], (err) =>
           plugin = @readPlugin_ path.join @pluginPath, 'node_modules', name
           @plugins_.push plugin


Missed an admin -> siteAdmin switch

Missed an admin -> siteAdmin switch

--- app/assets/javascripts/controllers/task_controller.js.coffee
+++ app/assets/javascripts/controllers/task_controller.js.coffee
@@ -6,7 +6,7 @@
   isUserEditable: Ember.computed 'paper.editable', 'isMetadata', ->
     @get('paper.editable') || !@get('isMetadata')

-  isCurrentUserAdmin: Ember.computed.alias 'controllers.application.currentUser.admin'
+  isCurrentUserAdmin: Ember.computed.alias 'controllers.application.currentUser.siteAdmin'
   isEditable: Ember.computed.or('isUserEditable', 'isCurrentUserAdmin')

   # This will get overriden in setupController


Fix for radio buttons on new project page

--- app/assets/javascripts/templates/project.templates.js.coffee
+++ app/assets/javascripts/templates/project.templates.js.coffee
@@ -74,8 +74,8 @@
           Payment Frequency
         </label>
         <radiogroup data-model=project data-attribute=payment_frequency>
-          <input data-model=project data-attribute=payment_frequency type="radio" checked="checked" value="monthly"> Monthly
-          <input data-model=project data-attribute=payment_frequency type="radio" value="total"> Total
+          <input data-model=project data-attribute=payment_frequency type="radio" name="project_payment" checked="checked" value="monthly"> Monthly
+          <input data-model=project data-attribute=payment_frequency type="radio" name="project_payment" value="total"> Total
         </radiogroup>
       </div>


Add a proxy to `AnalyticsRouter` for `/recentTeamActivity`

--- services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee
+++ services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee
@@ -12,3 +12,7 @@
 		publicApiRouter.use '/analytics/graphs',
 			AuthenticationController.httpAuth,
 			AnalyticsProxy.call('/graphs')
+
+		publicApiRouter.use '/analytics/recentTeamActivity',
+			AuthenticationController.httpAuth,
+			AnalyticsProxy.call('/recentTeamActivity')


fix download methods in CompileController

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -44,11 +44,20 @@
 				}

 	_compileAsUser: (req, callback) ->
+		# callback with user_id if isolated flag is set on request, undefined otherwise
 		isolated = req.query?.isolated is "true"
 		if isolated
-			AuthenticationController.getLoggedInUserId req, callback
-		else
-			callback()
+			AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
+		else
+			callback() # do a per-project compile, not per-user
+
+	_downloadAsUser: (req, callback) ->
+		# callback with user_id if isolated flag or user_id param is set on request, undefined otherwise
+		isolated = req.query?.isolated is "true" or req.params.user_id?
+		if isolated
+			AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
+		else
+			callback() # do a per-project compile, not per-user

 	downloadPdf: (req, res, next = (error) ->)->
 		Metrics.inc "pdf-downloads"
@@ -82,7 +91,9 @@
 					logger.log project_id:project_id, ip:req.ip, "rate limit hit downloading pdf"
 					res.send 500
 				else
-					CompileController.proxyToClsi(project_id, "/project/#{project_id}/output/output.pdf", req, res, next)
+					CompileController._downloadAsUser req, (error, user_id) ->
+						url = CompileController._getFileUrl project_id, user_id, req.params.build_id, "output.pdf"
+						CompileController.proxyToClsi(project_id, url, req, res, next)

 	deleteAuxFiles: (req, res, next) ->
 		project_id = req.params.Project_id
@@ -92,29 +103,37 @@
 				return next(error) if error?
 				res.sendStatus(200)

+	# this is only used by templates, so is not called with a user_id
 	compileAndDownloadPdf: (req, res, next)->
 		project_id = req.params.project_id
-		CompileController._compileAsUser req, (error, user_id) ->
-			return next(error) if error?
-			CompileManager.compile project_id, user_id, {}, (err)->
-				if err?
-					logger.err err:err, project_id:project_id, "something went wrong compile and downloading pdf"
-					res.sendStatus 500
-				url = "/project/#{project_id}/output/output.pdf"
-				CompileController.proxyToClsi project_id, url, req, res, next
+		# pass user_id as null, since templates are an "anonymous" compile
+		CompileManager.compile project_id, null, {}, (err)->
+			if err?
+				logger.err err:err, project_id:project_id, "something went wrong compile and downloading pdf"
+				res.sendStatus 500
+			url = "/project/#{project_id}/output/output.pdf"
+			CompileController.proxyToClsi project_id, url, req, res, next

 	getFileFromClsi: (req, res, next = (error) ->) ->
 		project_id = req.params.Project_id
-		build_id = req.params.build_id
-		user_id = req.params.user_id
+		CompileController._downloadAsUser req, (error, user_id) ->
+			return next(error) if error?
+			url = CompileController._getFileUrl project_id, user_id, req.params.build_id, req.params.file
+			CompileController.proxyToClsi(project_id, url, req, res, next)
+
+	# compute a GET file url for a given project, user (optional), build (optional) and file
+	_getFileUrl: (project_id, user_id, build_id, file) ->
 		if user_id? and build_id?
-			url = "/project/#{project_id}/user/#{user_id}/build/#{build_id}/output/#{req.params.file}"
+			url = "/project/#{project_id}/user/#{user_id}/build/#{build_id}/output/#{file}"
+		else if user_id?
+			url = "/project/#{project_id}/user/#{user_id}/output/#{file}"
 		else if build_id?
-			url = "/project/#{project_id}/build/#{build_id}/output/#{req.params.file}"
-		else
-			url = "/project/#{project_id}/output/#{req.params.file}"
-		CompileController.proxyToClsi(project_id, url, req, res, next)
-
+			url = "/project/#{project_id}/build/#{build_id}/output/#{file}"
+		else
+			url = "/project/#{project_id}/output/#{file}"
+		return url
+
+	# compute a POST url for a project, user (optional) and action
 	_getUrl: (project_id, user_id, action) ->
 		path = "/project/#{project_id}"
 		path += "/user/#{user_id}" if user_id?


add plugin task to install cordova plugin

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -1,15 +1,15 @@
-gulp = require('gulp')
-gutil = require('gulp-util')
-bower = require('bower')
-concat = require('gulp-concat')
-sass = require('gulp-sass')
-minifyCss = require('gulp-minify-css')
-rename = require('gulp-rename')
-sh = require('shelljs')
-coffee = require('gulp-coffee')
-browserify = require('gulp-browserify')
-bower = require('gulp-bower')
-less = require('gulp-less')
+gulp = require 'gulp'
+gutil = require 'gulp-util'
+bower = require 'bower'
+concat = require 'gulp-concat'
+sass = require 'gulp-sass'
+minifyCss = require 'gulp-minify-css'
+rename = require 'gulp-rename'
+sh = require 'shelljs'
+coffee = require 'gulp-coffee'
+browserify = require 'gulp-browserify'
+bower = require 'gulp-bower'
+less = require 'gulp-less'

 paths = sass: ['./scss/**/*.scss']

@@ -31,6 +31,10 @@
     .pipe(browserify({transform : [ 'debowerify' ]}))
   	.pipe(gulp.dest('./www/js/'))

+gulp.task 'plugin', ->
+  for plugin in require('./package.json').cordovaPlugins
+  	sh.exec "cordova plugin add #{plugin}"
+
 gulp.task 'watch', ->
   gulp.watch(paths.sass, ['sass'])


GithubAPI: use Github init for feature method requirements

--- workers/social/lib/social/models/github.coffee
+++ workers/social/lib/social/models/github.coffee
@@ -9,6 +9,7 @@
 module.exports = class Github extends Base

   OAUTH_PROVIDER = 'github'
+  USER_AGENT     = 'Koding-Bridge'

   @trait __dirname, '../traits/protected'

@@ -29,6 +30,21 @@
         fetchContent      :
           (signature Object, Function)

+  initGithubFor = (client) ->
+
+    { oauth: {token} } = client.r
+
+    gh = new GithubAPI
+      version        : '3.0.0'
+      debug          : true
+      timeout        : 5000
+      headers        :
+        'user-agent' : USER_AGENT
+
+    gh.authenticate { type: 'oauth', token }
+
+    return gh
+

   @listRepos = permit 'list repos', success: revive

@@ -37,21 +53,10 @@

   , (client, options, callback) ->

-    { oauth: {foreignId}, user: {username} } = client.r
+    initGithubFor client

-    gh = new GithubAPI
-      version        : '3.0.0'
-      debug          : true
-      timeout        : 5000
-      headers        :
-        'user-agent' : 'Koding-Bridge'
-
-    gh.authenticate
-      type           : 'oauth'
-      token          : foreignId
-
-    gh.repos.getAll {}, (err, res) ->
-      callback err, res
+      .repos.getAll {}, (err, res) ->
+        callback err, res

   @fetchContent = permit 'fetch content', success: revive


Use requested API for by_url

Use requested API for by_url

--- lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
+++ lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
@@ -68,7 +68,7 @@
             # Let the caller handle what happens after the request
             # via callbacks
             importUrl: (url, callbacks={}) ->
-                $.getJSON(outpost.ContentAPI.ContentCollection.prototype.url + "/by_url", { url: url })
+                $.getJSON(outpost.ContentAPI[@options.apiClass].prototype.url + "/by_url", { url: url })
                    .success((data, textStatus, jqXHR)      -> callbacks.success?(data))
                    .error((jqXHR, textStatus, errorThrown) -> callbacks.error?(jqXHR))
                    .complete((jqXHR, status)               -> callbacks.complete?(jqXHR))


At the very least check if it can be edited

--- lib/written/app/assets/javascripts/written/core/content.coffee
+++ lib/written/app/assets/javascripts/written/core/content.coffee
@@ -16,6 +16,9 @@
   initialize: =>
     if @parsers?
       return
+
+    if @element().contentEditable != 'true'
+      @element().contentEditable = 'true'

     Written.Parsers.freeze()


fucking spaces

fucking spaces

--- scripts/contestant-msg-member-nag.coffee
+++ scripts/contestant-msg-member-nag.coffee
@@ -36,9 +36,9 @@
     recipients: address
     template: template_name
     variables:
-      team: " #{currentTeam.name}"
-      team_id: " #{currentTeam.id}"
-      slug: " #{currentTeam.slug}"
+      team: "#{currentTeam.name}"
+      team_id: "#{currentTeam.id}"
+      slug: "#{currentTeam.slug}"
     , (args...) ->
       # console.log "completed sending"
       # console.log args


oauth: filter out noreply.github emails

oauth: filter out noreply.github emails

--- server/lib/server/github_callback.coffee
+++ server/lib/server/github_callback.coffee
@@ -73,8 +73,10 @@
     rawResp = ""
     userEmailResp.on "data", (chunk) -> rawResp += chunk
     userEmailResp.on "end", ->
-      email = JSON.parse(rawResp)[0]
-      originalResp.email = email
+      emails = JSON.parse(rawResp)
+      emails = emails.filter (email)-> !/noreply.github/.test email
+
+      originalResp.email = emails[0]

       saveOauthAndRenderPopup originalResp, res, clientId


fixed and refactored memoize

fixed and refactored memoize

--- node_modules_koding/domainer/lib/reseller/DomainService.coffee
+++ node_modules_koding/domainer/lib/reseller/DomainService.coffee
@@ -5,22 +5,23 @@
 # and a callback
 memoize = (func) ->
   ttl = 1000
-  lastQueryTime = new Date().getTime() - ttl
+  lastQueryTime = 0
   memo = undefined
   (callback) ->
     current = new Date().getTime()
-    if lastQueryTime <= current - ttl
+    if lastQueryTime < current - ttl
       lastQueryTime = current
       func.call this, (result) ->
         memo = result
         callback memo
+      return
     return callback memo

 splitDomain = (domainName) ->
   splitted =  domainName.split "."
   return {
-    first: splitted[0..-2],
-    extension: splitted[splitted.length-1] ? splitted.length > 1 : ""
+    first: splitted[0],
+    extension: if splitted.length > 1 then splitted[1..].join "." else ""
   }

 module.exports = class DomainService extends ServiceManager
@@ -49,36 +50,30 @@
       service    : "domains"
       action     : "available"
       parameters : params
-
     @makeRequestToResellerApi options, (err, apiResponseBody) =>
-      domainKey   = "#{domainName}.#{tld}"
+      domainKey   = domainName + "." + tld
       status      = apiResponseBody?[domainKey]?.status
       @getTldPrice tld, (price) =>
-        @getDomainSuggestion domainName, "tld", (suggestions)->
-          console.log(status, price, suggestions)
+        @getDomainSuggestion domainName, tld, (suggestions)->
           callback err, status, price, suggestions

   getDomainSuggestion: (domainName, tld, callback) ->
+    suggestions = []
+    collectResults = race (i, item, fin) =>
+      @getTldPrice splitDomain(item.domain).extension, (price) ->
+        suggestions.push {domain:item.domain, price:price}
+        fin()
+    , ->
+      callback suggestions
+
     domainr.search domainName, (suggestionResponse) =>
-      console.log(suggestionResponse)
-      suggestions = []
-      collect = race (i, item, fin) =>
-        @getTldPrice splitDomain(item.domain).extension, (price) ->
-          console.log(splitDomain(item.domain).extension, item)
-          console.log(price)
-          suggestions.push {domain:item.domain, price:price}
-          fin()
-      , ->
-        console.log(domainName)
-        console.log("exit:", suggestions)
-        callback suggestions
-      console.log("asdsad:"+domainName + "." + tld)
       suggestionResponse.results = (result for result in suggestionResponse.results when \
         result.availability is "available" and
         splitDomain(result.domain).extension in availableTLDs and
         result.domain isnt domainName + "." + tld)
       for domain in suggestionResponse.results
-          collect domain
+          collectResults domain
+
       if suggestionResponse.results.length is 0
         callback suggestions

@@ -108,6 +103,7 @@
           priceList[currentTld] = value[0].pricing.addnewdomain[1]
       return callback priceList

+
   registerDomain: (domainInfo, callback)->
     #invoice option may change, but for now NoInvoice parameter is given,
     params =


Fix failing test with implicit order.

Fix failing test with implicit order.

--- spec/javascripts/models/page_spec.coffee
+++ spec/javascripts/models/page_spec.coffee
@@ -45,6 +45,8 @@

     it 'can grab all the roots', ->
       roots = CMSimple.Page.roots()
+      titles = _.compact(_.pluck(roots, 'title'))
       expect(roots.length).toEqual(2)
-      expect(_.compact(_.pluck(roots, 'title'))).toEqual(['Parent', 'Single'])
+      expect(titles).toContain('Parent')
+      expect(titles).toContain('Single')


Actually shows date bar in bucket detail view

--- app/scripts/directives/bucket-summary/bucket-summary-directive.coffee
+++ app/scripts/directives/bucket-summary/bucket-summary-directive.coffee
@@ -26,11 +26,7 @@
       days_left = $scope.hours_left / 24
       $scope.budget_time_message = "There are #{Math.floor(days_left)} days left in the round!"

-    console.log("hours left = #{$scope.hours_left}")
-    console.log("total hours=#{$scope.total_hours}")
-
     percent_complete = (1 - ($scope.hours_left / $scope.total_hours)) * 100
-    console.log("% complete = #{percent_complete}")

     percent_complete = 0 if current_time < start_time
     percent_complete = 100 if current_time > end_time