Use numer and denom in Fractions instead of num and den (class still broken)

--- lib/number.coffee
+++ lib/number.coffee
@@ -444,50 +444,50 @@

 # The 'Fraction' class implements rational numbers.

-class Fraction
-  constructor: (@num, @den) ->
-
-  @normalized = (num, den) ->
-    if den.eq 0
-      throw new Error "expected a non-zero denominator, got #{den}"
-    else if den.lt 0
-      Fraction.normalized num.neg(), den.neg()
-    else
-      a = num.gcd den
-      new Fraction num.div(a), den.div(a)
-
-  neg__: -> new Fraction @num.neg(), @den
-  abs__: -> new Fraction @num.abs(), @den
-  sgn__: -> @num.sgn()
-  inv__: -> normalized @den, @num
-
-  isPos__: -> @num.isPos()
-  isNeg__: -> @num.isNeg()
-  isZero__: -> @num.isZero()
-
-  isEven__: -> @den.eq(1) and @num.isEven()
-  isOdd__: -> @den.eq(1) and @num.isOdd()
-
-  cmp__: (x) -> @minus__(x).num.cmp 0
+class Fraction extends NumberBase
+  constructor: (@numer, @denom) ->
+
+  @normalized = (n, d) ->
+    if d.eq 0
+      throw new Error "expected a non-zero denominator, got #{d}"
+    else if d.lt 0
+      Fraction.normalized n.neg(), d.neg()
+    else
+      a = num.gcd n, d
+      new Fraction n.div(a), d.div(a)
+
+  neg__: -> new Fraction @numer.neg(), @denom
+  abs__: -> new Fraction @numer.abs(), @denom
+  sgn__: -> @numer.sgn()
+  inv__: -> Fraction.normalized @denom, @numer
+
+  isPos__: -> @numer.isPos()
+  isNeg__: -> @numer.isNeg()
+  isZero__: -> @numer.isZero()
+
+  isEven__: -> @denom.eq(1) and @numer.isEven()
+  isOdd__: -> @denom.eq(1) and @numer.isOdd()
+
+  cmp__: (x) -> @minus__(x).numer.cmp 0

   plus__: (x) ->
-    a = num.gcd @den, x.den
-    s = num.div x.den, a
-    t = num.div @den, a
-    Fraction.normalized s.times(@num).plus(t.times(x.num)), s.times @den
+    a = num.gcd @denom, x.denom
+    s = num.div x.denom, a
+    t = num.div @denom, a
+    Fraction.normalized s.times(@numer).plus(t.times(x.numer)), s.times @denom

   minus__: (x) -> @plus__ x.neg__()

   times__: (x) ->
-    a = num.gcd @num, x.den
-    b = num.gcd @den, x.num
-    n = @num.div(a).times(x.num.div(b))
-    d = @den.div(b).times(x.den.div(a))
+    a = num.gcd @numer, x.denom
+    b = num.gcd @denom, x.numer
+    n = @numer.div(a).times(x.numer.div(b))
+    d = @denom.div(b).times(x.denom.div(a))
     Fraction.normalized n, d

   by__: (x) -> @times__ x.inv__()

-  toString: -> if @den.eq 1 then "#{@num}" else "#{@num}/#{@den}"
+  toString: -> if @denom.eq 1 then "#{@numer}" else "#{@numer}/#{@denom}"

 # ----
 # Exporting.


Include aliases in device objects when querying

--- api.coffee
+++ api.coffee
@@ -62,6 +62,21 @@
   task.device = device
   task.timestamp = new Date()
   return task
+
+
+addAliases = (device) ->
+  for k,v of config.ALIASES
+    for p in v
+      pp = p.split('.')
+      obj = device
+      for i in pp
+        if not obj[i]?
+          obj = null
+          break
+        obj = obj[i]
+
+      device[k] = obj if obj?
+

 cluster = require 'cluster'
 numCPUs = require('os').cpus().length
@@ -315,6 +330,7 @@
           if item is null
             response.end(']')
           else
+            addAliases(item) if collectionName is 'devices'
             response.write(JSON.stringify(item) + ",\n")
         )
         return


just hangup

just hangup

--- app.coffee
+++ app.coffee
@@ -28,8 +28,14 @@

   send_xml res, twiml

+app.post '/hangup', (req, res) ->
+  twiml = new twilio.TwimlResponse()
+  twiml.hangup()
+  senx_xml res, twiml
+
 app.post '/ringonce', (req, res) ->
   twiml = new twilio.TwimlResponse()
+  twiml.redirect '/hangup'
   if req.body['DialCallStatus'] == 'completed'
     console.log req
     console.log req.body


Support better mixing of Markdown and LaTeX by omitting another HTML blocks rule

Followup to 5843931c129c587557734bc15eed10ccbbd1d532

--- lib/markdown.coffee
+++ lib/markdown.coffee
@@ -12,12 +12,13 @@
     @markdownInline = (text) -> marked.inlineLexer text, {}, marked.defaults

   when 'markdown-it'
-    ## Remove the HTML blocks rule from CommonMark
+    ## Remove the 6th and 7th HTML blocks rule from CommonMark
     ## [https://spec.commonmark.org/0.29/#html-blocks], which prevents
-    ## Markdown processing in blocks that begin with specific HTML tags.
+    ## Markdown processing in blocks that begin with HTML tags.
     ## This rule interacts poorly with LaTeX, e.g., \item **bold**.
     blocks = require('markdown-it/lib/common/html_blocks')
     blocks[..] = []
+    require('markdown-it/lib/common/html_re').HTML_OPEN_CLOSE_TAG_RE = /\0\0\0/

     @hljs = require 'highlight.js'
     @markdownIt = require('markdown-it')


Insert Dropbox uploader into workflow

Insert Dropbox uploader into workflow

--- app.coffee
+++ app.coffee
@@ -56,10 +56,16 @@

   # In the future, we should queue this bad boy up.
   gpx = new GpxDownloader(parser.gpxLink())
+  key = process.env.DROPBOX_KEY
+  secret = process.env.DROPBOX_SECRET
+  token = process.env.DROPBOX_SECRET_TOKEN
+  dropboxUploader = new DropboxUploader(key, secret, token)
   stravaUploader = new StravaUploader
   gpx.download((filePath) ->
-    stravaUploader.upload(filePath, ->
-      mailer.sendMail("hello", "gpx file attached", filePath)
+    dropboxUploader.upload(filePath, ->
+      stravaUploader.upload(filePath, ->
+        mailer.sendMail("hello", "gpx file attached", filePath)
+      )
     )
   )
   response.send("OK")


Tweak copy

Tweak copy

--- app.coffee
+++ app.coffee
@@ -115,8 +115,8 @@
         found: (conversation) -> processMessage(conversation)
         notFound: () ->
             createNewUser sender, (conversation) ->
-                sendSMS(sender, "[OPERATIONS MANUAL: Using complex nudity-detection algorithms, Sext Machine is programmed to feel arousal proportional to the likelihood a photo is X-rated.")
-                sendSMS(sender, "Without sending actual explicit photos, arouse the unit by taking and sending pictures with your phone camera that it believes to contain nudity.]")
+                sendSMS(sender, "[OPERATIONS MANUAL: Using complex nudity-detection algorithms, Sext Machine is programmed to feel arousal proportional to the likelihood a photo has inappropriate content.")
+                sendSMS(sender, "Trick the unit into being aroused by taking and sending pictures with your phone camera that aren't x-rated, but it believes are.]")
                 sendSMS(sender, "how u doin bae? ;)")

 app.listen process.env.PORT || 3000


fixes 404.

fixes 404.

--- app.coffee
+++ app.coffee
@@ -34,8 +34,7 @@

 # 404 pages
 app.use (req, res) ->
-  res.status(404).send('Sorry cant find that!');
-});
+  res.status(404).send 'Sorry cant find that!'

 app.use errorhandler()


Rename to WRBacklog

Rename to WRBacklog

--- app.coffee
+++ app.coffee
@@ -26,7 +26,7 @@
   stop: () ->

-class WireRoomBacklog
+class WRBacklog

   @services: []

@@ -91,7 +91,7 @@

     @db = new mongolian('mongo://127.0.0.1:27017/wireroom')

-    @backlog = new WireRoomBacklog(@db)
+    @backlog = new WRBacklog(@db)

     RedisStore = require('socket.io/lib/stores/redis')
     @redis  = require('socket.io/node_modules/redis')


update sounds

update sounds

--- app.coffee
+++ app.coffee
@@ -29,6 +29,7 @@
 everyone = now.initialize(server)
 everyone.now.distributeMessage = (message) ->
   everyone.now.receiveMessage(this.now.name, message)
+  play.sound('./public/wavs/sfx/ding.wav')

 everyone.connected(  (message) ->
@@ -39,7 +40,7 @@

 everyone.now.updateElement = (className, top, left) ->
   everyone.now.receiveElement(className, top, left)
-  play.sound('./public/wavs/sfx/ding.wav')
+  play.sound('./public/wavs/drums/kick.wav')

 everyone.now.distributeMoveMessage = (message) ->
   everyone.now.receiveMessage(this.now.name, message)


serve assets from the roots project

serve assets from the roots project

--- app.coffee
+++ app.coffee
@@ -1,4 +1,5 @@
 express = require('express')
+path = require('path')
 stylus = require('stylus')
 axis = require('axis-css')
 grate = require('grate')
@@ -23,15 +24,23 @@
   dest: __dirname + '/public'
 )

+# allow express to parse body params
+app.use(express.bodyParser())

-app.use(express.bodyParser())
+# serve assets for roots-cms
 app.use(express.static("#{__dirname}/public"))

+# serve assets for the roots project
+app.use(express.static(path.join(config.project_dir, 'assets')))
+
+# set up basic auth if enabled
 if config.basic_auth
   app.use(express.basicAuth(config.basic_auth.username, config.basic_auth.password))

+# load the api
 app.use(require('./api'))

+# respond to all other requests with the marionette SPA
 app.get "*", (req, res) ->
   res.render 'index'


:art: revert displayName as prototype variable.

Using class variable to avoid inheritance is not work(was
misunderstood), class var is inherited and directly copied at class
creation, so I can’t use hasOwnProperty guard like prototype variable.
So I reverted to original prototype variable for ‘displayName’
information.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -196,10 +196,11 @@
 # -------------------------
 class ToggleCase extends TransformString
   @extend()
-  @displayName: 'Toggle ~'
+  displayName: 'Toggle ~'
   hover: icon: ':toggle-case:', emoji: ':clap:'
   toggleCase: (char) ->
-    if (charLower = char.toLowerCase()) is char
+    charLower = cahr.toLowerCase()
+    if charLower is char
       char.toUpperCase()
     else
       charLower
@@ -215,55 +216,55 @@

 class UpperCase extends TransformString
   @extend()
-  @displayName: 'Upper'
+  displayName: 'Upper'
   hover: icon: ':upper-case:', emoji: ':point_up:'
   getNewText: (text) ->
     text.toUpperCase()

 class LowerCase extends TransformString
   @extend()
-  @displayName: 'Lower'
+  displayName: 'Lower'
   hover: icon: ':lower-case:', emoji: ':point_down:'
   getNewText: (text) ->
     text.toLowerCase()

 class CamelCase extends TransformString
   @extend()
-  @displayName: 'Camelize'
+  displayName: 'Camelize'
   hover: icon: ':camel-case:', emoji: ':camel:'
   getNewText: (text) ->
-    _.camelize text
+    _.camelize(text)

 class SnakeCase extends TransformString
   @extend()
-  @displayName: 'Underscore _'
+  displayName: 'Underscore _'
   hover: icon: ':snake-case:', emoji: ':snake:'
   getNewText: (text) ->
-    _.underscore text
+    _.underscore(text)

 class DashCase extends TransformString
   @extend()
-  @displayName: 'Dasherize -'
+  displayName: 'Dasherize -'
   hover: icon: ':dash-case:', emoji: ':dash:'
   getNewText: (text) ->
-    _.dasherize text
+    _.dasherize(text)

 class TitleCase extends TransformString
   @extend()
-  @displayName: 'Titlize'
+  displayName: 'Titlize'
   getNewText: (text) ->
     _.humanizeEventName(_.dasherize(text))

 class EncodeUriComponent extends TransformString
   @extend()
-  @displayName: 'Encode URI Component %'
+  displayName: 'Encode URI Component %'
   hover: icon: 'encodeURI', emoji: 'encodeURI'
   getNewText: (text) ->
     encodeURIComponent(text)

 class DecodeUriComponent extends TransformString
   @extend()
-  @displayName: 'Decode URI Component %%'
+  displayName: 'Decode URI Component %%'
   hover: icon: 'decodeURI', emoji: 'decodeURI'
   getNewText: (text) ->
     decodeURIComponent(text)
@@ -353,7 +354,8 @@
   getItems: ->
     @transformers.map (klass) ->
       klass = Base.getClass(klass) if _.isString(klass)
-      displayName = klass.displayName ? _.humanizeEventName(_.dasherize(klass.name))
+      displayName = klass::displayName if klass::hasOwnProperty('displayName')
+      displayName ?= _.humanizeEventName(_.dasherize(klass.name))
       {name: klass, displayName}

   initialize: ->
@@ -420,7 +422,7 @@
 # -------------------------
 class Surround extends TransformString
   @extend()
-  @displayName: "Surround ()"
+  displayName: "Surround ()"
   pairs: [
     ['[', ']']
     ['(', ')']
@@ -750,7 +752,7 @@
 # -------------------------
 class IncrementNumber extends Operator
   @extend()
-  @displayName: 'Increment ++'
+  displayName: 'Increment ++'
   step: 1
   baseNumber: null

@@ -784,7 +786,7 @@

 class DecrementNumber extends IncrementNumber
   @extend()
-  @displayName: 'Decrement --'
+  displayName: 'Decrement --'
   step: -1

 # Put


:memo:

:memo:

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -627,10 +627,12 @@
     newRange = super
     # Adjust indentLevel with keeping original layout of pasting text.
     # Suggested indent level of newRange.start.row is correct as long as newRange.start.row have minimum indent level.
-    # When we paste following already indented two-line text, we can't apply suggested level naively.
+    # But when we paste following already indented three line text, we have to adjust indent level
+    #  so that `varFortyTwo` line have suggestedIndentLevel.
     #
-    #        varOne: value
-    #   varFortyTwo: value
+    #        varOne: value # suggestedIndentLevel is determined by this line
+    #   varFortyTwo: value # We need to make final indent level of this row to be suggestedIndentLevel.
+    #      varThree: value
     #
     # So what we are doing here is apply suggestedIndentLevel with fixing issue above.
     # 1. Determine minimum indent level among pasted range(= newRange ) excluding empty row


Search completion; fix Merriam Webster autocomplete

--- background_scripts/completion_engines.coffee
+++ background_scripts/completion_engines.coffee
@@ -142,14 +142,15 @@
 class Webster extends BaseEngine
   constructor: ->
     super
-      engineUrl: "https://www.merriam-webster.com/autocomplete?query=%s"
+      engineUrl: "https://www.merriam-webster.com/lapi/v1/mwol-search/autocomplete?search=%s"
       regexps: "^https?://www.merriam-webster.com/dictionary/"
       example:
         searchUrl: "https://www.merriam-webster.com/dictionary/%s"
         keyword: "dw"
         description: "Dictionary"

-  parse: (xhr) -> JSON.parse(xhr.responseText).suggestions
+  parse: (xhr) ->
+    suggestion.word for suggestion in JSON.parse(xhr.responseText).docs

 class Qwant extends BaseEngine
   constructor: ->


possible falsy could cause duplicate entries

--- lib/server.coffee
+++ lib/server.coffee
@@ -392,7 +392,7 @@
 								jobsCounter[0] = getbody.total_rows
 								if getbody.rows and getbody.rows.length > 0
 									for act in getbody.rows
-										if !findinque(act.id)
+										if findplace(act.id) is -1
 											act._status = "new"
 											act._lastchange =  ~~((new Date).getTime() / 1000)
 											workque.push act


is _status

is _status

--- lib/server.coffee
+++ lib/server.coffee
@@ -486,7 +486,7 @@
 						if not found
 							if findplace(jobId) isnt -1 # update the job
 								currentjob = findinque(jobId)
-								if currentjob.status is "working" and (~~((new Date).getTime() / 1000) - currentjob._lastchange) < 300
+								if currentjob._status is "working" and (~~((new Date).getTime() / 1000) - currentjob._lastchange) < 300
 									# dont re-assign if it is working on another that has reported it was taken in the last 5 minutes
 									currentjob._otherhost ||= {}
 									currentjob._otherhost[host] = true


Return early when path is outside ~/.atom

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -29,20 +29,20 @@
       return name unless name is 'EditorView'

       itemPath = item.getPath?()
-      if itemPath and path.dirname(itemPath) is atom.getConfigDirPath()
-        extension = path.extname(itemPath)
-        switch path.basename(itemPath, extension)
-          when 'config'
-            name = 'UserConfig'     if extension in ['.json', '.cson']
-          when 'init'
-            name = 'UserInitScript' if extension in ['.js', '.coffee']
-          when 'keymap'
-            name = 'UserKeymap'     if extension in ['.json', '.cson']
-          when 'snippets'
-            name = 'UserSnippets'   if extension in ['.json', '.cson']
-          when 'styles'
-            name = 'UserStylesheet' if extension in ['.css', '.less']
+      return name unless path.dirname(itemPath) is atom.getConfigDirPath()

+      extension = path.extname(itemPath)
+      switch path.basename(itemPath, extension)
+        when 'config'
+          name = 'UserConfig'     if extension in ['.json', '.cson']
+        when 'init'
+          name = 'UserInitScript' if extension in ['.js', '.coffee']
+        when 'keymap'
+          name = 'UserKeymap'     if extension in ['.json', '.cson']
+        when 'snippets'
+          name = 'UserSnippets'   if extension in ['.json', '.cson']
+        when 'styles'
+          name = 'UserStylesheet' if extension in ['.css', '.less']
       name

     @sendPaneItem: (item) ->


Atom 1.0.2

Atom 1.0.2

--- def/menu_darwin.cson
+++ def/menu_darwin.cson
@@ -37,15 +37,15 @@
 				value: "開新視窗"
 			"New File":
 				value: "新增文件"
-			"Open...":
+			"Open…":
 				value: "打開..."
-			"Add Project Folder...":
+			"Add Project Folder…":
 				value: "新增專案資料夾..."
 			"Reopen Last Item":
 				value: "重新打開上一次關閉的文件..."
 			Save:
 				value: "儲存"
-			"Save As...":
+			"Save As…":
 				value: "另存新檔..."
 			"Save All":
 				value: "全部儲存"
@@ -255,7 +255,7 @@
 			Developer:
 				value: "開發人員"
 				submenu:
-					"Open In Dev Mode...":
+					"Open In Dev Mode…":
 						value: "在開發人員模式中開啟"
 					"Run Atom Specs":
 						value: "執行 Atom 規格測試"


Pass correct arguments to fastdev

Pass correct arguments to fastdev

--- lib/tintan.coffee
+++ lib/tintan.coffee
@@ -77,11 +77,11 @@
   @titan: (args ...)->
     @tipy.apply(this, [['titanium.py'], args])

-  @fastdev: (args ...)-> @titan.apply this, ['fastdev'].concat(args)
+  @fastdev: (args ...)=> @titan.apply this, ['fastdev'].concat(args || [])

   @tipy: (ary, args ..., cb)->
     unless cb instanceof Function
-      args.push cb
+      if args?.length then args.push cb else args = cb
       cb = ->
     tool = path.join.apply(path, [@home(), 'mobilesdk', @os, @sdk()].concat(ary))
     p = spawn @py(), [tool].concat(args)


Move constant variable outside of class definition

--- resources/assets/coffee/react/modding-profile/stats.coffee
+++ resources/assets/coffee/react/modding-profile/stats.coffee
@@ -3,18 +3,15 @@

 import * as React from 'react'
 import { div, dd, dl, dt } from 'react-dom-factories'
-el = React.createElement

+entries = [
+  'ranked_beatmapset_count'
+  'loved_beatmapset_count'
+  'pending_beatmapset_count'
+  'graveyard_beatmapset_count'
+]

 export class Stats extends React.PureComponent
-  entries = [
-    'ranked_beatmapset_count'
-    'loved_beatmapset_count'
-    'pending_beatmapset_count'
-    'graveyard_beatmapset_count'
-  ]
-
-
   render: =>
     div className: 'profile-stats', entries.map(@renderEntry)


WeakMap::clear is no longer available

WeakMap::clear is no longer available

Remove!

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -54,7 +54,7 @@
   deactivate: ->
     @emitter?.dispose()
     @emitter = null
-    @editorSnippetExpansions?.clear()
+    @editorSnippetExpansions = null
     atom.config.transact => @subscriptions.dispose()

   getUserSnippetsPath: ->


Removing obsolete polling test

Removing obsolete polling test

--- lesson_leaderboard_spec.coffee
+++ lesson_leaderboard_spec.coffee
@@ -1,68 +0,0 @@
-#= require lib
-#= require lib/ranks
-#= require handlebars.runtime
-#= require handlebars.helpers
-#= require templates/student_dashboard/leaderboard
-#= require templates/shared/_leaderboard_entry
-#= require decorators/lesson_leaderboard
-#= require mock-ajax
-
-class FakeBunchball
-  classId: ->
-    'classroom'
-
-  schoolId: ->
-    'school'
-
-  stateCode: ->
-    'PA'
-
-  avatarFor: (username)->
-    username
-
-class FakePoller
-  getData: ->
-    {}
-
-describe "Lesson Leaderboard", ->
-  beforeEach ->
-    @poller = new FakePoller
-    @pollerSpy = spyOn(@poller, 'getData')
-
-    @leaderboard = window.ttm.decorators.LessonLeaderboard.build {
-      bunchball: new FakeBunchball
-      poller: @poller
-    }
-
-  describe "When there are lesson leaders", ->
-    beforeEach ->
-      @leaders = { rows: [
-        {
-          student_name: 'first last'
-          student_first_name: 'first'
-          student_last_name: 'last'
-          bunchball_username: 'bunchball'
-          lessons_completed: 10
-        }
-      ] }
-
-    describe "When building the top 3", ->
-      beforeEach ->
-        f """
-

-

-

-                Loading...
-
-        expect($('#lessons .lesson_leaders .replaceable').length).toEqual(0)
-
-      it "should render the first place kid", ->
-        expect($('#lessons .lesson_leaders .row .first_place').length).toEqual(1)


Removing obsolete polling test

Removing obsolete polling test


:art: Rename references to improve readability

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -7,7 +7,7 @@
   initialize: (@item) ->
     @path = @item.getPath?()

-    @subscriptions = new CompositeDisposable()
+    @repoSubscriptions = new CompositeDisposable()

     @isPreviewTab = atom.config.get('tabs.usePreviewTabs') and typeof @item.getPath is 'function'

@@ -126,7 +126,7 @@
     @configSubscription?.dispose()
     @vcsConfigSubscription?.dispose()
     @savedSubscription?.dispose()
-    @subscriptions?.dispose()
+    @repoSubscriptions?.dispose()
     @destroyTooltip()
     @remove()

@@ -205,11 +205,11 @@
     return unless repo?

     # Remove previous repo subscriptions.
-    @subscriptions?.dispose()
-
-    @subscriptions.add repo.onDidChangeStatus (event) =>
+    @repoSubscriptions?.dispose()
+
+    @repoSubscriptions.add repo.onDidChangeStatus (event) =>
       @updateVcsStatus(repo, event.pathStatus) if event.path is @path
-    @subscriptions.add repo.onDidChangeStatuses =>
+    @repoSubscriptions.add repo.onDidChangeStatuses =>
       @updateVcsStatus(repo)

   repoForPath: (goalPath) ->


Updated test to activate coffee-script and set coffee-compile grammar

--- spec/coffee-compile-spec.coffee
+++ spec/coffee-compile-spec.coffee
@@ -6,6 +6,16 @@
     atom.workspaceView = new WorkspaceView
     atom.workspace     = atom.workspaceView.model
     spyOn CoffeeCompileView, "saveCompiled"
+
+    waitsForPromise "language-coffee-script to activate", ->
+      atom.packages.activatePackage('language-coffee-script')
+
+    atom.config.set('coffee-compile.grammars', [
+      'source.coffee'
+      'source.litcoffee'
+      'text.plain'
+      'text.plain.null-grammar'
+    ])

   it "should call savedCompiled", ->
     atom.config.set("coffee-compile.compileOnSaveWithoutPreview", true)


databases list kite api

databases list kite api

--- client/landing/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
+++ client/landing/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
@@ -26,9 +26,19 @@
         @getListView().showAddModal()

   loadItems:(callback)->
-    @account.fetchDatabases (err,databases)=>
-      @instantiateListItems databases
-      callback?()
+
+    @getSingleton("kiteController").run
+      kiteName  : "databases"
+      toDo      : "fetchMysqlDatabases"
+      withArgs  : { username : KD.whoami().profile.nickname}
+    , (err, response)=>
+      if err then warn err
+      else
+        log response, "<<<< db list"
+
+    # @account.fetchDatabases (err,databases)=>
+    #   @instantiateListItems databases
+    #   callback?()

 class AccountDatabaseList extends KDListView
   constructor:(options,data)->


Update tests for new behavior

Update tests for new behavior

--- spec/keymap-manager-spec.coffee
+++ spec/keymap-manager-spec.coffee
@@ -411,9 +411,20 @@
         expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00dd', ctrl: true))).toBe 'ctrl-]'
         expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00de', ctrl: true))).toBe 'ctrl-\''

+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00ba', ctrl: true, shift: true))).toBe 'ctrl-:'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00bb', ctrl: true, shift: true))).toBe 'ctrl-+'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00bc', ctrl: true, shift: true))).toBe 'ctrl-<'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00bd', ctrl: true, shift: true))).toBe 'ctrl-_'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00be', ctrl: true, shift: true))).toBe 'ctrl->'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00bf', ctrl: true, shift: true))).toBe 'ctrl-?'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00db', ctrl: true, shift: true))).toBe 'ctrl-{'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00dc', ctrl: true, shift: true))).toBe 'ctrl-|'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00dd', ctrl: true, shift: true))).toBe 'ctrl-}'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('U+00de', ctrl: true, shift: true))).toBe 'ctrl-"'
+
       it "always includes the shift modifier in the keystroke", ->
-        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('9', ctrl: true, shift: true))).toBe 'ctrl-shift-9'
-        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('/', ctrl: true, shift: true))).toBe 'ctrl-shift-/'
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('9', ctrl: true, shift: true))).toBe 'ctrl-('
+        expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('/', ctrl: true, shift: true))).toBe 'ctrl-?'
         expect(keymapManager.keystrokeForKeyboardEvent(keydownEvent('a', ctrl: true, shift: true))).toBe 'ctrl-shift-A'

   describe "::findKeyBindings({command, target, keystrokes})", ->


upgrade wakatime cli to v4.1.6

upgrade wakatime cli to v4.1.6

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -14,7 +14,7 @@
 request = require 'request'
 rimraf = require 'rimraf'

-latestCLIVersion = '4.1.3'
+latestCLIVersion = '4.1.6'

 packageVersion = null
 unloadHandler = null


fixed: now correctly handles directories starting with "." - not included in parent namespace unless expicitly included; get their own internal namespace files created

--- src/Generation/Generator.coffee
+++ src/Generation/Generator.coffee
@@ -143,7 +143,7 @@

   generate: ->
     @log "\nscanning root: #{@root.yellow}" if @verbose
-    globPromise "#{@root}/**/*.coffee"
+    globPromise "#{@root}/**/*.coffee", dot: true
     .then (files) =>
       if files.length == 0
         error = "no .coffee files found"


Better.

Better.

--- client/publication.coffee
+++ client/publication.coffee
@@ -286,7 +286,7 @@
   currentHighlight?.page is @location.page and currentHighlight?.start is @location.start and currentHighlight?.end is @location.end

 Template.publicationAnnotationsItem.rendered = ->
-  $(@firstNode).nextUntil(@lastNode).addBack().add(@lastNode).data
+  $(@findAll('.annotation')).data
     annotation: @data

 Template.publicationEntry.displayDay = (time) ->


Don't render table if list is empty.

Don't render table if list is empty.

--- client/views-table.coffee
+++ client/views-table.coffee
@@ -48,8 +48,10 @@
   heading?()
   list_selector?() if records.length
   button_add?()
-  list_empty() if not records.length
-  record_table root.record, root.fieldview
+  if records.length
+    record_table root.record, root.fieldview
+  else
+    list_empty()
 ]

 list = ->


fixed vocabulary bug

fixed vocabulary bug

--- src/NaiveBayesClassifier.coffee
+++ src/NaiveBayesClassifier.coffee
@@ -51,8 +51,9 @@
 		categoryTokens = @tokens[category]

 		for token, frequency of @tokenFrequency expression
-			@vocabulary[token] = true
-			@vocabularySize++
+			if not @vocabulary[token]
+				@vocabulary[token] = true
+				@vocabularySize++

 			if not categoryTokens.frequency[token]
 				categoryTokens.frequency[token] = 0


fix a bug

fix a bug

--- lib/build/build-manager.coffee
+++ lib/build/build-manager.coffee
@@ -98,9 +98,12 @@
           form.append "scheme","#{options.scheme}"
           form.append "repository_url","#{options.repository_url}"

+        unless (options.content_src is "")
+          form.append "content_src","#{options.content_src}"
+
         form.append "title","#{options.title}"
         form.append "version","#{options.version}"
         form.append "build","#{options.build}"
-        form.append "content_src","#{options.content_src}"
+

         form.append "asset",fs.createReadStream(options.asset)


Fixed bugs with Surface fast-forwarding too fast on short levels and rewinding non-instantaneously when rerunning.

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -314,7 +314,7 @@
   #- Changes and events that only need to happen when the frame has changed

   onFrameChanged: (force) ->
-    @currentFrame = Math.min(@currentFrame, @world.frames.length)
+    @currentFrame = Math.min(@currentFrame, @world.frames.length - 1)
     @debugDisplay?.updateFrame @currentFrame
     return if @currentFrame is @lastFrame and not force
     progress = @getProgress()
@@ -416,7 +416,8 @@
     @casting = true
     @setPlayingCalled = false  # Don't overwrite playing settings if they changed by, say, scripts.
     @frameBeforeCast = @currentFrame
-    @setProgress 0
+    # This is where I wanted to trigger a rewind, but it turned out to be pretty complicated, since the new world gets updated everywhere, and you don't want to rewind through that.
+    @setProgress 0, 0

   onNewWorld: (event) ->
     return unless event.world.name is @world.name
@@ -435,7 +436,7 @@
     fastForwardBuffer = 2
     if @playing and not @realTime and (ffToFrame = Math.min(event.firstChangedFrame, @frameBeforeCast, @world.frames.length - 1)) and ffToFrame > @currentFrame + fastForwardBuffer * @world.frameRate
       @fastForwardingToFrame = ffToFrame
-      @fastForwardingSpeed = Math.max 4, 4 * 90 / (@world.maxTotalFrames * @world.dt)
+      @fastForwardingSpeed = Math.max 3, 3 * (@world.maxTotalFrames * @world.dt) / 60
     else if @realTime
       lag = (@world.frames.length - 1) * @world.dt - @world.age
       intendedLag = @world.realTimeBufferMax + @world.dt


devicecommunidation: Bring back debug for receiving

Was lost in 5f7e795833f4

--- lib/devicecommunication.coffee
+++ lib/devicecommunication.coffee
@@ -202,6 +202,7 @@
     # Low-level
     _onCommandReceived: (buf) ->
         try
+            console.log 'MICROFLO RECV:', buf.length, buf if debug_comms
             @_handleCommandReceived null, buf
         catch err
             console.log 'MICROFLO RECV ERROR:', buf.length, buf, err if debug_comms


Add Simone to alumni

Add Simone to alumni

--- app/pages/about/team-page.cjsx
+++ app/pages/about/team-page.cjsx
@@ -257,9 +257,9 @@
         his PC playing games or reading comics, he's at his PC studying web design and
         coding experimental apps.'''
       simoneDuca:
-        title: 'Web Developer'
-        bio: '''Simone is a front end web developer at the Zooniverse. He has a Phd in logic and philosophy
-        from Bristol and loves cooking.'''
+        title: 'Developer'
+        bio: '''Simone joined the Zooniverse in 2015 as frontend developer. In a previous life, he received a Phd in logic and philosophy
+        from Bristol. After moving "Up North", he joined another worthy cause in 2018.'''
       stuartLynn:
         title: 'Developer'
         bio: '''Stuart arrived at the Adler as a developer in July 2011, having been one of the founding
@@ -647,7 +647,7 @@
     title: counterpart "team.content.simoneDuca.title"
     bio: counterpart "team.content.simoneDuca.bio"
     image: "/assets/team/simone.jpg"
-    location: "oxford"
+    location: "alumni"
   stuartLynn:
     name: "Stuart Lynn"
     title: counterpart "team.content.stuartLynn.title"


Change back number of repeats

Change back number of repeats

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -274,7 +274,7 @@
   _findFilesInMetadata: (metadata) ->
     filesInMetadata = []
     for key, value of metadata
-      extensions = if isAdmin() then '\\.\\D\\w{2,3}' else "(?:#{VALID_SUBJECT_EXTENSIONS.join '|'})"
+      extensions = if isAdmin() then '\\.\\D\\w{2,4}' else "(?:#{VALID_SUBJECT_EXTENSIONS.join '|'})"
       filesInValue = value.match? ///([^#{INVALID_FILENAME_CHARS.join ''}]+#{extensions})///gi
       if filesInValue?
         filesInMetadata.push filesInValue...


see more artworks

see more artworks

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -41,7 +41,7 @@

 .controller('ArtworkListingController', ($scope, Artworks, $state) ->
   $scope.letter = $state.params.letter || "a"
-  $scope.artworks = Artworks.getList({title__istartswith: $scope.letter}).$object
+  $scope.artworks = Artworks.getList({title__istartswith: $scope.letter, limit: 100}).$object
   $scope.alphabet = "abcdefghijklmnopqrstuvwxyz".split("")
 )


Split triage into two separate controllers

RequestListCtrl and RequestTriageCtrl.

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -58,21 +58,24 @@
           console.log "Failed to create request", request
           for field, message of request._issues.attribute
             flash.error = "#{field}: #{message}"
-  .controller 'RequestTriageCtrl', ($scope, $http, Request, Waterpoint, flash) ->
+  .controller 'RequestListCtrl', ($scope, Request) ->
     $scope.status = 'open'
     $scope.filterStatus = () ->
       Request.query where: {status: $scope.status}, (requests) ->
         $scope.requests = requests._items
-        for r in requests._items
-          Waterpoint.get where: {wpt_code: r.attribute.waterpoint_id}, (waterpoint) ->
-            r.waterpoint = waterpoint._items[0]
-          r.triage = {}
-    $scope.triage = (request) ->
+    $scope.filterStatus()
+  .controller 'RequestTriageCtrl', ($scope, $http, $routeParams, Request, Waterpoint, flash) ->
+    Request.get id: $routeParams.id, (request) ->
+      $scope.request = request
+      Waterpoint.get where: {wpt_code: request.attribute.waterpoint_id}, (waterpoint) ->
+        $scope.waterpoint = waterpoint._items[0]
+    $scope.triage = {}
+    $scope.doTriage = () ->
       $http
         method: 'PATCH'
-        url: '/api/waterpoints/'+request.waterpoint._id
-        data: request.triage
-        headers: {'If-Match': request.waterpoint._etag}
+        url: '/api/waterpoints/'+$scope.waterpoint._id
+        data: $scope.triage
+        headers: {'If-Match': $scope.waterpoint._etag}
       .success (data, status, headers, config) ->
         console.log data, status, headers, config
         if status == 200 and data._status == 'OK'
@@ -80,7 +83,6 @@
         if status == 200 and data._status == 'ERR'
           for field, message of data._issues
             flash.error = "#{field}: #{message}"
-    $scope.filterStatus()

   .controller 'DashboardCtrl', ($scope, $http) ->
     $scope.plots = [


Fix m2fa specs

Fix m2fa specs

--- app/spec/m2fa/m2fa_spec.coffee
+++ app/spec/m2fa/m2fa_spec.coffee
@@ -6,7 +6,7 @@

   beforeEach ->
     @$ = ledger.m2fa
-    spyOn(@$, 'Client').and.returnValue jasmine.createSpyObj('client', ['on','off','sendChallenge','rejectPairing','confirmPairing','requestValidation'])
+    spyOn(@$, 'Client').and.returnValue jasmine.createSpyObj('client', ['on','off','once','sendChallenge','rejectPairing','confirmPairing','requestValidation'])
     @$._clientFactory = (pairingId) -> new ledger.m2fa.Client(pairingId)

   it "init device pairing with a new random pairingId, a client, listen client event, and return the pairingId and a promise", ->
@@ -20,7 +20,7 @@
     expect(client).toBeDefined()
     expect(@$.Client).toHaveBeenCalledWith(pairingId)

-    expect(client.on.calls.count()).toBe(2)
+    expect(client.on.calls.count()).toBe(3)
     expect(client.on.calls.argsFor(0)[0]).toBe('m2fa.identify')
     expect(client.on.calls.argsFor(0)[1]).toEqual(jasmine.any(Function))
     expect(client.on.calls.argsFor(1)[0]).toBe('m2fa.challenge')
@@ -32,9 +32,11 @@
     expect(result[1].constructor.name).toBe('Promise')

   it "prefix pairingId when it save asociated label", ->
-    spyOn(ledger.storage.sync, 'set')
-    @$.setPairingLabel("a_random_pairing_id", "label")
-    expect(ledger.storage.sync.set).toHaveBeenCalledWith("__m2fa_a_random_pairing_id": "label")
+    spyOn(ledger.m2fa.PairedSecureScreen, 'create').and.callThrough()
+    spyOn(ledger.m2fa.PairedSecureScreen.prototype, 'toStore')
+    @$.saveSecureScreen("a_random_pairing_id", "label")
+    expect(ledger.m2fa.PairedSecureScreen.create).toHaveBeenCalledWith("a_random_pairing_id", "label")
+    expect(ledger.m2fa.PairedSecureScreen.prototype.toStore).toHaveBeenCalled()

   it "remove pairingId prefix when it get all pairing labels", ->
     spyOn(ledger.storage.sync, 'keys').and.callFake (cb) -> cb(["__m2fa_a_random_pairing_id", 'onOtherBadKey'])
@@ -50,12 +52,15 @@
     tx =
       _out: {authorizationPaired: "XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx"}
     pairingId = "a_random_pairing_id"
-    client = jasmine.createSpyObj('client',['on','off','sendChallenge','rejectPairing','confirmPairing','requestValidation'])
+    client = jasmine.createSpyObj('client',['on','off','once','sendChallenge','rejectPairing','confirmPairing','requestValidation'])
     spyOn(@$, '_getClientFor').and.returnValue(client)
+    spyOn(ledger.api.M2faRestClient.instance, 'wakeUpSecureScreens')

     [c, r] = @$.validateTx(tx, pairingId)

     expect(r.constructor.name).toBe('Promise')
+
+    expect(ledger.api.M2faRestClient.instance.wakeUpSecureScreens).toHaveBeenCalledWith([pairingId])

     expect(@$._getClientFor).toHaveBeenCalledWith(pairingId)
     expect(client.off.calls.count()).toBe(2)
@@ -71,9 +76,12 @@
   it "get call validateTx for each client on validateTxOnAll", ->
     tx =
       _out: {authorizationPaired: "XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx"}
-    spyOn(@$, 'getPairingIds').and.returnValue(then: ((cb) -> cb("a_random_pairing_id":"label")))
-    spyOn(@$, 'validateTx').and.returnValue(Q.defer().promise)
+    spyOn(@$, 'getPairingIds').and.returnValue(Q([{"a_random_pairing_id":"label"}]))
+    spyOn(@$, 'validateTx').and.returnValue(Q())
+    console.log(typeof ledger.api.M2faRestClient.instance)
+    spyOn(ledger.api.M2faRestClient.instance, 'wakeUpSecureScreens')
     [c, r] = @$.validateTxOnAll(tx)
+    expect(ledger.api.M2faRestClient.instance.wakeUpSecureScreens).toHaveBeenCalledWith(["a_random_pairing_id"])
     expect(@$.validateTx.calls.count()).toBe(1)
     expect(@$.validateTx.calls.argsFor(0)).toEqual([tx,"a_random_pairing_id"])


Remove 'he' as supported language for now, not enough translations

--- app/views/core/RootView.coffee
+++ app/views/core/RootView.coffee
@@ -168,7 +168,7 @@

   addLanguagesToSelect: ($select, initialVal) ->
     # For now, we only want to support a few languages for Ozaria when launching for HoC 2019:
-    supportedLanguages = ['en-US', 'es-419', 'zh-HANS', 'ru', 'he']
+    supportedLanguages = ['en-US', 'es-419', 'zh-HANS', 'ru']
     filteredLocale = _.pick(locale, supportedLanguages)
     codes = _.keys(filteredLocale)


Remove config options that are already defaults

Underscore template option `namespace` already defaults to 'JST' in `grunt-contrib-jst`. Handlebars template options `namespace` also defaults to 'JST' and `wrapped` already defaults to `true`.

--- config/application.coffee
+++ config/application.coffee
@@ -41,16 +41,11 @@

   #templates
   handlebars:
-    options:
-      namespace: "JST"
-      wrapped: true
     compile:
       files:
         "<%= files.template.generatedHandlebars %>": "<%= files.template.handlebars %>"

   jst:
-    options:
-      namespace: "JST"
     compile:
       files:
         "<%= files.template.generatedUnderscore %>": "<%= files.template.underscore %>"


Minor fix

Minor fix

--- client/Main/notificationcontroller.coffee
+++ client/Main/notificationcontroller.coffee
@@ -220,7 +220,7 @@
     notification = new KDNotificationView
       type     : 'tray'
       cssClass : "mini realtime #{options.type}"
-      duration : 100000000
+      duration : 10000
       title    : "#{options.title}"
       content  : options.content  or null


client/socialapicontroller: add channel endpoints

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -959,7 +959,7 @@
       , callback

     # add if control if options have -> accountId & messageId & groupName
-    pinMessage: (options, callback) ->
+    pin: (options, callback) ->

       doXhrRequest
         type     : 'POST'
@@ -968,11 +968,115 @@
       , callback

     # add if control if options have -> accountId & messageId & groupName
-    unpinMessage: (options, callback) ->
+    unpin: (options, callback) ->

       doXhrRequest
         type     : 'POST'
         endPoint : "/api/social/activity/pin/remove"
+        data     : options
+      , callback
+
+    # follow -> addParticipants function
+    follow: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/add"
+        data     : options
+      , callback
+
+    # unfollow -> removeParticipants function
+    unfollow: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/remove"
+        data     : options
+      , callback
+
+    listParticipants: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/channel/#{channelId}/participants"
+      , callback
+
+    addParticipants: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/add"
+        data     : options
+      , callback
+
+    removeParticipants: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/remove"
+        data     : options
+      , callback
+
+    leave: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/remove"
+        data     : options
+      , callback
+
+    acceptInvite: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/invitation/accept"
+        data     : options
+      , callback
+
+    rejectInvite: (options, callback) ->
+      { channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/invitation/reject"
+        data     : options
+      , callback
+
+    # kickParticipants calls leave endpoint, but required extra accountIds
+    kickParticipants: (options, callback) ->
+      { channelId , accountIds } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participants/remove"
+        data     : options
+      , callback
+
+    fetchFollowedChannels: (options, callback) ->
+      { accountId } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/account/#{accountId}/channels"
+      , callback
+
+    fetchProfileFeed: (options, callback) ->
+      { targetId } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/account/#{targetId}/posts"
+      , callback
+
+    glancePinnedPost: (options, callback) ->
+      { accountId, messageId, groupName } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/activity/pin/glance"
+        data     : options
+      , callback
+
+    updateLastSeenTime: (options, callback) ->
+      { accountId, channelId } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{channelId}/participant/#{accountId}/presence"
         data     : options
       , callback


ChatSettingsPane: No need to call createParticipantsList.

--- client/ide/views/chat/settingspane.coffee
+++ client/ide/views/chat/settingspane.coffee
@@ -23,7 +23,6 @@

     @on 'CollaborationStarted', =>
       @toggleButtons 'started'
-      # @createParticipantsList()

   createElements: ->


Adds view test

Adds view test

--- components/articles_grid/test/view.coffee
+++ components/articles_grid/test/view.coffee
@@ -5,14 +5,14 @@
 { resolve } = require 'path'
 { fabricate } = require 'antigravity'
 Articles = require '../../../collections/articles'
-ArticleView = benv.requireWithJadeify resolve(__dirname, '../article'), ['template']
 ArticlesGridView = benv.requireWithJadeify resolve(__dirname, '../view'), ['template', 'button', 'empty', 'figure']
-ArticlesFeedView.__set__ 'ArticleView', ArticleView

-describe 'ArticlesFeedView', ->
+describe 'ArticlesGridView', ->
   before (done) ->
     benv.setup ->
-      benv.expose $: benv.require 'jquery'
+      benv.expose
+        $: benv.require 'jquery'
+        sd: ARTSY_EDITORIAL_ID: '123'
       Backbone.$ = $
       done()

@@ -21,34 +21,40 @@

   describe 'with an empty collection', ->
     beforeEach ->
-      @view = new ArticlesFeedView collection: new Articles
+      @view = new ArticlesGridView collection: new Articles

     describe '#render', ->
       beforeEach ->
         @view.render()

       it 'renders the empty state', ->
-        @view.$('.articles-feed-item').should.have.lengthOf 1
-        @view.$('.js-articles-feed-more').is(':empty').should.be.true()
-        @view.$('.articles-feed-item').text().should.equal 'No results. View all articles'
+        @view.$('.articles-grid__figure').should.have.lengthOf 1
+        @view.$('.js-articles-grid-more').is(':empty').should.be.true()
+        @view.$('.articles-grid__figure').text().should.equal 'No results. View all articles'

   describe 'with an articles collection', ->
     beforeEach ->
       @articles = new Articles [
-        { id: _.uniqueId(), thumbnail_title: 'Foo Bar', contributing_authors: [] }
-        { id: _.uniqueId(), thumbnail_title: 'Bar Baz', contributing_authors: [] }
+        { id: _.uniqueId(), thumbnail_title: 'Foo Bar', contributing_authors: [], author: {name: 'Molly'} }
+        { id: _.uniqueId(), thumbnail_title: 'Foo Baz', contributing_authors: [], author_id: '123', author: {name: 'Artsy Editorial'} }
+        { id: _.uniqueId(), thumbnail_title: 'Bar Baz', contributing_authors: [{name: 'Jon Snow'}], author: {name: 'Halley'}}
+        { id: _.uniqueId(), thumbnail_title: 'Bar Bar', contributing_authors: [{name: 'Sansa Stark'}, {name: 'Tyrion Lannister'}], author_id: '123', author: {name: 'Artsy Editorial'} }
       ]
-      @articles.count = 10
-      @view = new ArticlesFeedView collection: @articles
+      @articles.count = 15
+      @view = new ArticlesGridView collection: @articles

     describe '#render', ->
       beforeEach ->
         @view.render()

       it 'renders the articles', ->
-        @view.$('.articles-feed-item').should.have.lengthOf 2
-        @view.$('.article-figure-title').map(-> $(this).text()).get()
-          .should.eql ['Foo Bar', 'Bar Baz']
+        @view.$('.articles-grid__figure').should.have.lengthOf 4
+        @view.$('.articles-grid__title').map(-> $(this).text()).get()
+          .should.eql ['Foo Bar', 'Foo Baz', 'Bar Baz', 'Bar Bar']

       it 'renders the button', ->
-        @view.$('button').text().should.equal 'More Articles (8)'
+        @view.$('button').text().should.equal 'More Articles (11)'
+
+      it 'renders the author name', ->
+        @view.$('.articles-grid__author').map(-> $(this).text()).get()
+          .should.eql ['Molly', 'Artsy Editorial', 'Jon Snow', 'Artsy Editorial  •  Sansa Stark and Tyrion Lannister']


Add package option to enable linting on document changes. This option is disabled by default.

--- lib/linter-languagetool.coffee
+++ lib/linter-languagetool.coffee
@@ -15,6 +15,10 @@
       type: 'string'
       description: 'A language code of the user\'s native language, enabling false friends checks for some language pairs.'
       default: require('electron').remote.app.getLocale()
+    lintsOnChange:
+      type: 'boolean'
+      description: 'If enabled the linter will run on every change on the file.'
+      default: false

   provideLinter: ->
     LinterProvider = require './linter-provider'
@@ -22,7 +26,7 @@
     return {
       name: 'languagetool'
       scope: 'file'
-      lintsOnChange: false
+      lintsOnChange: atom.config.get 'linter-languagetool.lintsOnChange'
       grammarScopes: atom.config.get 'linter-languagetool.grammerScopes'
       lint: provider.lint
     }


Removes logs

Removes logs

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -39,7 +39,6 @@
     @subscribe editor, 'screen-lines-changed.minimap', @update

   registerBufferChanges: (event) =>
-    console.log 'here'
     @bufferChanges.push event

   update: =>


add comment

add comment

--- lib/mycha_configuration.coffee
+++ lib/mycha_configuration.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore'

-# Parses the Mycha options out of the given command-line arguments
+# Parses the Mycha options out of the given command-line arguments,
 # augments them with default values,
 # and represents them in a user-friendly format.
 class MychaConfiguration


Finally added absolute paths with Amazon S3

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -420,7 +420,6 @@
 	@loadNpmTasks 'grunt-shell'
 	@loadNpmTasks 'grunt-text-replace'
 	@loadNpmTasks 'grunt-s3-sync'
-	@loadNpmTasks 'grunt-cdn'

 	@registerTask 'build', ['bower-install', 'shell:files', 'copy', 'license']
 	@registerTask 'default', ['compass:dev', 'csslint', 'coffeeredux', 'concat']


Make syntax change in coffeescript

Make syntax change in coffeescript

Some parts of our Gruntfile seem to have issues with different versions
of coffeescript.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -853,7 +853,7 @@
   'copy:chrome_app'
 ].concat fullyVulcanize('chrome/app/polymer', 'ext-missing', 'vulcanized')

-taskManager.add 'build_chrome_ext', [
+taskManager.add('build_chrome_ext', [
   'base'
   'ts:chrome_extension'
   'copy:chrome_extension'
@@ -861,7 +861,7 @@
   'browserify:chromeExtMain'
   'browserify:chromeContext'
 ].concat fullyVulcanize('chrome/extension/generic_ui/polymer', 'root', 'vulcanized', true)
-.concat fullyVulcanize('chrome/extension/generic_ui/polymer', 'logs', 'vulcanized-view-logs', true)
+.concat fullyVulcanize('chrome/extension/generic_ui/polymer', 'logs', 'vulcanized-view-logs', true))

 taskManager.add 'build_chrome', [
   'build_chrome_app'
@@ -869,14 +869,14 @@
 ]

 # Firefox build tasks.
-taskManager.add 'build_firefox', [
+taskManager.add('build_firefox', [
   'base'
   'ts:firefox'
   'copy:firefox'
   'copy:firefox_additional'
   'browserify:firefoxContext'
 ].concat fullyVulcanize('firefox/data/generic_ui/polymer', 'root', 'vulcanized', true)
-.concat fullyVulcanize('firefox/data/generic_ui/polymer', 'logs', 'vulcanized-view-logs', true)
+.concat fullyVulcanize('firefox/data/generic_ui/polymer', 'logs', 'vulcanized-view-logs', true))

 # CCA build tasks.
 taskManager.add 'build_cca', [


Move all gzip / uglify tasks to build:minified.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -344,8 +344,6 @@
     'coffee:compile'
     'copy:commonjs'
     'concat:commonjs'
-    'uglify:commonjs'
-    'compress:commonjs'
   ]

   grunt.registerTask 'build:amd', [
@@ -353,16 +351,28 @@
     'urequire'
     'copy:amd'
     'concat:amd'
-    'uglify:amd'
-    'compress:amd'
   ]

   grunt.registerTask 'build:brunch', [
     'coffee:compile'
     'copy:commonjs'
     'concat:brunch'
+  ]
+
+  grunt.registerTask 'build:minified', [
+    'uglify:commonjs'
+    'compress:commonjs'
+    'uglify:amd'
+    'compress:amd'
     'uglify:brunch'
     'compress:brunch'
+  ]
+
+  grunt.registerTask 'build:all', [
+    'build:amd'
+    'build:commonjs'
+    'build:brunch'
+    'build:minified'
   ]

   grunt.registerTask 'build', [


need to copy custom core freedom providers to dist/

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -127,6 +127,7 @@
       }

       freedomTypings: Rule.copyModule 'freedom/typings'
+      freedomCustomCoreProvidersTypings: Rule.copyModule 'freedom/coreproviders'
       freedomBuilds: {
         files: [
           {
@@ -288,6 +289,7 @@
     'uglify:freedomForChromeForUproxy'
     'uglify:freedomForFirefoxForUproxy'
     'copy:freedomTypings'
+    'copy:freedomCustomCoreProvidersTypings'
     'copy:freedomBuilds'
   ]


remove jasmine test from watch task as it takes rather long and pollute output console

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -21,14 +21,10 @@
     watch:
       coffee:
         files: ['**/*.coffee']
-        tasks: ['coffee', 'karma:unit:run', 'jasmine']
-#        tasks: ['coffee']
+        tasks: ['coffee', 'karma:unit:run']
       jade:
         files: ['src/**/*.jade']
         tasks: 'jade'
-      test:
-        files: ['test/**/*.coffee']
-        tasks: ['karma:unit:run', 'jasmine']

     coffee:
       compile:
@@ -163,6 +159,8 @@
           'test/lib/angular-mocks.js'
         ]
 #        helpers: 'test/helpers/helper.js'
+        display: 'short'
+        summary: true
         keepRunner: true

   grunt.registerTask 'default', ['coffee', 'jade', 'copy', 'karma:unit', 'protractor']


Add min files to dev build

Add min files to dev build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -49,7 +49,7 @@
     test: []
     vtex_deploy: ['shell:cp', 'shell:cp_br']
   # Development tasks
-    dev: ['nolr', 'build', 'watch']
+    dev: ['nolr', 'build', 'min', 'watch']
     default: ['dev']

   # Project configuration.


fixed typo

fixed typo

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -195,7 +195,7 @@
           "compass:dev"
         ]

-    # Run the server
+    # Run the back-end server
     run:
       reminder_server:
         options:


Integrated the bin load-any-fixture in grunt tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,6 +12,7 @@
                     'api/controllers/api.js': 'dev/api/controllers/api.coffee'
                     'api/models/user.js': 'dev/api/models/user.coffee'
                     'data/fixtures/fictitious/fixture.js': 'dev/data/fixtures/fictitious/fixture.coffee'
+                    'data/configs/bootstrapper.js': 'dev/data/configs/bootstrapper.coffee'
                 }
             tests:
                 options:
@@ -55,6 +56,7 @@
                         files: [
                             'dev/*.coffee',
                             'dev/api/**/*.coffee',
+                            'dev/data/**/**/*.coffee',
                             'dev/public/assets/app/modules/*.coffee',
                             'dev/var/test-public/modules/*.coffee'
                         ]


Add homepage generation task

Add homepage generation task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,6 +29,15 @@
                         demo: "
here's the demo !
"
                 files:
                     "docs/demo.html": "_docs/_pages/demo.jade" # TODO : change this
+            home:
+                options:
+                    data: # TMP
+                        name: "demo test"
+                        id: "test"
+                        doc: "
here's the docs !
"
+                        demo: "
here's the demo !
"
+                files:
+                    "docs/index.html": "_docs/_pages/index.jade" # TODO : change this
         copy:
             assets:
                 expand: yes


Uglify as part of `grunt coffee`

Uglify as part of `grunt coffee`

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,6 +1,6 @@

 module.exports = (grunt) ->
-
+
   # Project configuration.
   grunt.initConfig
     pkg: grunt.file.readJSON 'package.json'
@@ -53,7 +53,7 @@
     watch:
       coffee:
         files: ['src/*.coffee', 'spec/javascripts/*.spec.coffee', 'spec/spec_helper.coffee']
-        tasks: ['compile']
+        tasks: ['compile', 'uglify']
       test:
         options:
           debounceDelay: 250


Corrected urls replacement.

Corrected urls replacement.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -122,7 +122,7 @@
             overwrite: true
             replacements: [
               from: '/v1/'
-              to: 'api.trackseats.info/v1/'
+              to: '//api.trackseats.info/v1/'
             ]

     # Load plugins


remove unnecessary options

remove unnecessary options

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -106,17 +106,10 @@
             -vn -codec:a libvorbis -qscale:a 10
             #{ DEST }audio/compressed/ogg/<%= ffmpegPath %>.ogg"
         ].join '&&'
-        options:
-          callback: (err, stdout, stderr, cb) ->
-            console.warn stderr if stderr
-            console.log stdout if stdout
-            cb()

       coffeelint:
         command:
           "#{ BIN }coffeelint Gruntfile.coffee"
-        options:
-          stdout: true

     bower:
       options:


now copying all js files from scripts to temp during build, need this for jslove - issue #31

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -55,6 +55,15 @@

 		# Copies directories and files from one location to another.
 		copy:
+			# Copies the contents of the temp directory, except views, to the dist directory.
+			# In 'dev' individual files are used.
+			dev:
+				files: [
+					cwd: './.temp/'
+					src: '**'
+					dest: './dist/'
+					expand: true
+				]
 			# Copies img directory to temp.
 			img:
 				files: [
@@ -63,21 +72,12 @@
 					dest: './.temp/'
 					expand: true
 				]
-			# Copies libs directory to temp.
-			libs:
+			# Copies js files to the temp directory
+			js:
 				files: [
 					cwd: './src/'
-					src: 'scripts/libs/**/*.js'
+					src: 'scripts/**/*.js'
 					dest: './.temp/'
-					expand: true
-				]
-			# Copies the contents of the temp directory, except views, to the dist directory.
-			# In 'dev' individual files are used.
-			dev:
-				files: [
-					cwd: './.temp/'
-					src: '**'
-					dest: './dist/'
 					expand: true
 				]
 			# Copies select files from the temp directory to the dist directory.
@@ -354,10 +354,10 @@
 	grunt.registerTask 'default', [
 		'clean:working'
 		'coffee:scripts'
+		'copy:js'
 		'less'
 		'template:views'
 		'copy:img'
-		'copy:libs'
 		'template:dev'
 		'copy:dev'
 	]
@@ -376,10 +376,10 @@
 	grunt.registerTask 'prod', [
 		'clean:working'
 		'coffee:scripts'
+		'copy:js'
 		'less'
 		'template:views'
 		'imagemin'
-		'copy:libs'
 		'ngTemplateCache'
 		'requirejs'
 		'template:prod'


fix s3

fix s3

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -13,7 +13,7 @@
   grunt.loadNpmTasks('grunt-aws')

   grunt.initConfig
-    bower: '<json:bower.json>'
+    bower: grunt.file.readJSON("bower.json")
     pkg: '<json:package.json>'
     srcFolder: 'src'
     compiledFolder: 'compiled' # Temporary holding area.
@@ -117,7 +117,7 @@
       build:
         cwd: "dist/"
         src: "**"
-        dest: "<%= bower.version %>/"
+        dest: '<%= bower.version %>/'

     karma:
       main:


jshint:verbose task.

jshint:verbose task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -26,10 +26,19 @@
 			]

 		jshint:
-			options:
-				jshintrc: '.jshintrc'
-				# reporter: './node_modules/jshint/src/reporters/non_error.js',
 			all:
+				options:
+					jshintrc: '.jshintrc'
+				src: [
+					'*.js'
+					'utils/*.js'
+					'tests_node/*.js'
+					'grunt-custom-tasks/*.js'
+				]
+			verbose:
+				options:
+					jshintrc: '.jshintrc'
+					reporter: './node_modules/jshint/src/reporters/non_error.js'
 				src: [
 					'*.js'
 					'utils/*.js'
@@ -108,7 +117,7 @@
 	grunt.loadTasks './grunt-custom-tasks/'

 	# Basic tasks.
-	grunt.registerTask 'check', ['checkstrict', 'checklicense', 'coffeelint', 'jshint']
+	grunt.registerTask 'check', ['checkstrict', 'checklicense', 'coffeelint', 'jshint:all']
 	grunt.registerTask 'build', ['browserify', 'uglify']
 	grunt.registerTask 'test', ['nodeunit', 'jscoverage_report']


[BUGFIX] Ganti nama grunt task untuk heroku

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -130,6 +130,6 @@
     'cssmin:dist'
     'notify:build'
   ]
-  grunt.registerTask 'heroku:production', ['build']
+  grunt.registerTask 'heroku', ['build']


bump version to 1.0.6 in ezBreadcrumbs.coffee

--- src/coffee/ezBreadcrumbs.coffee
+++ src/coffee/ezBreadcrumbs.coffee
@@ -2,7 +2,7 @@
  * EzBreadcrumbs - jQuery plugin that transforms a breadcrumbs to a responsive one. Useful when making responsive websites.
  * Copyright(c) Exequiel Ceasar Navarrete <exequiel.navarrete@ninthdesign.com>
  * Licensed under MIT
- * Version 1.0.5
+ * Version 1.0.6
 ###
 (($, window, document, undefined_) ->
     "use strict"


Updated comment

Updated comment

--- src/wrathnet/expansions/wotlk/handlers/auth-handler.coffee
+++ src/wrathnet/expansions/wotlk/handlers/auth-handler.coffee
@@ -39,7 +39,7 @@

     super

-    # Holds signals this auth handler dispatches
+    # Holds signals this authentication handler dispatches
     ObjectUtil.merge @on, {
       authenticate: new signals.Signal()
       reject: new signals.Signal()


doc fix

doc fix

--- example.coffee
+++ example.coffee
@@ -4,7 +4,7 @@
 # issue (thanks to mslog's buffering) and will see all log messages
 #
 # First, start the client:
-#     ./mslog ipc:///tmp/mslogger/example
+#     ./bin/ms-logger ipc:///tmp/mslogger/example
 #
 # Then run this file
 #     coffee example.coffee
@@ -19,4 +19,4 @@
   logger.info "counting: #{count+=1}"
   if count is 100
     process.exit()
-, 10+, 10


popularpost: remove sending type to fetching popular posts

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -124,10 +124,10 @@
   get url, data, callback

 fetchPopularPosts = (data, callback)->
-  if not data.groupName or not data.type or not data.channelName
+  if not data.groupName or not data.channelName
     return callback {message: "Request is not valid for listing popular topics"}

-  url = "/popular/posts/#{data.channelName}/#{data.type}"
+  url = "/popular/posts/#{data.channelName}"
   get url, data, callback

 fetchPinnedMessages = (data, callback)->


Panel: Fixed "open" / "close" methods, when called directly.

--- src/elements/Panel/Panel.coffee
+++ src/elements/Panel/Panel.coffee
@@ -36,11 +36,11 @@
 			icon_active: @_icon_opened
 			icon_inactive: @_icon_closed
 			onActivate: (btn, flags, event) =>
-				@open(not flags.initial_activate)
+				@__open(not flags.initial_activate)
 				@_onActivate?(btn, flags, event)

 			onDeactivate: (btn, flags, event) =>
-				@close(not flags.initial_activate)
+				@__close(not flags.initial_activate)
 				@_onDeactivate?(btn, flags, event)

 		@append(@button, "header")
@@ -103,7 +103,15 @@
 	isOpen: ->
 		!@isClosed()

-	close: (trigger = true) ->
+	open: ->
+		@button.activate()
+		@
+
+	close: ->
+		@button.deactivate()
+		@
+
+	__close: (trigger = true) ->
 		CUI.dom.addClass(@DOM, "cui-panel-closed")

 		if trigger
@@ -112,7 +120,7 @@
 				node: @DOM
 		@

-	open: (trigger = true) ->
+	__open: (trigger = true) ->
 		done = =>
 			CUI.dom.removeClass(@DOM, "cui-panel-closed")
 			if trigger
@@ -158,5 +166,3 @@
 		else
 			@append(content, key)
 		@
-
-


minority report

minority report

--- library/membrane/access.coffee
+++ library/membrane/access.coffee
@@ -117,7 +117,7 @@
             return next undefined if _.isEmpty error
             assert message = error.message or error
             logger.error format(message).red
-            return next error
+            return next.call this, error

     # A usable hook that gets asynchronously invoked once a new
     # socket connection is going to be setup during the handshake.
@@ -135,4 +135,4 @@
             return next undefined if _.isEmpty error
             assert message = error.message or error
             logger.error format(message).red
-            return next error
+            return next.call this, error


improve on the logging looks

improve on the logging looks

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -228,13 +228,14 @@
         isocket = "Executed %s socket trampoline"
         message = "Inbound duplex connection at %s"
         request = "Acknowledged socket from %s request"
-        assert d = "Disengaging protocol for %s socket"
+        assert discon = "Disengaging %s of %s".yellow
         assert identify = try @constructor.identify()
         logger.debug message.magenta, identify.underline
-        logger.debug request.green, context.url.underline
+        logger.debug request.grey, context.url.underline
         logger.debug isocket.green, callback.socket.id.bold
         callback.socket.on "disconnect", (error, args) =>
-            logger.debug d.yellow, callback.socket.id.bold
+            assert identity = try callback.socket.id.bold
+            logger.debug discon, identity, identify.underline
             assert disengage = @downstream disengage: ->
             return disengage context, callback.socket
         connected = @downstream connected: callback


More complicated autocall implementation, enables Z-coord spec (time)

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -166,10 +166,14 @@
                 return unless value?.remote?.autocall?
                 params = JSON.stringify value.remote.autocall
                 template = "#{symbol}.#{key}.call(#{symbol}, %s)"
-                assert formatted = format template, params
+                formatted = Object.create String.prototype
+                formatted.valueOf = -> format template, params
+                formatted.priority = value.remote.autocall.z
                 uns = -> context.invokes.unshift formatted
                 return uns() if value.remote.autocall.unshift
                 return context.invokes.push formatted
+        sorter = (invoke) -> return invoke.priority or 0
+        context.invokes = _.sortBy context.invokes, sorter

     # An internal routine that is called on the context object prior
     # to flushing it down to the client. This method gathers all the


Enabled forced swapin/swapout reload

Enabled forced swapin/swapout reload

--- library/nucleus/bundles.coffee
+++ library/nucleus/bundles.coffee
@@ -89,7 +89,7 @@
     @defaults.secure.cert = "#{__dirname}/../../keys/cert.pem"
     @defaults.layout = library: "library", config: "config"
     @defaults.assets = dirs: [], opts: {}
-    @defaults.watch = dirs: [], force: no
+    @defaults.watch = dirs: [], force: yes
     @defaults.socket = "log level": 0
     @defaults.env.preserve = ["pub"]

@@ -108,6 +108,6 @@
     @defaults.secure.cert = "#{__dirname}/../../keys/cert.pem"
     @defaults.layout = library: "library", config: "config"
     @defaults.assets = dirs: [], opts: {}
-    @defaults.watch = dirs: [], force: no
+    @defaults.watch = dirs: [], force: yes
     @defaults.socket = "log level": 0
     @defaults.env.preserve = ["pub"]


add unit tests

add unit tests

--- services/document-updater/test/unit/coffee/ShareJsUpdateManager/ShareJsUpdateManagerTests.coffee
+++ services/document-updater/test/unit/coffee/ShareJsUpdateManager/ShareJsUpdateManagerTests.coffee
@@ -3,6 +3,7 @@
 should = chai.should()
 modulePath = "../../../../app/js/ShareJsUpdateManager.js"
 SandboxedModule = require('sandboxed-module')
+crypto = require('crypto')

 describe "ShareJsUpdateManager", ->
 	beforeEach ->
@@ -26,8 +27,10 @@
 		beforeEach ->
 			@lines = ["one", "two"]
 			@version = 34
-			@update = {p: 4, t: "foo"}
 			@updatedDocLines = ["onefoo", "two"]
+			content = @updatedDocLines.join("\n")
+			@hash = crypto.createHash('sha1').update("blob " + content.length + "\x00").update(content, 'utf8').digest('hex')
+			@update = {p: 4, t: "foo", v:@version, hash:@hash}
 			@model =
 				applyOp: sinon.stub().callsArg(2)
 				getSnapshot: sinon.stub()
@@ -90,6 +93,18 @@
 			it "should call the callback with the error", ->
 				@callback.calledWith(@error).should.equal true

+		describe "with an invalid hash", ->
+			beforeEach (done) ->
+				@error = new Error("invalid hash")
+				@model.getSnapshot.callsArgWith(1, null, {snapshot: "unexpected content", v: @version})
+				@model.db.appliedOps["#{@project_id}:#{@doc_id}"] = @appliedOps = ["mock-ops"]
+				@ShareJsUpdateManager.applyUpdate @project_id, @doc_id, @update, @lines, @version, (err, docLines, version, appliedOps) =>
+					@callback(err, docLines, version, appliedOps)
+					done()
+
+			it "should call the callback with the error", ->
+				@callback.calledWith(@error).should.equal true
+
 	describe "_listenForOps", ->
 		beforeEach ->
 			@model = on: (event, callback) =>


model_postgres: for an actor, getSubscriptions() shall return not only users but also listeners

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -274,15 +274,12 @@
             cb2 null, subscribers
         ], cb

-    getSubscriptions: (user, cb) ->
-        db = @db
-        async.waterfall [(cb2) ->
-            db.query "SELECT node, subscription FROM subscriptions WHERE \"user\" = $1 ORDER BY updated DESC", [ user ], cb2
-        , (res, cb2) ->
-            subscriptions = for row in res.rows
-                { node: row.node, subscription: row.subscription }
-            cb2 null, subscriptions
-        ], cb
+    ##
+    # Not only by users but also by listeners.
+    # @param cb {Function} cb(Error, { user, node, subscription })
+    getSubscriptions: (actor, cb) ->
+        db.query "SELECT user, node, subscription FROM subscriptions WHERE \"user\"=$1 OR listener=$1 ORDER BY updated DESC", [ actor ], (err, res) ->
+            cb err, res?.rows

     getPingNodes: (user, cb) ->
         db = @db


ResourceStateModal: typo fix

ResourceStateModal: typo fix

--- client/app/lib/providers/resourcestatemodal/views/machineflow/startmachineprogresspageview.coffee
+++ client/app/lib/providers/resourcestatemodal/views/machineflow/startmachineprogresspageview.coffee
@@ -37,7 +37,7 @@

Spinning up #{title}

-
We're building your VM. Once is running you can get to coding.

+
We're building your VM. Once it is running you can get to coding.

             {{> @progressBar}}
             {{> @statusText}}


Fixes #2359 -- tweak grammar to use new name

--- grammar.coffee
+++ grammar.coffee
@@ -36,7 +36,7 @@
   action = if match = unwrap.exec action then match[1] else "(#{action}())"
   action = action.replace /\bnew /g, '$&yy.'
   action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&'
-  action = action.replace /\b(Op|Value\.create)\b/g, 'yy.$&'
+  action = action.replace /\b(Op|Value\.(create|wrap))\b/g, 'yy.$&'
   [patternString, "$$ = #{action};", options]

 # Grammatical Rules
@@ -143,10 +143,10 @@
   # Assignment when it happens within an object literal. The difference from
   # the ordinary **Assign** is that these allow numbers and strings as keys.
   AssignObj: [
-    o 'ObjAssignable',                          -> Value.create $1
-    o 'ObjAssignable : Expression',             -> new Assign Value.create($1), $3, 'object'
+    o 'ObjAssignable',                          -> Value.wrap $1
+    o 'ObjAssignable : Expression',             -> new Assign Value.wrap($1), $3, 'object'
     o 'ObjAssignable :
-       INDENT Expression OUTDENT',              -> new Assign Value.create($1), $4, 'object'
+       INDENT Expression OUTDENT',              -> new Assign Value.wrap($1), $4, 'object'
     o 'Comment'
   ]

@@ -220,26 +220,26 @@

   # Variables and properties that can be assigned to.
   SimpleAssignable: [
-    o 'Identifier',                             -> Value.create $1
+    o 'Identifier',                             -> Value.wrap $1
     o 'Value Accessor',                         -> $1.add $2
-    o 'Invocation Accessor',                    -> Value.create $1, [].concat $2
+    o 'Invocation Accessor',                    -> Value.wrap $1, [].concat $2
     o 'ThisProperty'
   ]

   # Everything that can be assigned to.
   Assignable: [
     o 'SimpleAssignable'
-    o 'Array',                                  -> Value.create $1
-    o 'Object',                                 -> Value.create $1
+    o 'Array',                                  -> Value.wrap $1
+    o 'Object',                                 -> Value.wrap $1
   ]

   # The types of things that can be treated as values -- assigned to, invoked
   # as functions, indexed into, named as a class, etc.
   Value: [
     o 'Assignable'
-    o 'Literal',                                -> Value.create $1
-    o 'Parenthetical',                          -> Value.create $1
-    o 'Range',                                  -> Value.create $1
+    o 'Literal',                                -> Value.wrap $1
+    o 'Parenthetical',                          -> Value.wrap $1
+    o 'Range',                                  -> Value.wrap $1
     o 'This'
   ]

@@ -314,13 +314,13 @@

   # A reference to the *this* current object.
   This: [
-    o 'THIS',                                   -> Value.create new Literal 'this'
-    o '@',                                      -> Value.create new Literal 'this'
+    o 'THIS',                                   -> Value.wrap new Literal 'this'
+    o '@',                                      -> Value.wrap new Literal 'this'
   ]

   # A reference to a property on *this*.
   ThisProperty: [
-    o '@ Identifier',                           -> Value.create new Literal('this'), [new Access($2)], 'this'
+    o '@ Identifier',                           -> Value.wrap new Literal('this'), [new Access($2)], 'this'
   ]

   # The array literal.
@@ -384,7 +384,7 @@
   # A catch clause names its error and runs a block of code.
   Catch: [
     o 'CATCH Identifier Block',                 -> [$2, $3]
-    o 'CATCH Object Block',                     -> [Value.create($2), $3]
+    o 'CATCH Object Block',                     -> [Value.wrap($2), $3]
   ]

   # Throw an exception object.
@@ -433,7 +433,7 @@
   ]

   ForBody: [
-    o 'FOR Range',                              -> source: Value.create($2)
+    o 'FOR Range',                              -> source: Value.wrap($2)
     o 'ForStart ForSource',                     -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
   ]

@@ -447,8 +447,8 @@
   ForValue: [
     o 'Identifier'
     o 'ThisProperty'
-    o 'Array',                                  -> Value.create $1
-    o 'Object',                                 -> Value.create $1
+    o 'Array',                                  -> Value.wrap $1
+    o 'Object',                                 -> Value.wrap $1
   ]

   # An array or range comprehension has variables for the current element


Include changes

Include changes

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -19,9 +19,13 @@
       'solarized-dark-syntax'
     ]
     'projectHome': '/Users/stillers/workspace'
-  'whitespace':
-    'removeTrailingWhitespace': false
+  'whitespace': {}
   'bracket-matcher':
     'autocompleteBrackets': false
   'find-and-replace':
     'focusEditorAfterSearch': true
+  'atom-beautify':
+    '_analyticsUserId': '542ad6ff-4e4a-479a-bee1-7e619cab00b0'
+    'analytics': false
+    'beautifyEntireFileOnSave': false
+    'js_jslint_happy': true


update string.coffee

update string.coffee

--- js/str.coffee
+++ js/str.coffee
@@ -1,17 +1,15 @@

 define [], ->
-
   String::toCapitalCase = -> @charAt(0).toUpperCase() + @substr(1)

-  String::toCamelCase = () ->
-    (@toWords().map (p) -> p.toCapitalCase() ).join('')
+  String::toCamelCase = -> (@toWords().map (p) -> p.toCapitalCase() ).join ''

-  String::toLowerCamelCase = () ->
+  String::toLowerCamelCase = ->
     [first,rest...] = @toWords()
-    first + (rest.map (p) -> p.toCapitalCase() ).join('')
+    first + (rest.map (p) -> p.toCapitalCase() ).join ''

-  String::toWords = () -> @split(/[-_,\.]+/)
+  String::toWords = () -> @split /[-_,\.]+/

   String::toDashCase = () ->
-    @replace( /([A-Z])/g , (v) -> '-' + v.toLowerCase() ).replace( /^-/, '')
+    @replace( /([A-Z])/g , (v) -> '-' + v.toLowerCase() ).replace /^-/, ''


minor...

minor...

--- src/remote/services/hull.coffee
+++ src/remote/services/hull.coffee
@@ -87,7 +87,7 @@
         identified = true
         identify(app.config.data.me)

-      analytics.track("init", { appId: config.appId })
+      analytics.track("init", { appId: app.config.appId })
       app.core.services.add([ { path: 'hull/*path',  handler: handler } ])
       app.core.services.add([ { path: 'track/*path', handler: trackHandler } ])


Added xhr._requestHeader test helper

Added xhr._requestHeader test helper

--- src/tb/lib/ti/ajax/index.coffee
+++ src/tb/lib/ti/ajax/index.coffee
@@ -134,6 +134,8 @@

         readyState: 0

+        _requestHeader: (name) -> requestHeaders[name]
+
         setRequestHeader: (name, value) ->
           requestHeaders[name] = value


Use relative path instead of full url.

Use relative path instead of full url.

--- app/assets/javascripts/app/controllers/candidates.js.coffee
+++ app/assets/javascripts/app/controllers/candidates.js.coffee
@@ -26,7 +26,7 @@
     @phrase     = $('#app').data('phrase')
     @locale     = $('#app').data('locale')
     @params = {
-      url: "http://poichecker.dev/admin/places/#{@parent_id}/candidates/suggest"
+      url: "/admin/places/#{@parent_id}/candidates/suggest"
       processData: true

     }


Fixed tags.

Fixed tags.

--- app/assets/javascripts/app/controllers/widget/tag.js.coffee
+++ app/assets/javascripts/app/controllers/widget/tag.js.coffee
@@ -16,11 +16,11 @@
     @cacheKey = "tags::#{@object_type}::#{@object.id}"

     if @tags
-      @render(@tags)
+      @render()
       return

-    @tagList = App.Store.get( @cacheKey ) || []
-    if !_.isEmpty(@tagList)
+    @tags = App.Store.get( @cacheKey ) || []
+    if !_.isEmpty(@tags)
       @render()
       @delay(
         =>
@@ -41,14 +41,14 @@
         o_id:   @object.id
       processData: true
       success: (data, status, xhr) =>
-        @tagList = data.tags
-        App.Store.write( @cacheKey, @tagList )
+        @tags = data.tags
+        App.Store.write( @cacheKey, @tags )
         @render()
     )

   render: ->
     @html App.view('widget/tag')(
-      tags: @tagList || [],
+      tags: @tags || [],
     )

   showInput: (e) ->
@@ -70,7 +70,7 @@
       @render()
       return

-    @tagList.push item
+    @tags.push item
     @render()

     @ajax(
@@ -90,7 +90,7 @@
     item = $(e.target).parents('li').find('.tag').text()
     return if !item

-    @tagList = _.filter(@tagList, (tagItem) -> return tagItem if tagItem isnt item )
+    @tags = _.filter(@tags, (tagItem) -> return tagItem if tagItem isnt item )
     @render()

     @ajax(


prevent rendering on empty subjects array; clean up code

--- app/assets/javascripts/components/image-subject-viewer.cjsx
+++ app/assets/javascripts/components/image-subject-viewer.cjsx
@@ -32,7 +32,7 @@

   usingFakeSubject: ->
     if getUrlParamByName('use_fake_subject') is "true"
-      console.log 'Using fake subject...'
+      console.log 'DEBUG NOTE: USING FAKE SUBJECTS'
       return true
     else
       return false
@@ -75,11 +75,9 @@
     @setView 0, 0, @state.imageWidth, @state.imageHeight

     if @usingFakeSubject()
-      console.log 'Using fake subjects...'
       @setState subjectEndpoint: "./offline/example_subjects/marking_subjects.json", =>
         @fetchSubjects(@state.subjectEndpoint)
     else
-      console.log 'Using regular subjects...'
       @fetchSubjects(@state.subjectEndpoint)

     window.addEventListener "resize", this.updateDimensions
@@ -150,11 +148,9 @@

     # prepare new classification
     if @state.subjects.shift() is undefined or @state.subjects.length <= 0
-      console.log 'Out of subjects. Fetching some more...'
       @fetchSubjects(@state.subjectEndpoint)
       return
     else
-      console.log 'Loading current subject image...'
       @loadImage @state.subjects[0].location

     @state.classification = new Classification @state.subjects[0]
@@ -357,8 +353,8 @@
       $('html, body').animate scrollTop: vertical*@state.selectedMark.y-window.innerHeight/2+80, 500

   render: ->
-    return null if @state.subjects is null
-    console.log 'subjects: ', @state.subjects[0]
+    # don't render if ya ain't got subjects (yet)
+    return null if @state.subjects is null or @state.subjects.length is 0

     viewBox = [0, 0, @state.imageWidth, @state.imageHeight]


Добавил протокол, сделал проверку на [jcn

--- app/assets/javascripts/media_lib/inserter_factory.js.coffee
+++ app/assets/javascripts/media_lib/inserter_factory.js.coffee
@@ -65,13 +65,15 @@
 class MediaLib.IFrameVideoInserter extends MediaLib.BaseInserter
   constructor: (@host, @uploader, @model) ->
   insert: ($uploader) ->
+    throw "host isn't defined" unless @host
+
     $uploader.find('.js-uploader-duration').val @model.duration
     $uploader.find('.js-uploader-input').val @model.static_name
     $uploader.find('.js-uploader-input').trigger 'change'

     $uploaderVideo = $uploader.find('.js-uploader-video')
     if $uploaderVideo.length
-      $player = $("")
+      $player = $("")
       $uploaderVideo.html $player

 # Стратегия вставки видео-модели


Remove debugging code

Remove debugging code

--- app/assets/javascripts/rglossa/components/cwb-simple.coffee
+++ app/assets/javascripts/rglossa/components/cwb-simple.coffee
@@ -25,8 +25,3 @@
   search: ->
     @set('query', @_query)
     @sendAction()
-
-  queryDidChange: (->
-    debugger
-    console.log @get('query')
-  ).observes('query')


update code

update code

--- bot.coffee
+++ bot.coffee
@@ -4,7 +4,7 @@

 AUTH = process.env.AUTH
 ROOM = "mashupfm";
-bot = new PlugAPI(AUTH, "4w@fWs$");
+bot = new PlugAPI(AUTH, "h90");

 songLengthLimit = 8
 songLengthLimitSeconds = songLengthLimit * 60


fixed #55: ⍺⍉⍵ with repeating values in ⍺

--- src/vocabulary/transpose.coffee
+++ src/vocabulary/transpose.coffee
@@ -15,18 +15,27 @@
       # 'a'⍉1 2 !!! DOMAIN ERROR
       # 2⍉1 2 !!! RANK ERROR
       # 2 0 1⍉2 3 4⍴⍳24 <=> 3 4 2⍴0 12 1 13 2 14 3 15 4 16 5 17 6 18 7 19 8 20 9 21 10 22 11 23
+      # 2 0 0⍉2 3 4⍴⍳24 !!! RANK ERROR
+      # 0 0⍉3 3⍴⍳9 <=> 0 4 8
+      # 0 0⍉2 3⍴⍳9 <=> 0 4
+      # 0 0 0⍉3 3 3⍴⍳27 <=> 0 13 26
+      # 0 1 0⍉3 3 3⍴⍳27 <=> 3 3⍴0 3 6 10 13 16 20 23 26
       if alpha.shape.length > 1 then throw RankError()
       if alpha.shape.length is 0 then alpha = new APLArray [alpha.unwrap()]
       n = omega.shape.length
       if alpha.shape[0] isnt n then throw LengthError()
-      shape = Array n
-      stride = Array n
+      shape = []
+      stride = []
       for x, i in alpha.toArray()
         if not isInt x, 0 then throw DomainError()
         if x >= n then throw RankError()
-        if shape[x]? then throw DomainError 'Duplicate axis'
-        shape[x] = omega.shape[i]
-        stride[x] = omega.stride[i]
+        if shape[x]?
+          shape[x] = Math.min shape[x], omega.shape[i]
+          stride[x] += omega.stride[i]
+        else
+          shape[x] = omega.shape[i]
+          stride[x] = omega.stride[i]
+      for u in shape when not u? then throw RankError()
       new APLArray omega.data, shape, stride, omega.offset
     else
       # Transpose (`⍉`)


Switched global variable notation

Switched global variable notation

--- web/coffee/dependencies.coffee
+++ web/coffee/dependencies.coffee
@@ -1,9 +1,9 @@
-window.apiCall = (type, url, data) ->
+@apiCall = (type, url, data) ->
   $.ajax {url: url, type: type, data: data, cache: false}
   .fail (jqXHR, text) ->
     $.notify "API is offline. :(", "error"

-window.redirectIfNotLoggedIn = ->
+@redirectIfNotLoggedIn = ->
   apiCall "GET", "/api/user/status", {}
   .done (data) ->
     switch data["status"]
@@ -11,7 +11,7 @@
         if not data.data["logged_in"]
           window.location.href = "/login"

-window.redirectIfLoggedIn = ->
+@redirectIfLoggedIn = ->
   apiCall "GET", "/api/user/status", {}
   .done (data) ->
     switch data["status"]
@@ -28,7 +28,7 @@
       style = "success"
   return style

-window.apiNotify = (data) ->
+@apiNotify = (data) ->
   style = getStyle data
   $.notify data.message, style


Added left move and merger

Added left move and merger

--- js/main.coffee
+++ js/main.coffee
@@ -10,14 +10,6 @@

 buildBoard = ->
   [0..3].map -> [0..3].map -> 0
-
-# buildBoardSlow = ->
-#   board = []
-#   for row in [0..3]
-#     board[row] = []
-#     for col in [0..3]
-#       board[row][col] = 0
-#   board

 generateTile = (board) ->
   value = randomValue()
@@ -38,7 +30,6 @@

   newBoard

-
 getRow = (r, board) ->
   [board[r][0], board[r][1], board[r][2], board[r][3]]

@@ -57,9 +48,18 @@
           row[b] = 0
           break
         else if row[b] isnt 0 then break
+
+  else if direction is 'left'
+    for a in [0...3]
+      for b in [a+1..3]
+
+        if row[a] is 0 then break
+        else if row[a] == row[b]
+          row[a] *= 2
+          row[b] = 0
+          break
+        else if row[b] isnt 0 then break
   row
-
-# console.log mergeCells [4,4,4,4], 'right'

 collapseCells = (row, direction) ->
   row = row.filter (ele) -> ele isnt 0
@@ -71,14 +71,28 @@
       row.push 0
   row

-# console.log collapseCells [0,4,4,0], 'right'
-
 moveIsValid = (originalBoard, newBoard) ->
   for row in [0..3]
     for col in [0..3]
       if originalBoard[row][col] isnt newBoard[row][col]
         return true
   false
+
+boardIsFull = (board) ->
+  for row in board
+    if 0 in row
+      return false
+  true
+
+noValidMove = (board) ->
+  direction = 'right' #FIXME: Handle other directions
+  newBoard = move(board, direction)
+  if moveIsValid(board, newBoard)
+    return false
+  true
+
+isGameOver = (board) ->
+  boardIsFull(board) and noValidMove(board)

 showBoard = (board) ->
   for row in [0..3]
@@ -115,11 +129,20 @@
       # try moving
       newBoard = move(@board, direction)
       printArray newBoard
+      # check the move validity, by comparing the original and new board
       if moveIsValid(@board, newBoard)
         console.log "Valid"
         @board = newBoard
+        # generate tile
         generateTile(@board)
         showBoard(@board)
+        #check game lost
+        if isGameOver(@board)
+          console.log "YOU LOSE"
+        else
+          # show board
+          showBoard(@board)
+
       else
         console.log "Invalid"


Fix retina rendering bug

Fix retina rendering bug

--- framer/Config.coffee
+++ framer/Config.coffee
@@ -13,7 +13,6 @@
 	bottom: 0;
 	pointer-events: none;
 	overflow: hidden;
-	-webkit-perspective: 1000;
 }

 .framerLayer {
@@ -24,6 +23,7 @@
 	-webkit-overflow-scrolling: touch;
 	-webkit-box-sizing: border-box;
 	-webkit-user-select: none;
+	-webkit-perspective: 1000;
 }

 .framerLayer input,


"Added new templates"

"Added new templates"

--- assets/js/views/toolbar.coffee
+++ assets/js/views/toolbar.coffee
@@ -13,10 +13,16 @@

-              Base html5 page
+              Basic html

-              Base + jQuery
+              jQuery
+

+

+              jQuery + Coffee
+

+

+              YUI


Change test because of not being sure of order.

--- workers/feeder/tests/feeder_test.coffee
+++ workers/feeder/tests/feeder_test.coffee
@@ -75,22 +75,29 @@
     setUp (queue) ->
       expected = [testAccount, "tag1", "tag2"]
       currentIdx = 0
+      messages = {}
       queue.subscribe {ack:true, prefetchCount:1}, (m, h, d) ->
         {exchange, routingKey, consumerTag} = d
         message = m.data+"" if m.data?
         regEx = new RegExp "^#{exchangePrefix}"
         ownerString = exchange.replace regEx, ""
-        expectedOwner = expected[currentIdx]
-        console.log "#"+currentIdx+" - Message should be from the exchange of #{expectedOwner}"
-        assert.equal ownerString, expectedOwner, "should be from the exchange of #{expectedOwner}"
-        console.log "#"+currentIdx+" - Passed."
+        messages[ownerString] = message
         currentIdx++
         queue.shift()

         if currentIdx is expected.length
-          console.log "This test case passed!"
           #queue.unsubscribe consumerTag
           queue.close()
+
+          console.log "Checking received messages"
+          for key of messages
+            console.log "Message on exchange #{key}"
+            assert.equal (key in expected), true, "should receive valid key"
+            # console.log "Message should be from the exchange of #{expectedOwner}"
+            console.log "Message should be the #{testActivity}"
+            assert.equal messages[key], testActivity
+
+          console.log "This test case passed!"
           tasks.next()

       .addCallback (ok) ->


Documentation of flatMapWithConcurrencyLimit

--- readme-src.coffee
+++ readme-src.coffee
@@ -565,7 +565,7 @@
 """

 doc.fn "observable.flatMapLatest(f)", """
-like flatMap, but instead of including events from
+like `flatMap`, but instead of including events from
 all spawned streams, only includes them from the latest spawned stream.
 You can think this as switching from stream to stream.
 Note that instead of a function, you can provide a stream/property too.
@@ -576,6 +576,26 @@
 doc.fn "observable.flatMapFirst(f)", """
 like flatMap, but doesn't spawns a new
 stream only if the previously spawned stream has ended.
+
+The [Function Construction rules](#function-construction-rules) below apply here.
+"""
+
+doc.fn "observable.flatMapWithConcurrencyLimit(@ : Observable[A], limit : Number, f : A -> Observable[B] | Event[B] | B) : EventStream[B]", """
+a super method of *flatMap* family. It limits the number of open spawned streams and buffers incoming events.
+`flatMapConcat` is `flatMapWithConcurrencyLimit(1)` (only one input active),
+and `flatMap` is `flatMapWithConcurrencyLimit ∞` (all inputs are piped to output).
+
+The [Function Construction rules](#function-construction-rules) below apply here.
+"""
+
+doc.fn "observable.flatMapConcat(@ : Observable[A], f : A -> Observable[B] | Event[B] | B) : EventStream[B]", """
+a `flatMapWithConcurrencyLimit` with limit of 1.
+
+The [Function Construction rules](#function-construction-rules) below apply here.
+"""
+
+doc.fn "observable.rateLimit(@ : Observable[A], minimumInterval) : EventStream[A]", """
+rate limits and buffers changes in the observable, so at most one event in minimumInteval is issued.
 """

 doc.fn "observable.scan(seed, f) : Property[A]", """
@@ -871,6 +891,11 @@
   .input("Bacon.sequentially(200, [9,0,2,0,0,3]).filter(function(x) { return x })")
   .input("Bacon.sequentially(200, [0,1,0,12,8,0]).filter(function(x) { return x })")
   .output("function(a,b) { return a.merge(b) }")
+
+doc.fn "stream.holdWhen(@ : EventStream[A], valve : Observable[B]) : EventStream[A]", """
+pauses and buffers the event stream if last event in valve is truthy.
+All buffered events are released when valve becomes falsy.
+"""

 doc.fn "stream.startWith(value)", """
 adds a starting value to the stream, i.e. concats a


Client: Activity: formatting

Client: Activity: formatting

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -148,7 +148,7 @@
       thread = thread.set 'channel', channels.get channelId
       thread.update 'messages', (msgs) -> msgs.map (messageId) ->
         message = messages.get messageId
-        if message.has('__editedBody')
+        if message.has '__editedBody'
           message = message.set 'body', message.get '__editedBody'
         if message.has '__editedPayload'
           message = message.set 'payload', message.get '__editedPayload'


doXhrRequest: add timeout support

doXhrRequest: add timeout support

--- client/app/lib/util/doXhrRequest.coffee
+++ client/app/lib/util/doXhrRequest.coffee
@@ -1,5 +1,5 @@
 module.exports = (options = {}, callback) ->
-  { type, endPoint, data, async } = options
+  { type, endPoint, data, async, timeout } = options
   type = 'POST'  unless type

   async or= yes
@@ -40,6 +40,14 @@

     return callback null, response

+  if timeout?
+    xhr.timeout   = timeout
+    xhr.ontimeout = ->
+      return callback {
+        message : 'operation timed out'
+        code    : xhr.status
+      }
+
   requestData = JSON.stringify data  if data

   return xhr.send requestData


lab/Modal: introduce disabled state for ModalFooter

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/component-lab/Modal/Modal.coffee
+++ client/component-lab/Modal/Modal.coffee
@@ -67,13 +67,19 @@

-      <Button type={secondaryButtonType} size={primaryButtonSize} onClick={onSecondaryButtonClick}>
-        {secondaryButtonTitle}
-      </Button>
+      <Button
+        disabled={props.disabled}
+        type={secondaryButtonType}
+        size={primaryButtonSize}
+        onClick={onSecondaryButtonClick}
+        children={secondaryButtonTitle} />

-      <Button type={primaryButtonType} size={secondaryButtonSize} onClick={onPrimaryButtonClick}>
-        {primaryButtonTitle}
-      </Button>
+      <Button
+        disabled={props.disabled}
+        type={primaryButtonType}
+        size={secondaryButtonSize}
+        onClick={onPrimaryButtonClick}
+        children={primaryButtonTitle} />


Fix typo

Fix typo

--- client/components/PaymentSuccess.coffee
+++ client/components/PaymentSuccess.coffee
@@ -13,7 +13,7 @@
         <Grid>

Оплата успешна

Оплата занятий успешна, срок занятий на сайте будет продлен в ближайшее время после обработки платежа.
-            Обычно обработка занимает несколько секунд, в особых случаях может продолжаться несколко часов.
+            Обычно обработка занимает несколько секунд, в особых случаях может продолжаться несколько часов.
             Если через два часа срок оплаченных занятий не будет продлен, свяжитесь со мной.

Чек об оплате (в соответствии с законом о самозанятых; чек может появиться не сразу, а через 20-30 секунд):


Annotation focus hack

Annotation focus hack

--- client/publication.coffee
+++ client/publication.coffee
@@ -622,6 +622,12 @@

     Meteor.Router.toNew Meteor.Router.annotationPath Session.get('currentPublicationId'), Session.get('currentPublicationSlug'), template.data._id

+    # Focus immediately after converting local annotation
+    # TODO: Improve this
+    Meteor.setTimeout ->
+      $(template?.findAll '.body[contenteditable=true]').get(0)?.focus()
+    , 100
+
     # On click to convert local annotation we are for sure inside the annotation, so we can
     # immediatelly send a mouse enter event to make sure related highlight has a hovered state
     $('.viewer .display-wrapper .highlights-layer .highlights-layer-highlight').trigger 'annotationMouseenter', [template.data._id]
@@ -638,10 +644,11 @@
     else
       Meteor.Router.toNew Meteor.Router.annotationPath Session.get('currentPublicationId'), Session.get('currentPublicationSlug'), template.data._id

-    # Focus by clicking on the annotation
-    # TODO: Make it work when converting local annotation
-    # TODO: Fix this and make it work
-    #body = $(template.findAll '.body[contenteditable=true]').get(0)?.focus()
+    # Focus body no matter where the annotation was clicked
+    # TODO: Improve this
+    Meteor.setTimeout ->
+      $(template?.findAll '.body[contenteditable=true]').get(0)?.focus()
+    , 100

     return # Make sure CoffeeScript does not return anything


Client: Activity: Updated onScrollThresholdReached event default handler

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -12,7 +12,7 @@
     title                    : null
     messages                 : null
     isDataLoading            : no
-    onScrollThresholdReached : no
+    onScrollThresholdReached : kd.noop

   componentDidUpdate: ->


Fix avatar view for guests

Fix avatar view for guests

--- client/app/CommonViews/avatarviews/avatarview.coffee
+++ client/app/CommonViews/avatarviews/avatarview.coffee
@@ -35,27 +35,22 @@
           @tooltip.getView().updateData data if data?.profile.nickname

     @bgImg = null
-
-  # click:(event)->
-  #   event.stopPropagation()
-  #   event.preventDefault()
-  #   @getTooltip().hide()
-  #   @utils.defer => @emit 'LinkClicked'
-  #   return no
+    @fallbackUri = "#{KD.apiUri}/images/defaultavatar/default.avatar.#{options.size.width}.png"
+
+  setAvatar:(uri)->
+    if @bgImg isnt uri
+      @$().css "background-image", uri
+      @bgImg = uri

   render:->
     account = @getData()
     return unless account
-    {profile} = account
-    options = @getOptions()
-    fallbackUri = "#{KD.apiUri}/images/defaultavatar/default.avatar.#{options.size.width}.png"
-
-    # this is a temp fix to avoid avatar flashing on every account change - Sinan 08/2012
-    bgImg = "url(//gravatar.com/avatar/#{profile.hash}?size=#{options.size.width}&d=#{encodeURIComponent fallbackUri})"
-
-    if @bgImg isnt bgImg
-      @$().css "background-image", bgImg
-      @bgImg = bgImg
+
+    {profile, type} = account
+    return @setAvatar "url(#{@fallbackUri})"  if type is 'unregistered'
+
+    {width} = @getOptions().size
+    @setAvatar "url(//gravatar.com/avatar/#{profile.hash}?size=#{width}&d=#{encodeURIComponent @fallbackUri})"

     flags = account.globalFlags?.join(" ") ? ""
     @$('cite').addClass flags
@@ -77,8 +72,6 @@
     @profileName = new KDCustomHTMLView
       tagName : 'a'
       cssClass : 'profile-name'
-      # click:(event)=>
-      #   KD.getSingleton('router').handleRoute "/#{@getData().profile.nickname}", state:@getData()
       attributes:
         href : "/#{@getData().profile.nickname}"
         target : '_blank'


ComputeController.UI: auto change type of known fields like ssh_private_key

--- client/app/lib/providers/computecontroller.ui.coffee
+++ client/app/lib/providers/computecontroller.ui.coffee
@@ -35,6 +35,9 @@
     fn args...

+  KNOWN_FIELD_TYPES   =
+    'ssh_private_key' : 'textarea'
+
   @generateAddCredentialFormFor = (options) ->

     { provider, requiredFields, defaultTitle, defaultValues, callback } = options
@@ -53,7 +56,7 @@
       for field in requiredFields
         continue  if field is '__rawContent'
         name = field.name ? field
-        type = field.type ? 'text'
+        type = field.type ? KNOWN_FIELD_TYPES[field] ? 'text'
         { values } = field
         credentialFields[name] = {
           label: name.capitalize()