Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -25,6 +25,25 @@
     ###*
     Generates a public jQuery style getter / setter method for each
     String argument.
+
+

+
+    myObject = Core
+      r: 255
+      g: 0
+      b: 100
+
+    myObject.attrAccessor "r", "g", "b"
+
+    myObject.r(myObject.r() - 1)
+
+    => 254
+
+    myObject.I.r
+
+    => 254
+
+

     @name attrAccessor
     @methodOf Core#


Using crazy new include lookup for non-string modules

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -171,7 +171,8 @@
       send: (name, args...) ->
         self[name](args...)

-    # Initial module inclue, for reconst
+    # Initial module inclue, for reconstructing objects from JSON
+

     return self
 )()


--- src/core_object.coffee
+++ src/core_object.coffee
@@ -171,7 +171,7 @@
       send: (name, args...) ->
         self[name](args...)

-
+    # Initial module inclue, for

     return self
 )()


refactored config aggregator

refactored config aggregator

--- lib/TemplateConfig.coffee
+++ lib/TemplateConfig.coffee
@@ -63,26 +63,17 @@
   # used to combine or reduce a value if one already exists in the context.
   # can be a map that aggregates specific properties
   aggregate: (context, key, value, existing) =>
-    if sysmo.isFunction(@config.aggregate)
-      !! context[key] = @config.aggregate(key, value, existing)
-    else if sysmo.isFunction(@config.aggregate?[key])
-      !! context[key] = @config.aggregate[key](key, value, existing)
-    else false
+    aggregator = @config.aggregate?[key] or @config.aggregate
+
+    return false if !sysmo.isFunction(aggregator)
+
+    context[key] = aggregator(key, value, existing)
+
+    return true

   applyFormatting: (node, value, key) =>
-    if sysmo.isFunction(@config.format)
-      @format @config.format, node, value, key
-
-    else if sysmo.isFunction(@config.format?[key])
-      @format @config.format[key], node, value, key
-
-    else @ensureKeyValue key, value
-
-  format: (formatter, node, value, key) =>
-    pair = formatter node, value, key
-    @ensureKeyValue key, value, pair
-
-  ensureKeyValue: (key, value, pair = {}) ->
+    formatter = @config.format?[key] or @config.format
+    pair = if sysmo.isFunction(formatter) then formatter(node, value, key) else {}
     pair.key = key if 'key' not of pair
     pair.value = value if 'value' not of pair
     pair


adding rerequire to the css bundler to rerequire files and prevent caching of files

--- src/css_bundler.coffee
+++ src/css_bundler.coffee
@@ -7,14 +7,17 @@
     @files = toArray(files).map(resolve).map(require.resolve)

   compile: ->
-    @files.map(require).join('')
-
+    @files.map(@rerequire).join('')
+
   createServer: ->
-    console.log('building server')
     (req, res, next) =>
       content = @compile()
       res.writeHead(200, 'Content-Type': 'text/css')
       res.end(content)
+
+  rerequire: (file) ->
+    delete require.cache[file]
+    require(file)

 module.exports =
   CssBundler: CssBundler


Added module name to logger Replaced msg with res

--- src/hubot-archive-today.coffee
+++ src/hubot-archive-today.coffee
@@ -21,15 +21,15 @@
         FOUND: /https:\/\/archive.is\/.{5}/i
         NOT_FOUND: /Invalid URL:/i

-    robot.respond /archive (.+)/i, (msg) ->
-        data = "url=#{encodeURIComponent(msg.match[1])}"
-        msg.http(ARCHIVE_TODAY_SUBMIT_URL)
+    robot.respond /archive (.+)/i, (res) ->
+        data = "url=#{encodeURIComponent(res.match[1])}"
+        res.http(ARCHIVE_TODAY_SUBMIT_URL)
             .header("content-type", "application/x-www-form-urlencoded")
-            .post(data) (err, res, body) ->
-                robot.logger.debug body
+            .post(data) (err, httpRes, body) ->
+                robot.logger.debug "hubot-archive-today: #{body}"

                 if err or body.match(PATTERNS.NOT_FOUND)
-                    return msg.reply "#{msg.match[1]} is not a valid URL."
+                    return res.reply "#{res.match[1]} is not a valid URL."

                 links = body.match PATTERNS.FOUND
-                msg.reply links[0]
+                res.reply links[0]


Add translation for collaborator, close #153

--- app/pages/project/science-case.cjsx
+++ app/pages/project/science-case.cjsx
@@ -9,6 +9,7 @@
   projectRoles:
     title: 'The team'
     owner: 'Owner'
+    collaborator: 'Collaborator'
     translator: 'Translator'

 module.exports = React.createClass


LiveStreaming: Clean up session_id handling

* Use `session_id` as a param in both indices and segments
* Fix stream index if no session id is given

--- src/streammachine/outputs/live_streaming.coffee
+++ src/streammachine/outputs/live_streaming.coffee
@@ -62,7 +62,7 @@
             # session id attached.  If so, we'll proxy it through to the URLs
             # for the ts files.

-            session_id = @opts.req.param("session")
+            session_id = @opts.req.param("session_id")

             @opts.res.writeHead 200,
                 "Content-type": "application/vnd.apple.mpegurl"
@@ -83,7 +83,7 @@
                 http://#{@stream.opts.host}/#{@stream.key}/ts/#{seg.id}.#{@stream.opts.format}
                 """

-                @opts.res.write seg_record + ( session_id && "?session_id=#{session_id}" ) + "\n"
+                @opts.res.write seg_record + ( (session_id && "?session_id=#{session_id}") || "" ) + "\n"

             @opts.res.end()

@@ -107,7 +107,7 @@
                 for key,s of @group.streams
                     @opts.res.write """
                     #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=#{s.opts.bandwidth},CODECS="#{s.opts.codec}"
-                    http://#{s.opts.host}/#{s.key}.m3u8?session=#{session_id}
+                    http://#{s.opts.host}/#{s.key}.m3u8?session_id=#{session_id}

                     """


Fix typo

Fix typo

--- scripts/jacon.coffee
+++ scripts/jacon.coffee
@@ -7,5 +7,5 @@

 module.exports = (robot) ->
-  robot.head /@jacon/i, (msg) ->
+  robot.hear /@jacon/i, (msg) ->
     msg.send "@JakePearce ^"


only when directed at hubot

only when directed at hubot

--- scripts/kicks.coffee
+++ scripts/kicks.coffee
@@ -7,9 +7,6 @@
 module.exports = (robot) ->
   robot.hear /kicks hubot/i, (msg) ->
     msg.send "*oof*"
-
-  robot.respond /you suck/i, (msg) ->
-    msg.send gifs.come_at_me_bro

   robot.hear /hubot you suck/i, (msg) ->
     msg.send gifs.come_at_me_bro


<server> Getting ready to deal with drivers...

--- server/config.coffee
+++ server/config.coffee
@@ -20,7 +20,7 @@
 			# actuatorsDrivers = ...
 			# sensorsDrivers = ...

-			# Pass reference to controllers:
+			# @todo Pass their reference to controllers:
 			# require('./controllers/actuator').setDrivers actuatorsDrivers
 			# require('./controllers/sensor').setDrivers sensorsDrivers


Cast counts from lrange to integer

Cast counts from lrange to integer

--- server/limits.coffee
+++ server/limits.coffee
@@ -240,8 +240,10 @@
 		lrange(@key + ':' + id, 0, -1).then (counts) ->

 			# } Sum all the scores.
-			counts.reduce ((l, r) -> l + r), 0
-
+			counts.map(
+				(count) -> parseInt count, 10
+
+			).reduce ((l, r) -> l + r), 0

 	# ### .ttl
 	#


GOV-335, only redirect login errors for unauth users to static error page

--- shared/router.coffee
+++ shared/router.coffee
@@ -158,4 +158,6 @@
   #catch onlogin failures and redirect to static error page
   Accounts.onLoginFailure (err) ->
     console.log("on login error:" + JSON.stringify(err))
-    Router.go 'othervpc'+    errno = err?.error?.error
+    if errno == 418 or errno == Accounts.LoginCancelledError.numericError
+      Router.go 'othervpc'


Rule を更新できるように

Rule を更新できるように

--- src/coffee/kazitori.coffee
+++ src/coffee/kazitori.coffee
@@ -56,7 +56,7 @@

 ## Kazitori クラス
 class Kazitori
-  VERSION:"0.2.3"
+  VERSION:"0.9.9"
   history:null
   location:null
   handlers:[]
@@ -67,6 +67,8 @@
   notFound:null
   beforeAnytimeHandler:null
   direct:null
+  #ルーターをネストできる最大回数
+  maxDepth: 4
   isIE:false
   #URL パラメーター
   _params:
@@ -359,29 +361,62 @@
     if not callback
       if isBefore
         callback = @_bindFunctions(name)
-      else if name isinstanceof Kazitori
+      else if name instanceof Kazitori
         console.log "nest"
-        #@.handlers.concat name.handlers
-        #@.beforeHandlers.concat name.beforeHandlers
-        #return @
+        @_bindChild(rule, name)
+        return @
       else if typeof name is "function"
-        callback = name
+        #これ再帰的にチェックするメソッドに落としこもう
+        #__super__ って coffee のクラスだけなきガス
+        #むーん
+        if name.hasOwnProperty('__super__')
+          try
+            child = new name({'isAutoStart':false})
+            @_bindChild(rule, child)
+            return @
+          catch e
+            callback = name
+        else
+          callback = name
       else
         callback = @[name]

     target = if isBefore then @.beforeHandlers else @.handlers
+    console.log "before rule instance::", rule
     target.unshift new Rule(rule, (fragment)->
       args = @.router.extractParams(@, fragment)
       callback && callback.apply(@.router, args)
     ,@)
     return @

+  _bindChild:(rule, child)->
+    for childRule in child.handlers
+      childRule.update(rule)
+    @.handlers = child.handlers.concat @.handlers
+
+
   #ルールを後から追加する
-  # add:(rule)->
-    # return @
+  appendRouter:(child, childRoot)->
+    if not child instanceof Kazitori
+      #throw new Error("引数が不正です")
+      return false
+    return @

   #ルールを削除する
-  # remove:(rule)->
+  removeRouter:(child)->
+    if not child instanceof Kazitori
+      # throw new Error("引数が不正です")
+      return false
+    #if if rule isinstanceof String
+      #まま
+    # else if rule is inspired Kazitori
+      #まとめて削除
+    #else if rule is Rule
+      #単品
+    # else
+      #エラーにしないで return false とかのほうがいいかな
+      #console.error('TypeError.')
+      #throw new Error('rule じゃない')
     # return @

@@ -478,7 +513,6 @@
     return matched

-
   beforeFailed:(event)=>
     @.beforeFailedHandler.apply(@, arguments)
     @._beforeDeffer.removeEventListener(KazitoriEvent.TASK_QUEUE_FAILED, @beforeFailed)
@@ -518,12 +552,6 @@

   # befores から指定された事前に処理したいメソッドをバインド
   _bindBefores:()->
-    if not @.befores?
-      return
-    befores = @_keys(@.befores)
-    for key in befores
-      @registerHandler(key, @.befores[key], true)
-
     if @.beforeAnytime
       callback = @_bindFunctions(@.beforeAnytime)
       @.beforeAnytimeHandler = {
@@ -532,6 +560,11 @@
           callback && callback.apply(@, args)
         ,@
       }
+    if not @.befores?
+      return
+    befores = @_keys(@.befores)
+    for key in befores
+      @registerHandler(key, @.befores[key], true)
     return

   # notFound で指定されたメソッドをバインド
@@ -552,7 +585,7 @@
       callback = notFoundFuncName
     else
       callback = @[notFoundFuncName]
-
+    console.log notFoundFragment, @.notFound
     @._notFound = new Rule(notFoundFragment, (fragment)->
       args = @.router.extractParams(@, fragment)
       callback && callback.apply(@.router, args)
@@ -616,8 +649,6 @@
     return if matched.length > 0 then matched else false

-
-
   #===============================================
   #
   # URL Queries
@@ -858,29 +889,30 @@
 # ちょっと大げさな気もするけど外部的には変わらんし
 # 今後を見据えてクラス化しておく
 class Rule
-  rule:null
+  rule:""
   _regexp:null
   callback:null
   name:""
   router:null
   isVariable:false
   types:[]
-  constructor:(string, callback, router)->
-    @rule = string
+  constructor:(rule, callback, router)->
+    if typeof rule isnt "string" and typeof rule isnt "Number"
+      return
     @callback = callback
-    @_regexp = @_ruleToRegExp(string)
-
-    #これ…どうなんだろ…
     @router = router
-    @types = []
-
-    re = new RegExp(namedParam)
-    matched = string.match(re)
-    if matched isnt null
-      @isVariable = true
-      for m in matched
-        t = m.match(genericParam)||null
-        @types.push if t isnt null then t[1] else null
+    @update(rule)
+    # @rule = string
+    # @_regexp = @_ruleToRegExp(string)
+    # @types = []
+
+    # re = new RegExp(namedParam)
+    # matched = string.match(re)
+    # if matched isnt null
+    #   @isVariable = true
+    #   for m in matched
+    #     t = m.match(genericParam)||null
+    #     @types.push if t isnt null then t[1] else null

   #マッチするかどうかテスト
   # **args**
@@ -893,6 +925,28 @@
   _ruleToRegExp:(rule)->
     newRule = rule.replace(escapeRegExp, '\\$&').replace(optionalParam, '(?:$1)?').replace(namedParam, '([^\/]+)').replace(splatParam, '(.*?)')
     return new RegExp('^' + newRule + '$')
+
+  update:(path)=>
+    @.rule = path + @.rule
+    @._regexp = @_ruleToRegExp(@.rule)
+
+    re = new RegExp(namedParam)
+    matched = path.match(re)
+    if matched isnt null
+      @isVariable = true
+      for m in matched
+        t = m.match(genericParam)||null
+        @types.push if t isnt null then t[1] else null
+
+
+#うーん…
+class InternalGroup
+  prefix: null #root ?
+  rules: []
+  constructor:(prefix, rules)->
+    @.prefix = prefix
+    @.rules = rules
+

 class EventDispatcher
   listeners:{}


Fixed opening Untitled.txt when tried to open new file from file tree. #notagoodcommit

--- client/app/Applications/Ace.kdapplication/aceappview.coffee
+++ client/app/Applications/Ace.kdapplication/aceappview.coffee
@@ -56,7 +56,7 @@

   viewAppended:->
     super
-    @utils.defer => @addNewTab() if @tabView.panes.length is 0
+    @utils.wait 30, => @addNewTab() if @tabView.panes.length is 0

   addNewTab: (file) ->
     file = file or FSHelper.createFileFromPath 'localfile:/Untitled.txt'


NavigationActivityLink: Fixed new activity items counter for activity link on navigation panel.

--- client/app/MainApp/navigation/navigationactivitylink.coffee
+++ client/app/MainApp/navigation/navigationactivitylink.coffee
@@ -14,7 +14,6 @@
       partial   : ""
       click     : =>
         @setActivityLinkToDefaultState()
-        appManager.tell "Activity", "unhideNewItems"

     @count.hide()

@@ -22,19 +21,21 @@
       tagName   : "span"
       cssClass  : "main-nav-icon #{__utils.slugify @getData().title}"

-    @utils.wait 1000, =>
-      KD.getSingleton("activityController").on "ActivitiesArrived", (activities) =>
+    mainController = KD.getSingleton "mainController"
+
+    mainController.ready =>
+      activityController = KD.getSingleton "activityController"
+      activityController.on "ActivitiesArrived", =>
         return if KD.getSingleton("router").currentPath is "/Activity"

-        appManager.tell "Activity", "getNewItemsCount", (itemCount) =>
-          @updateNewItemsCount itemCount
+        newItemsCount = activityController.getNewItemsCount()
+        @updateNewItemsCount newItemsCount  if newItemsCount > 0

-    mainController = KD.getSingleton "mainController"
+        activityController.on "NewItemsCounterCleared", @bound "setActivityLinkToDefaultState"
+
     mainController.on "NavigationLinkTitleClick", (options) =>
-      @setActivityLinkToDefaultState() if options.appPath is "Activity"
-
-    mainController.on "ShouldResetNavigationTitleLink", =>
-      @setActivityLinkToDefaultState()
+      if options.appPath is "Activity"
+        KD.getSingleton("activityController").clearNewItemsCount()

   updateNewItemsCount: (itemCount) ->
     return if itemCount is 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -51,7 +51,7 @@
   flicker: ({duration: 30, frequency: 3, alpha: 0.5}) ->
     Object.reverseMerge I,
       flickerDuration: 30
-      f
+      flickerFrequency:

     I.flickerDuration = duration
     I.flickerFrequency = frequency


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -54,6 +54,7 @@
         duration: 5
         frequency: 0.2
         alpha: 0.3
+
       # => This causes the sprite to flicker between full opacity
       # => and 30% opacity every 5 frames for 90 frames


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -73,3 +73,5 @@
     I.flickerDuration = options.duration
     I.flickerFrequency = options.frequency
     I.flickerAlpha = options.alpha
+
+    return self


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -49,6 +49,8 @@
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###
   flicker: ({duration: 30, frequency: 3, alpha: 0.5}) ->
+    Object.reverse
+
     I.flickerDuration = duration
     I.flickerFrequency = frequency
     I.flickerAlpha = alpha


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -147,7 +147,6 @@
   }

   modules = GameObject.defaultModules.concat(I.includedModules)
-  modules = modules.without(I.excludedModules)

   modules.each (Module) ->
     self.include Module


adding in proptypes for project attribute to group component

--- src/components/exercise/group.cjsx
+++ src/components/exercise/group.cjsx
@@ -39,6 +39,7 @@

   propTypes:
     group: React.PropTypes.oneOf(_.keys(RULES)).isRequired
+    project: React.PropTypes.oneOf(['tutor', 'concept-coach']).isRequired

   getDefaultProps: ->
     group: 'default'


update homework end message with turn in information

--- src/components/task-step/ends.cjsx
+++ src/components/task-step/ends.cjsx
@@ -32,8 +32,7 @@

       {@props.breadcrumbs}
       <BS.Panel bsStyle="default" footer={footer} className='-homework-completed'>
-
Turn in your homework.

-
Great Job!

+
Your homework will be automatically turned in at the due date.

       </BS.Panel>


mask primitive

mask primitive

--- src/primitives/types/classes.coffee
+++ src/primitives/types/classes.coffee
@@ -22,6 +22,7 @@
   vertex:            require './transform/vertex'
   fragment:          require './transform/fragment'
   layer:             require './transform/layer'
+  mask:              require './transform/mask'

   array:             require './data/array'
   interval:          require './data/interval'


facebook fix ensureLoggedin

facebook fix ensureLoggedin

--- src/remote/services/facebook.coffee
+++ src/remote/services/facebook.coffee
@@ -15,17 +15,18 @@
     super(config, gateway)
     @loadFbSdk(@getSettings())

-  request : (args...)->
+  request : (args...)=>
     @ensureLoggedIn().then ()=> @performRequest(args...)

-  ensureLoggedIn : ()->
+  ensureLoggedIn : ()=>
+    self = this
     new Promise (resolve, reject)=>
       args = Array.prototype.slice.call(arguments)
       if @fbUser?.status=='connected'
         resolve()
       else
         FB.getLoginStatus (res)=>
-          @updateFBUserStatus(res)
+          self.updateFBUserStatus(res)
           if res.status=='connected' then resolve() else reject()
         , true
       # , true ~ Maybe we dont need a roundtrip each time.


Open DNS dnsdb is not passive

Open DNS dnsdb is not passive

--- src/scripts/opendns-umbrella.coffee
+++ src/scripts/opendns-umbrella.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   OpenDNS Umbrella Passive DNS & Reputation
+#   OpenDNS Umbrella DNS database & Reputation
 #
 # Dependencies:
 #   None


Strengthens IP regex to validate that each octet is in valid range

--- src/scripts/reputation-links.coffee
+++ src/scripts/reputation-links.coffee
@@ -15,7 +15,8 @@
 #   Scott J Roberts - @sroberts

 module.exports = (robot) ->
-  robot.respond /reputation ip (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/i, (msg) ->
+  robot.respond /reputation ip ((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))/i, (msg) ->
+
     target_ip = msg.match[1].toLowerCase()

     reputation_links = """#{target_ip} IP Reputation:


Closure is finally working

Closure is finally working

--- src/scripts/views/SourceFile.coffee
+++ src/scripts/views/SourceFile.coffee
@@ -19,10 +19,10 @@
     .appendTo "#{vd.filelistContentId()} ul"

   _formatCode: ->
-    s = @code.split("\n")
     parent = $("

")
+    cnt = 0

-    for line in s
+    for line in @code.split("\n")
       linediv = $("

")
       loc = $("

")

@@ -31,4 +31,13 @@

       linediv.append loc
       parent.append linediv
+
+      # Attach a click handler to enable the creation of breakpoints
+      f = (cnt) ->
+        linediv.click =>
+          console.log "#{@filename}:#{@id} at cnt #{cnt}"
+      f.call(linediv, cnt)
+
+      cnt++
+
     @formatted_code = parent


Fixe missing verification on room vote when no player

--- src/server/RoomPlayerManager.coffee
+++ src/server/RoomPlayerManager.coffee
@@ -42,7 +42,7 @@
 		return players;

 	getMainPlayer: () ->
-		if not @havePlayer then throw "No player connected"
+		if not @havePlayer() then throw "No player connected"
 		return @players.front();

 	addPlayer:	(player) ->


fix https://github.com/sagemathinc/smc/issues/818: "getting file listing for undefined" when trashing files

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -489,6 +489,9 @@
     set_directory_files : (path, sort_by_time, show_hidden) =>
         if not path?
             path = @get_store().get('current_path')
+        if not path?
+            # nothing to do if path isn't defined -- there is no current path -- see https://github.com/sagemathinc/smc/issues/818
+            return

         if not @_set_directory_files_lock?
             @_set_directory_files_lock = {}


test(iscroll): test directive and controller

--- test/spec/directives/iscroll.coffee
+++ test/spec/directives/iscroll.coffee
@@ -8,7 +8,10 @@
   beforeEach inject ($rootScope, $compile) ->
     scope = $rootScope.$new()
     scope.$apply()
-    element = $compile('

') scope
+    element = $compile('
+

+

+
') scope

   describe 'element', ->
     it 'should have immediate child `bp-iscroll-wrapper`', ->
@@ -16,3 +19,21 @@

     it 'should have transcluded content', ->
       expect(element.children().children().is '.inner' ).toBe true
+
+  describe 'directive', ->
+    it 'should init and destroy iscroll', inject ($timeout) ->
+      $timeout.flush()
+      iscroll = scope.getIScroll()
+      expect(iscroll instanceof IScroll).toBe true
+      expect(scope.getIScrollSticky() instanceof IScrollSticky).toBe true
+      spyOn iscroll, 'destroy'
+      element.trigger '$destroy'
+      expect(iscroll.destroy).toHaveBeenCalled()
+
+  describe 'controller', ->
+    it 'should set iscroll instance', ->
+      foo = {}
+      bar = {}
+      scope.setIScroll foo, bar
+      expect(scope.getIScroll()).toBe foo
+      expect(scope.getIScrollSticky()).toBe bar


Cosmetic

Cosmetic

--- tests/html/src/endtoendtests.coffee
+++ tests/html/src/endtoendtests.coffee
@@ -8,8 +8,7 @@
     else
         asyncTest protocol, ->
             expect(5)
-            path = '/echo'
-            url = if /^http/.test(path) then path else client_opts.url + path
+            url = client_opts.url + '/echo'

             code = """
             hook.test_body(!!document.body, typeof document.body);


Fix for empty cache launch on windows

Fix for empty cache launch on windows

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -133,6 +133,7 @@

   expireAvatarCache: ->
     fs.readdir path.join(app.getDataPath(), 'Cache/settings-view'), (error, _files) ->
+      _files ?= []
       files = {}
       for filename in _files
         parts = filename.split('-')


Implement getFilterKey

Implement getFilterKey

--- lib/diff-list-view.coffee
+++ lib/diff-list-view.coffee
@@ -9,14 +9,15 @@
   getEmptyMessage: ->
     'No diffs'

+  getFilterKey: ->
+    'lineText'
+
   attach: ->
     @storeFocusedElement()
     atom.workspaceView.appendToTop(this)
     @focusFilterEditor()

-  viewForItem: ({oldStart, newStart, oldLines, newLines}) ->
-    bufferRow = if newStart > 0 then newStart - 1 else newStart
-    lineText = @editor.lineForBufferRow(bufferRow) ? ''
+  viewForItem: ({oldStart, newStart, oldLines, newLines, lineText}) ->
     $$ ->
       @li class: 'two-lines', =>
         @div lineText, class: 'primary-line'
@@ -24,6 +25,9 @@

   populate: ->
     diffs = atom.project.getRepo()?.getLineDiffs(@editor.getPath(), @editor.getText()) ? []
+    for diff in diffs
+      bufferRow = if diff.newStart > 0 then diff.newStart - 1 else diff.newStart
+      diff.lineText = @editor.lineForBufferRow(bufferRow) ? ''
     @setItems(diffs)

   toggle: ->


DEMO: add spinning hourglass icon

DEMO: add spinning hourglass icon

--- demo/src/demos/Playground.coffee
+++ demo/src/demos/Playground.coffee
@@ -977,6 +977,7 @@
 			"fa-list-ul",
 			"fa-picture-o",
 			"fa-sitemap",
+			"fa-hourglass-half",
 		]
 		dt.addExample("Single Icon",
 			new CUI.Block
@@ -1047,12 +1048,38 @@
 				text: "Label"
 				icon: "fa-spinner"
 		)
+
+		dt.addExample("Hourglass spinner",
+			new CUI.Block
+				class: "cui-demo-hourglass"
+				content: @getHourglassIcon()
+		)

 		dt.addExample("Times Thin, regular font icon (not FA)",
 			new CUI.Label
 				text: "Close"
 				icon: "fa-times-thin"
 		)
+
+	getHourglassIcon: ->
+		hourglass_icons = [
+			"fa-hourglass-start"
+			"fa-hourglass-half"
+			"fa-hourglass-end"
+			"fa-hourglass-end"
+			"fa-hourglass-o"
+		]
+		hourglass_container = CUI.dom.div("cui-hourglass-animation fa-stack")
+
+		for i in hourglass_icons
+			icon = new CUI.Icon
+				icon: i
+				class: "fa-stack-1x"
+
+			CUI.dom.append(hourglass_container, icon.DOM)
+
+		hourglass_container
+

 	getIconCollection: (icons = []) ->
 		icon_container = CUI.dom.div("cui-demo-icon-container")


fix: reloading extensions with multiple startPanels

--- src/javascripts/app.coffee
+++ src/javascripts/app.coffee
@@ -22,9 +22,9 @@
     ChapterMarks,
     Download,
     EpisodeInfo,
+    Share,
     Playlist,
     Playerjs,
-    Share,
     Transcript,
   ]

@@ -149,9 +149,11 @@
     @theme.removeButtons()
     @theme.removePanels()
     PodigeePodcastPlayer.defaultExtensions.forEach (extension) =>
-      self.extensions[extension.extension.name] = new extension(self)
+      name = extension.extension.name
+      self.extensions[name] = new extension(self)
+      return if self.options.startPanels && self.options.startPanels.length
       if currentlyActiveExtension instanceof extension
-        self.theme.togglePanel(self.extensions[extension.extension.name].panel)
+        self.theme.togglePanel(self.extensions[name].panel)

   bindWindowResizing: =>
     $(window).on('resize', _.debounce(@sendSizeChange, 250))


исправлены баги с сохранением и загрузкой состояния

--- lib/nbuild/builder.coffee
+++ lib/nbuild/builder.coffee
@@ -191,6 +191,7 @@
       @state[name] = _.extend @state[name], value
     else
       @state[name] = value
+    @_saveState()

   ###*
   * Load state from file
@@ -198,9 +199,10 @@
   ###

   _loadState: ->
-    if existsSync(STATE_FILE)
+    path = join(@defines.PROJECT_DIR, STATE_FILE)
+    if existsSync(path)
       try
-        data = fs.readFileSync(join(@defines.PROJECT_DIR, STATE_FILE), 'utf-8')
+        data = fs.readFileSync(path, 'utf-8')
         @state = JSON.parse(data)
       catch err
         console.log "Warning: invalid state file #{STATE_FILE}!"
@@ -262,7 +264,6 @@
        type isnt 'define'
       config = @_expandConfig(config)
     @types[type](name, config)
-    @_saveState()

   ###*
   * Find command config
@@ -390,6 +391,8 @@
           console.log "rmdir #{entry.path}" if @verbose
         catch err
           console.warn "Warning: can't delete dir #{entry.path}".yellow
+    delete @state[options['step-name']]
+    @_saveState()

   ###*
   * call node


fix backgroundColor

fix backgroundColor

--- framer/Context.coffee
+++ framer/Context.coffee
@@ -262,10 +262,10 @@

 	@define "backgroundColor",
 		get: ->
-			return @_rootElement?.style["backgroundColor"]
+			return @_element?.style["backgroundColor"]
 		set: (value) ->
 			if Color.isColor(value)
-				@_rootElement?.style["backgroundColor"] = new Color value.toString()
+				@_element?.style["backgroundColor"] = new Color value.toString()

 	@define "perspective",
 		get: ->


optimizing dasharray and make tests pass

--- framer/SVGPath.coffee
+++ framer/SVGPath.coffee
@@ -43,7 +43,13 @@
 		endLength = @strokeEnd ? @length
 		dasharray = []
 		if endLength is startLength
-			dasharray = [0, startLength, 0, @length - endLength]
+			if startLength isnt 0
+				dasharray.push(0)
+				dasharray.push(startLength)
+			remaining = @length - endLength
+			if remaining isnt 0
+				dasharray.push(0)
+				dasharray.push(remaining)
 		else if endLength < startLength
 			gap = startLength - endLength
 			remaining = @length - startLength


works on 0.5 too

works on 0.5 too

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -33,7 +33,7 @@
         items:
           type: 'string'
       numberOfThreads:
-        title: 'Number of Threads (only on Julia v0.6+, experimental)'
+        title: 'Number of Threads (experimental)'
         type: 'integer'
         default: 1
     order: 3


fix image path error

fix image path error

--- lib/pandoc-wrapper.coffee
+++ lib/pandoc-wrapper.coffee
@@ -1,8 +1,9 @@
 path = require 'path'
 fs = require 'fs'
 {execFile} = require 'child_process'
-matter = require('gray-matter')
+matter = require 'gray-matter'
 # temp = require 'temp'
+{parseMD} = require './md'

 getFileExtension = (documentType)->
   if documentType == 'pdf_document'
@@ -46,6 +47,38 @@

   Object.assign({}, data, config)

+processPaths = (text, outputDir, projectDirectoryPath, rootDirectoryPath)->
+  match = null
+  offset = 0
+  output = ''
+
+  resolvePath = (src)->
+    if src.startsWith('http://') or
+       src.startsWith('https://') or
+       src.startsWith('atom://') or
+       src.startsWith('file://') or
+       src.startsWith('#')
+      return src
+    else if src.startsWith('/')
+      return path.relative(outputDir, path.resolve(projectDirectoryPath, '.'+src))
+    else # ./test.png or test.png
+      return path.relative(outputDir, path.resolve(rootDirectoryPath, src))
+
+  # replace path in ![](...) and []()
+  r = /(\!?\[.*?]\()([^\)|^'|^"]*)(.*?\))/gi
+  text = text.replace r, (whole, a, b, c)->
+    if b[0] == '<'
+      b = b.slice(1, b.length-1)
+      a + '<' + resolvePath(b.trim()) + '> ' + c
+    else
+      a + resolvePath(b.trim()) + ' ' + c
+
+  # replace path in tag
+  r = /(<[img|a|iframe].*?[src|href]=['"])(.+?)(['"].*?>)/gi
+  text = text.replace r, (whole, a, b, c)->
+    a + resolvePath(b) + c
+
+  text
 ###
 title
 author
@@ -57,8 +90,6 @@
   config = loadOutputYAML md, config
   text = matter.stringify(text, config)
   args = []
-
-  console.log config

   extension = null
   outputConfig = null
@@ -89,17 +120,17 @@

   console.log args.join(' ')

-  program = execFile 'pandoc', args
+  # change link path to relative path
+  text = processPaths text, path.dirname(outputFilePath), md.projectDirectoryPath, md.rootDirectoryPath
+
+  # change working directory
+  cwd = process.cwd()
+  process.chdir(path.dirname(outputFilePath))
+
+  program = execFile 'pandoc', args, (err)->
+    process.chdir(cwd) # change cwd back
+    throw err if err
+    atom.notifications.addInfo "File #{path.basename(outputFilePath)} was created", detail: "path: #{outputFilePath}"
   program.stdin.end(text)

-  program.on 'exit', ()->
-    atom.notifications.addInfo "File #{path.basename(outputFilePath)} was created", detail: "path: #{outputFilePath}"
-
-  program.on 'error', (err)->
-    throw err
-  ###
-  , (error)->
-    throw error if error
-    atom.notifications.addInfo "File #{path.basename(outputFilePath)} was created", detail: "path: #{outputFilePath}"
-  ###
 module.exports = pandocConvert


Limit sync store consistency check chain to 20

--- app/src/storage/synced_store.coffee
+++ app/src/storage/synced_store.coffee
@@ -217,7 +217,7 @@
   _computeCommit: (data, changes) ->
     data = @_applyChanges(data, changes)
     commitHash = ledger.crypto.SHA256.hashString _(data).toJson()
-    data.__hashes = [commitHash].concat(data.__hashes or [])
+    data.__hashes = [commitHash].concat(data.__hashes.slice(0, @HASHES_CHAIN_MAX_SIZE - 1) or [])
     [commitHash, data]

   # @return A jQuery promise


...or not, got a couple lines around the wrong way. going to go ahead and blame emacs and it's crazy buffer system for that

--- assets/js/student.coffee
+++ assets/js/student.coffee
@@ -29,13 +29,14 @@
         return

 $('#scratch').keyup ->
-    if $("#jqprotect").length > 0
+    if $("#jqprotect").length == 0
       disallowDollar = /\$/
       if disallowDollar.test $(this).val()
           dollarCharacterConsideredHarmful()
           return
-      $(".alert-error").hide 'explode', 1000
       student.emit 'edit', {text:$(this).val()}
+    $(".alert-error").hide 'explode', 1000
+

 output = (txt) ->
     $("#console").val $("#console").val()+txt+"\n>> "


Add to the list of permissive licenses

Add to the list of permissive licenses

--- src/legal-eagle.coffee
+++ src/legal-eagle.coffee
@@ -87,7 +87,7 @@
 readIfExists = (path) ->
   readFileSync(path, 'utf8') if existsSync(path)

-PermissiveLicenses = ['MIT', 'BSD', 'Apache', 'WTF']
+PermissiveLicenses = ['MIT', 'BSD', 'Apache', 'WTF', 'LGPL', 'ISC']

 omitPermissiveLicenses = (licenseSummary) ->
   for name, {license} of licenseSummary


closes #104

closes #104

--- app/partials/main-footer.cjsx
+++ app/partials/main-footer.cjsx
@@ -13,7 +13,7 @@
   footer:
     info:
       content: '''
-        The Zooniverse is a collection of web-based Citizen Science projects that use the efforts of volunteers to help researchers deal with the flood of data that confronts them.
+        The Zooniverse is the world’s largest and most popular platform for people-powered research. This research is made possible by hundreds of thousands of volunteers around the world. Zooniverse research results in new discoveries, datasets useful to the wider research community, and many publications.
       '''
       callToAction: '''See the Zooniverse'''
     # change this to api fetch (either launch approved or featured?) when project categories exist


Revert "Search completion; promote the top completion."

This reverts commit 4309dcd3030687f3ed02b86bbdf7c485baaee4a5.

This is a bad idea.  It pushes the suggestion at the top of the list out
of the way.  Users aren't going to like that if it's the one they're
looking for.

Conflicts:
	background_scripts/completion.coffee

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -289,7 +289,7 @@
         queryTerms: queryTerms
         type: "domain"
         url: domains[0]?[0] ? "" # This is the URL or an empty string, but not null.
-        relevancy: 2
+        relevancy: 1
       ].filter (s) -> 0 < s.url.length

   # Returns a list of domains of the form: [ [domain, relevancy], ... ]
@@ -499,7 +499,7 @@
       type: description
       url: Utils.createSearchUrl queryTerms, searchUrl
       title: queryTerms.join " "
-      relevancy: 2
+      relevancy: 1
       autoSelect: custom
       highlightTerms: not haveCompletionEngine
       isSearchSuggestion: true
@@ -511,15 +511,12 @@
         @previousSuggestions[url] = new Suggestion
           queryTerms: queryTerms
           type: description
-          url: Utils.createSearchUrl suggestion, searchUrl
+          url: url
           title: suggestion
           insertText: suggestion
           highlightTerms: false
           highlightTermsExcludeUrl: true
           isCustomSearch: custom
-          # The first (top) suggestion gets a score of 1.  This puts it two <Tab>s away if a domain completion
-          # is present (which has a score of 2), and one <Tab> away otherwise.
-          relevancy: if 0 < count++ then null else 1
           relevancyFunction: @computeRelevancy
           relevancyData: factor


Call focus explicit for ediable, prevent focus loss in firefox

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -132,6 +132,7 @@
     # convert temp mention to mention
     node = document.createTextNode(String.fromCharCode(160)) #
     $(node).insertAfter(@_current.$tempMention)
+    @$editable.focus() # refocus editable, required for firefox
     @_setCaretToEndOf(node)

     @_current.$tempMention.replaceWith($mention)


fix failable test

fix failable test


upgrade atom and set a bigger font size

upgrade atom and set a bigger font size

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,11 +1,11 @@
 'release-notes':
-  'viewedVersion': '0.94.0'
+  'viewedVersion': '0.95.0'
 'welcome':
   'showOnStartup': false
 'editor':
   'showIndentGuide': false
   'fontFamily': 'Source Code Pro'
-  'fontSize': 12
+  'fontSize': 13
 'core':
   'audioBeep': false
   'disabledPackages': [


Use Menlo in Atom

Use Menlo in Atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,41 +1,35 @@
-{
-  "editor": {
-    "fontSize": 15,
-    "showIndentGuide": true
-  },
-  "core": {
-    "excludeVcsIgnoredPaths": true,
-    "ignoredNames": [
-      ".bundle",
-      ".git",
-      "log",
-      "repositories",
-      "tmp",
-      "vendor"
-    ],
-    "themes": [
-      "unity-ui",
-      "glacier-syntax"
-    ],
-    "hideGitIgnoredFiles": true,
-    "disabledPackages": [
-      "background-tips",
-      "exception-reporting",
-      "metrics"
-    ]
-  },
-  "spell-check": {
-    "grammars": [
-      "text.plain",
-      "source.gfm",
-      "text.git-commit"
-    ]
-  },
-  "welcome": {
-    "showOnStartup": false
-  },
-  "release-notes": {
-    "viewedVersion": "0.85.0"
-  },
-  "tree-view": {}
-}
+'editor':
+  'fontSize': 15
+  'showIndentGuide': true
+  'fontFamily': 'Menlo'
+'core':
+  'excludeVcsIgnoredPaths': true
+  'ignoredNames': [
+    '.bundle'
+    '.git'
+    'log'
+    'repositories'
+    'tmp'
+    'vendor'
+  ]
+  'themes': [
+    'unity-ui'
+    'glacier-syntax'
+  ]
+  'hideGitIgnoredFiles': true
+  'disabledPackages': [
+    'background-tips'
+    'exception-reporting'
+    'metrics'
+  ]
+'spell-check':
+  'grammars': [
+    'text.plain'
+    'source.gfm'
+    'text.git-commit'
+  ]
+'welcome':
+  'showOnStartup': false
+'release-notes':
+  'viewedVersion': '0.85.0'
+'tree-view': {}


Allow Markdown in setting descriptions

Allow Markdown in setting descriptions

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -210,16 +210,22 @@
 appendOptions = (namespace, name, value) ->
   keyPath = "#{namespace}.#{name}"
   title = getSettingTitle(keyPath, name)
-  description = getSettingDescription(keyPath)
+  description = markdown(getSettingDescription(keyPath))
   options = atom.config.getSchema(keyPath)?.enum ? []

   @label class: 'control-label', =>
     @div class: 'setting-title', title
-    @div class: 'setting-description', description
+    @div class: 'setting-description', =>
+      @raw(description)

   @select id: keyPath, class: 'form-control', =>
     for option in options
       @option value: option, option
+
+marked = null
+markdown = (text) ->
+  marked ?= require 'marked'
+  marked(text).replace(/<p>(.*)<\/p>/, "$1")

 appendCheckbox = (namespace, name, value) ->
   keyPath = "#{namespace}.#{name}"


:lipstick: Use @constructor instead of EditorView

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -1663,7 +1663,7 @@
   # Private:
   getCharacterWidthCache: (scopes, char) ->
     scopes ?= NoScope
-    obj = EditorView.characterWidthCache
+    obj = @constsructor.characterWidthCache
     for scope in scopes
       obj = obj[scope]
       return null unless obj?
@@ -1672,7 +1672,7 @@
   # Private:
   setCharacterWidthCache: (scopes, char, val) ->
     scopes ?= NoScope
-    obj = EditorView.characterWidthCache
+    obj = @constructor.characterWidthCache
     for scope in scopes
       obj[scope] ?= {}
       obj = obj[scope]
@@ -1680,7 +1680,7 @@

   # Private:
   clearCharacterWidthCache: ->
-    EditorView.characterWidthCache = {}
+    @constructor.characterWidthCache = {}

   pixelOffsetForScreenPosition: (position) ->
     {top, left} = @pixelPositionForScreenPosition(position)


Support for arrays, afFieldInput options and deeply nested objects

--- autoform-fixtures.coffee
+++ autoform-fixtures.coffee
@@ -58,86 +58,104 @@
   getFakeTextCallback = getFakeTextCallback || getFakeText
   result = {}
   schema = ss.schema()
-  for k of schema
-    field = schema[k]
-    if field.autoform?.omit or k.slice(-2) is ".$"
+  for schemaK of schema
+    joinPathComponents = (a, b)->
+      if a and a.length > 0
+        return a + "." + b
+      else
+        return b
+    getSubkeys = (baseString, remainingComponents)->
+      if remainingComponents.length == 0
+        return [baseString]
+      if remainingComponents[0] == "$"
+        return _.chain(_.range(result[baseString].length))
+          .map((v, idx)->
+            return getSubkeys(
+              joinPathComponents(baseString, idx),
+              remainingComponents.slice(1))
+          )
+          .flatten()
+          .value()
+      else
+        return getSubkeys(
+          joinPathComponents(baseString, remainingComponents[0]),
+          remainingComponents.slice(1))
+
+    field = schema[schemaK]
+    if field.autoform?.omit
       continue
-    if field.type.name is "Object"
-      result[k] = {}
-      continue
-    if field.type.name is "Array"
-      element = ss.schema("#{k}.$")
-      if ["String", "Number"].indexOf(element.type.name) + 1
-        if field.autoform?.options
-          values = getValues(field.autoform.options, element.type)
-          count = field.maxCount || 3 # values.length
-          result[k] = fillValues(values, count)
-        # else
-        #   throw new Error("#{k} - ss without options")
-      else
-        throw new Error("#{k} - not supported type [#{element.type.name}]")
-      continue
-    if field.autoform?.options
-      values = getValues(field.autoform.options, field.type)
-      result[k] = values[Math.floor(Math.random() * values.length)]
-      continue
-    if field.type.name is "String"
-      if field.max
-        max = field.max
-        max = max.call() if typeof max is "function"
-        result[k] = getFakeTextCallback(k, max)
-      else
-        result[k] = getFakeTextCallback(k)
-      continue
-    if field.type.name is "Number"
-      min = 0
-      if field.min
-        min = field.min
-        min = min.call() if typeof min is "function"
-      max = 9
-      if field.max
-        max = field.max
-        max = max.call() if typeof max is "function"
-      range = [min..max]
-      result[k] = range[Math.floor(Math.random() * range.length)]
-      continue
-    if field.type.name is "Date"
-      min = false
-      if field.min
-        min = field.min
-        min = min.call() if typeof min is "function"
-      max = false
-      if field.max
-        max = field.max
-        max = max.call() if typeof max is "function"
-      if min and max
-        days = moment(max).diff(moment(min), 'days')
-        result[k] = moment(min).add(Math.round(Math.random() * days), 'day').toDate()
-      else if min
-        result[k] = moment(min).add(Math.round(Math.random() * 15000), 'day').toDate()
-      else if max
-        result[k] = moment(max).add(-Math.round(Math.random() * 15000), 'day').toDate()
-      else
-        result[k] = new Date()
-      continue
-    if field.type.name is "Boolean"
-      result[k] = !!Math.round(Math.random())
-      continue
+
+    for k in getSubkeys("", schemaK.split('.'))
+      arrayField = null
+      if schemaK.slice(-2) == ".$"
+        arrayField = schema[schemaK.slice(0, -2)]
+      if field.type.name is "Object"
+        result[k] = {}
+        continue
+      if field.type.name is "Array"
+        count = field.maxCount || 3
+        result[k] = _.range(count)
+        continue
+      options = field.autoform?.options or
+        field.autoform?.afFieldInput?.options or
+        arrayField?.autoform?.options or
+        arrayField?.autoform?.afFieldInput?.options
+      if options
+        values = getValues(options, field.type)
+        result[k] = values[Math.floor(Math.random() * values.length)]
+        continue
+      if field.type.name is "String"
+        if field.max
+          max = field.max
+          max = max.call() if typeof max is "function"
+          result[k] = getFakeTextCallback(k, max)
+        else
+          result[k] = getFakeTextCallback(k)
+        continue
+      if field.type.name is "Number"
+        min = 0
+        if field.min
+          min = field.min
+          min = min.call() if typeof min is "function"
+        max = 9
+        if field.max
+          max = field.max
+          max = max.call() if typeof max is "function"
+        range = [min..max]
+        result[k] = range[Math.floor(Math.random() * range.length)]
+        continue
+      if field.type.name is "Date"
+        min = false
+        if field.min
+          min = field.min
+          min = min.call() if typeof min is "function"
+        max = false
+        if field.max
+          max = field.max
+          max = max.call() if typeof max is "function"
+        if min and max
+          days = moment(max).diff(moment(min), 'days')
+          result[k] = moment(min).add(Math.round(Math.random() * days), 'day').toDate()
+        else if min
+          result[k] = moment(min).add(Math.round(Math.random() * 15000), 'day').toDate()
+        else if max
+          result[k] = moment(max).add(-Math.round(Math.random() * 15000), 'day').toDate()
+        else
+          result[k] = new Date()
+        continue
+      if field.type.name is "Boolean"
+        result[k] = !!Math.round(Math.random())
+        continue
   result

 AutoForm.Fixtures.normalizeData = (data) ->
   result = {}
   for k of data
     namespace = k.split(".")
-    # stupid code, sorry
-    if namespace.length is 1
-      result[namespace[0]] = data[k]
-    else if namespace.length is 2
-      result[namespace[0]][namespace[1]] = data[k]
-    else if namespace.length is 3
-      result[namespace[0]][namespace[1]][namespace[2]] = data[k]
-    else
-      throw new Error("Current version is support only 3 level of namespace")
+    resultObjPointer = result
+    for pathComponent in namespace.slice(0,-1)
+      resultObjPointer = resultObjPointer[pathComponent]
+    resultObjPointer[namespace.slice(-1)[0]] = data[k]
   result

 AutoForm.Fixtures.getData = (ss, getFakeTextCallback) ->


getPath() is deprecated

getPath() is deprecated

--- lib/show-todo-view.coffee
+++ lib/show-todo-view.coffee
@@ -142,7 +142,7 @@
       ignoreFromSettings = atom.config.get('todo-show.ignoreThesePaths')

       for ignorePath in ignoreFromSettings
-        ignoredPath = atom.project.getPath() + ignorePath
+        ignoredPath = atom.project.getPaths()[0] + ignorePath

         if e.filePath.substring(0, ignoredPath.length) == ignoredPath
           include = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/emitterable.coffee
+++ src/emitterable.coffee
@@ -32,7 +32,7 @@
   self.bind 'draw', (canvas) ->
     I.particles.invoke "draw", canvas

-  self.bind 'overlay',
+  self.bind 'overlay', () ->

   self.bind 'update', (dt) ->
     I.batchSize.times ->


Whitespace clean-up.

Whitespace clean-up.

--- src/environment.coffee
+++ src/environment.coffee
@@ -12,7 +12,6 @@
     # Create a new environment.
     constructor: () ->
         #FIXME: add arguments for gravity and whatnot.
-
         @logger = new timber.Logger("timber.Environment")
         @logger.debug("Initializing")

@@ -42,15 +41,11 @@

     # Return the force of drag on the element.
     getDragForce : (element) ->
-
         # FIXME: this is broken at low speeds, and will in fact return a force
         # that is larger than the element's velocity.
-
         s = element.speed
         s = if s > 1 then s * s else s
-
         m = 0.5 * @density * s * element.dragCoefficient
-
         return element.direction.scale(-m)


Add license override for specificity@0.1.3

The author added a license to the repository

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -35,3 +35,7 @@

       MIT/X11
     """
+  'specificity@0.1.3':
+    repository: 'https://github.com/keeganstreet/specificity'
+    license: 'MIT'
+    source: 'package.json in repository'


refactored the way the icon rotation is applied

--- lib/vehicle_marker.coffee
+++ lib/vehicle_marker.coffee
@@ -1,10 +1,9 @@
 require('sensor-maps/marker')

-# Icons required for the vehicle marker
 SM.VehicleMovingIcon = L.Icon.extend
   iconUrl: '/assets/images/marker-blue.png'
   iconSize: new L.Point(16, 22)
-  shadowSize: new L.Point(16, 22)
+  shadowSize: new L.Point(1, 1)
   iconAnchor: new L.Point(8,11)

 SM.VehicleStoppedIcon = L.Icon.extend
@@ -46,8 +45,9 @@
       mapObject._reset() # A reset is needed to redraw the layer
   ).observes('icon')
   _rotationDidChange: ( ->
-    $img = $(@get('mapObject')._icon)
-    rotation = @getPath('vehicle.heading')
-    webkitTransform = $img.css('-webkit-transform')
-    $img.css('-webkit-transform', webkitTransform + " rotate(#{rotation}deg)")
+    # TODO: rotation changes are lost when the map is zoomed
+    img = @get('mapObject')._icon
+    rotation = @getPath('vehicle.heading') || 0
+    value = img.style.getPropertyValue('-webkit-transform')
+    img.style.setProperty('-webkit-transform',value + " rotate(#{rotation}deg)")
   ).observes('vehicle.heading')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -50,10 +50,9 @@
   ###
   flicker: ({duration: 30, frequency: 3, alpha: 0.5}) ->
     Object.reverseMerge I,
-      flickerAlpha
+      flickerAlpha: 0.5
       flickerDuration: 30
-      flickerFrequency: 3
-
+      flickerFrequency: 3

     I.flickerDuration = duration
     I.flickerFrequency = frequency


Add tacticId and locationKey to FB model

--- src/formbuilder.coffee
+++ src/formbuilder.coffee
@@ -182,6 +182,10 @@

     field 'document', visible: no

+    field 'tacticId', visible: no
+
+    field 'locationKey', visible: no
+
     field 'urlParts', visible: no

     field 'assets', visible: no, value: #{@toCoffeeString @assets.getAssets(), urls}


Store illustration details on the rectangle marks

--- overrides.coffee
+++ overrides.coffee
@@ -69,7 +69,7 @@
     ms.rescale current_tool.mark.left - MARGIN, current_tool.mark.top - MARGIN, w, h

   if task.key is 'details'
-    value = decisionTree.valueChain[subjectViewer.taskIndex]
+    value = current_tool.mark.details
     task.reset value if value?

   if task.key is 'details'


alternate version with logos

alternate version with logos

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -25,7 +25,7 @@
         If you’d like to try a similar project, how about:

Chicago Wildlife Watch

-

Chimp & See

+

Chimp & See


Include alarm description.

Include alarm description.

--- src/scoutapp-awssns.coffee
+++ src/scoutapp-awssns.coffee
@@ -85,6 +85,10 @@
           title: 'Reason for State Change'
           value: alarmObject.NewStateReason
           short: false
+        ,
+          title: 'Description'
+          value: alarmObject.AlarmDescription
+          short: false
         ]

       else


Defer building parser until eval'ing

Defer building parser until eval'ing

--- src/packages/command-panel/src/command-interpreter.coffee
+++ src/packages/command-panel/src/command-interpreter.coffee
@@ -4,9 +4,9 @@
 module.exports =
 class CommandInterpreter
   constructor: (@project) ->
-    @parser = PEG.buildParser(fs.read(require.resolve 'command-panel/commands.pegjs'))

   eval: (string, activeEditSession) ->
+    @parser ?= PEG.buildParser(fs.read(require.resolve 'command-panel/commands.pegjs'))
     compositeCommand = @parser.parse(string)
     @lastRelativeAddress = compositeCommand if compositeCommand.isRelativeAddress()
     compositeCommand.execute(@project, activeEditSession)


Support sending to a specific index

Support sending to a specific index

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -142,6 +142,7 @@
   setupSendDefaults component
   # Set up bracket forwarding rules
   setupBracketForwarding component, config
+  component.ordered = config.ordered
   # Create the processing function
   component.process (input, output, context) ->
     # Abort unless WirePattern-style preconditions don't match
@@ -301,12 +302,18 @@
   ports.forEach (port) ->
     outProxy[port] =
       connect: ->
-      beginGroup: (group) ->
-        output.sendIP port, new IP 'openBracket', group
-      send: (data) ->
-        output.sendIP port, new IP 'data', data
-      endGroup: (group) ->
-        output.sendIP port, new IP 'closeBracket', group
+      beginGroup: (group, idx) ->
+        ip = new IP 'openBracket', group
+        ip.index = idx
+        output.sendIP port, ip
+      send: (data, idx) ->
+        ip = new IP 'data', data
+        ip.index = idx
+        output.sendIP port, ip
+      endGroup: (group, idx) ->
+        ip = new IP 'closeBracket', group
+        ip.index = idx
+        output.sendIP port, ip
       disconnect: ->
   if ports.length is 1
     return outProxy[ports[0]]


Use the isLegacy helper

Use the isLegacy helper

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -335,7 +335,7 @@
   # Subscribe to events from all connected sockets and re-emit them
   subscribeSocket: (socket, source) ->
     socket.on 'connect', =>
-      if source and typeof source.component.load is 'undefined'
+      if source and source.component.isLegacy()
         # Handle activation for legacy components
         source.component.__openConnections = 0 unless source.component.__openConnections
         source.component.__openConnections++
@@ -368,7 +368,7 @@
         id: socket.getId()
         socket: socket
         metadata: socket.metadata
-      if source and source.component.__openConnections?
+      if source and source.component.isLegacy()
         # Handle deactivation for legacy components
         source.component.__openConnections--
         if source.component.__openConnections < 0


expose jasmine

expose jasmine

--- jasmine-harness.coffee
+++ jasmine-harness.coffee
@@ -1,4 +1,4 @@
-require './spec/lib/jasmine.js'
+jasmine = require './spec/lib/jasmine.js'
 require './spec/lib/jasmine-html.js'
 require './spec/lib/jasmine.tap_reporter.js'


minor changes to options script for last steps in options page redesign

--- src/lib/options.coffee
+++ src/lib/options.coffee
@@ -269,7 +269,7 @@
         showErrors errors
   # Prompt the user to confirm removal of the selected template.
   $('#delete_btn').click ->
-    $('.delete_hdr').html i18n.get 'opt_delete_header',
+    $('.delete_hdr').html i18n.get 'opt_delete_wizard_header',
       [templates.find('option:selected').text()]
     $('#delete_con').modal 'show'
   # Cancel the template removal process.
@@ -333,10 +333,10 @@
   $('.export_copy_btn').on 'click', (event) ->
     $this = $ this
     ext.copy $('.export_content').val(), yes
-    $this.text i18n.get 'copied'
+    $this.text i18n.get 'opt_export_wizard_copy_alt_button'
     $this.delay 800
     $this.queue ->
-      $this.text i18n.get 'copy'
+      $this.text i18n.get 'opt_export_wizard_copy_button'
       $this.dequeue()
     event.preventDefault()
   # Deselect all of the templates in the list.
@@ -453,10 +453,10 @@
     $this = $ this
     $('.import_file_btn').val ''
     $('.import_content').val ext.paste()
-    $this.text i18n.get 'pasted'
+    $this.text i18n.get 'opt_import_wizard_paste_alt_button'
     $this.delay 800
     $this.queue ->
-      $this.text i18n.get 'paste'
+      $this.text i18n.get 'opt_import_wizard_paste_button'
       $this.dequeue()
   # Select all of the templates in the list.
   $('.import_select_all_btn').on 'click', ->
@@ -539,19 +539,16 @@

 # Bind the event handlers required for controlling toolbar behaviour changes.
 loadToolbarControlEvents = ->
-  buttons     = $ '#toolbarPopup, #notToolbarPopup'
-  description = $ '#toolbarPopup_desc'
+  buttons = $ '#toolbarPopup, #notToolbarPopup'
   # Bind a click event to listen for changes to the button selection.
   buttons.click ->
-    id = $(this).attr 'id'
-    if id is 'toolbarPopup'
-      description.text i18n.get 'opt_toolbar_popup_text'
-      $(".notToolbarPopup").hide()
-      $(".toolbarPopup").show()
-    else
-      description.text i18n.get 'opt_toolbar_not_popup_text'
-      $(".toolbarPopup").hide()
-      $(".notToolbarPopup").show()
+    switch $(this).attr 'id'
+      when 'notToolbarPopup'
+        $('.toolbarPopup').hide()
+        $('.notToolbarPopup').show()
+      when 'toolbarPopup'
+        $('.notToolbarPopup').hide()
+        $('.toolbarPopup').show()
   buttons.filter('.active').click()

 # Bind the event handlers required for persisting toolbar behaviour changes.
@@ -920,6 +917,8 @@
     i18n.init
       footer:
         opt_footer: "#{new Date().getFullYear()}"
+      version_definition:
+        opt_guide_standard_version_text: ext.version
     # Bind tab selection event to all tabs.
     initialTabChange = yes
     $('a[tabify]').click ->
@@ -963,8 +962,6 @@
       ext.SHORTCUT_MAC_MODIFIERS
     else
       ext.SHORTCUT_MODIFIERS
-    # Insert current extension version where required.
-    $('.version-replace').text ext.version
     # Initialize all popovers, tooltips and *go-to* links.
     $('[popover]').each ->
       $this   = $ this


fixed broken references in options.coffee

--- src/lib/options.coffee
+++ src/lib/options.coffee
@@ -384,7 +384,7 @@
 loadImages = ->
   imagePreview = $ '#feature_image_preview'
   images       = $ '#feature_image'
-  for image in ext.images
+  for image in ext.IMAGES
     $('<option/>',
       text:  image.name
       value: image.id
@@ -555,7 +555,7 @@
       existing.title = feature.title
   existing.enabled = feature.enabled
   # Only only existing images.
-  for image in ext.images when image.id is feature.image
+  for image in ext.IMAGES when image.id is feature.image
     existing.image = feature.image
     break
   # Only allow valid keyboard shortcuts.
@@ -708,7 +708,7 @@
         shortcut: ''
         title:    utils.i18n 'untitled'
       # Only allow existing images.
-      for image in ext.images when image.id is feature.image
+      for image in ext.IMAGES when image.id is feature.image
         newFeature.image = feature.image
         break
       # Only allow valid keyboard shortcuts.
@@ -877,9 +877,9 @@
     # Load the current option values.
     load()
     if ext.isThisPlatform 'mac'
-      $('#feature_shortcut_txt').html ext.shortcutMacModifiers
+      $('#feature_shortcut_txt').html ext.SHORTCUT_MAC_MODIFIERS
     else
-      $('#feature_shortcut_txt').html ext.shortcutModifiers
+      $('#feature_shortcut_txt').html ext.SHORTCUT_MODIFIERS
     # Initialize all faceboxes.
     $('a[facebox]').click ->
       $.facebox div: $(this).attr 'facebox'


Updates the query for a show's related articles to MPv2.

--- src/desktop/apps/show/components/related_articles/view.coffee
+++ src/desktop/apps/show/components/related_articles/view.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 template = -> require('./template.jade') arguments...
-metaphysics = require '../../../../../lib/metaphysics.coffee'
+metaphysics = require '../../../../../lib/metaphysics2.coffee'
 Backbone = require 'backbone'
 module.exports = class RelatedArticlesView extends Backbone.View

@@ -15,18 +15,16 @@
     metaphysics
       variables: show_id: @showId
       query: '
-        query($show_id: String!)
-        {
-          related_articles: articles(show_id: $show_id) {
+        query ($show_id: String!) {
+          related_articles: articles(showID: $show_id) {
             id
             href
             title
-            thumbnail_title
+            thumbnail_title: thumbnailTitle
             author {
               name
-              href
             }
-            thumbnail_image {
+            thumbnail_image: thumbnailImage {
               cropped(width: 300, height: 225) {
                 url
               }


:steam_locomotive::recycle: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,7 +1,6 @@
-version: "0.2.1"
+version: "0.3.0"
 remoteDependencies: [
   "//code.jquery.com/jquery-1.10.1.min.js"
   "//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"
   "http://strd6.github.io/tempest/javascripts/envweb.js"
-  "http://strd6.github.io/require/v0.2.2.js"
 ]


調整前に戻した

調整前に戻した

--- grunt/copy.coffee
+++ grunt/copy.coffee
@@ -1,25 +1,5 @@
 module.exports =
-	dev_pc:
-		files: [
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_PC_PATH %><%= FLA_DIR %>assets/images/"
-			src: [
-				"**/*"
-			]
-			dest: "<%= DEV_PC_PATH %><%= IMAGE_DIR %>"
-		]
-	dev_sp:
-		files: [
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %><%= FLA_DIR %>assets/images/"
-			src: [
-				"**/*"
-			]
-			dest: "<%= DEV_SP_PATH %><%= IMAGE_DIR %>"
-		]
-	production_pc:
+	production:
 		files: [
 			expand: true
 			flatten: false
@@ -31,70 +11,8 @@
 				"!<%= HANDLEBARS_LAYOUTS_DIR %>**"
 				"!<%= HANDLEBARS_DATA_DIR %>**"
 				"!<%= SASS_DIR %>**"
-				"!js/app.js"
+				"!**/*.map"
 				"!**/*.hbs"
-				"!**/*.map"
 			]
 			dest: "<%= PRODUCTION_PC_PATH %>"
 		]
-	production_sp:
-		files: [
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %><%= IMAGE_DIR %>"
-			src: [
-				"**/*.{png,gif,jpg,jpeg}"
-			]
-			dest: "<%= PRODUCTION_SP_PATH %><%= IMAGE_DIR %>"
-		,
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %><%= CSS_DIR %>"
-			src: [
-				"*.css"
-			]
-			dest: "<%= PRODUCTION_SP_PATH %><%= CSS_DIR %>"
-		,
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %><%= JS_DIR %>"
-			src: [
-				"*.js"
-			]
-			dest: "<%= PRODUCTION_SP_PATH %><%= JS_DIR %>"
-		,
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %><%= PDF_DIR %>"
-			src: [
-				"*.pdf"
-			]
-			dest: "<%= PRODUCTION_SP_PATH %><%= PDF_DIR %>"
-		,
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_SP_PATH %>"
-			src: [
-				"**/*.html"
-				"**/*.htm"
-			]
-			dest: "<%= PRODUCTION_SP_PATH %>"
-		]
-	production_fp:
-		files: [
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_FP_PATH %><%= IMAGE_DIR %>"
-			src: [
-				"**/*.gif"
-			]
-			dest: "<%= PRODUCTION_FP_PATH %><%= IMAGE_DIR %>"
-		,
-			expand: true
-			flatten: false
-			cwd: "<%= DEV_FP_PATH %>"
-			src: [
-				"**/*.{html,htm}"
-			]
-			dest: "<%= PRODUCTION_FP_PATH %>"
-		]


Redesign help

Redesign help

--- modules/help.coffee
+++ modules/help.coffee
@@ -8,25 +8,37 @@
     pfx = d.data.prefix or @prefix
     gstr = ""
     if msg.guild
-      gstr = "\nPrefix for `#{msg.guild.name}`: #{pfx}"
-    reply = """
-    **#{@engine.settings.name} #{@engine.settings.version}**
-    Running FocaBotCore #{@engine.version}
-    #{gstr}
-
-    #{process.env.HELP_MESSAGE or ''}
-
-    Changelog: https://thebitlink.gitbooks.io/focabot-docs/content/Changelog.html
-    Command List: https://thebitlink.gitbooks.io/focabot-docs/content/Commands.html
-    Audio Filters: https://thebitlink.gitbooks.io/focabot-docs/content/Filters.html
-    Configuration: https://thebitlink.gitbooks.io/focabot-docs/content/Configuration.html
-    """
-    msg.author.openDM().then (dm)=>
-      dm.sendMessage reply
-      msg.reply 'Check your DMs!' if msg.channel.guild_id
-
+      gstr = "\n**Prefix for #{msg.guild.name}**: `#{pfx}`"
+    msg.channel.sendMessage '', false, {
+      url: 'https://focabot.thebit.link/'
+      color: 0x00AAFF if not Core.settings.debug
+      color: 0xFF3300 if Core.settings.debug
+      author: {
+        name: "#{@engine.settings.name} #{@engine.settings.version}"
+        icon_url: Core.bot.User.avatarURL
+      }
+      description: """
+      Running FocaBotCore #{@engine.version}
+      #{gstr}
+      #{process.env.HELP_MESSAGE or ''}
+      """
+      fields: [
+        {
+          name: 'Help Links:',
+          value: "[Commands](https://focabot.thebit.link/commands?prefix=#{encodeURIComponent pfx}) / " +
+          "[Filters](https://focabot.thebit.link/filters?prefix=#{encodeURIComponent pfx}) / " +
+          "[Manual](https://focabot.thebit.link/manual)"
+        }
+      ]
+      footer:
+        icon_url: "https://cdn.discordapp.com/avatars/164588804362076160/a_fb8ec7f2fefd17b5759a403e18f27929.jpg"
+        text: "Made by TheBITLINK#3141"
+    }
+    # msg.author.openDM().then (dm)=>
+    #   dm.sendMessage reply
+    #   msg.reply 'Check your DMs!' if msg.channel.guild_id

   filtersCommandFunction: (msg,args)=>
-    msg.reply 'To learn more about audio filters, check this link: https://thebitlink.gitbooks.io/focabot-docs/content/Filters.html'
+    msg.reply 'To learn more about audio filters, check this link: https://focabot.thebit.link/filters?prefix=#{encodeURIComponent pfx}'

 module.exports = HelpModule


Buffer getter

Buffer getter

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -136,33 +136,32 @@
       throw new Error 'Contains query is only possible on buffered ports'
     @buffer.filter((packet) -> return true if packet.event is 'data').length

-  # Fetches a packet from the port
-  get: (scope) ->
+  getBuffer: (scope) ->
     if scope?
       return undefined unless scope of @scopedBuffer
       buf = @scopedBuffer[scope]
     else
       buf = @buffer
+    return buf
+
+  # Fetches a packet from the port
+  get: (scope) ->
+    buf = @getBuffer scope
     return if @options.control then buf[buf.length - 1] else buf.shift()

   # Returns true if port contains packet(s) matching the validator
   has: (scope, validate) ->
-    if scope?
-      return false unless scope of @scopedBuffer
-      buf = @scopedBuffer[scope]
-    else
-      return false unless @buffer.length
-      buf = @buffer
+    buf = @getBuffer scope
+    return false unless buf?.length
     for packet in buf
       return true if validate packet
     false

   # Returns the number of data packets in an inport
   length: (scope) ->
-    if scope?
-      return 0 unless scope of @scopedBuffer
-      return @scopedBuffer[scope].length
-    return @buffer.length
+    buf = @getBuffer scope
+    return 0 unless buf
+    return buf.length

   # Tells if buffer has packets or not
   ready: (scope) ->


Player ticker

Player ticker

--- public/app/game.coffee
+++ public/app/game.coffee
@@ -55,15 +55,15 @@
             @input.on "key:down[Right]", => @player.vx = 1
             @input.on "key:up[Right]", => @player.vx = 0

+            $e.Ticker.addListener (elapsed, paused) =>
+                @player.x += @player.vx
+                @player.y += @player.vy
+                @player.avatar.x = @player.x
+                @player.avatar.y = @player.y * -1

         tick: (elapsed, paused) =>
             window.elapsed = elapsed

-            # Move the player
-            @player.avatar.x = @player.x += @player.vx
-            @player.avatar.y = @player.y += @player.vy
-            @player.avatar.y *= -1
-
             # Chase the player with the camera
             @camera.x = @player.x
             @camera.y = @player.y


Fix relative path matter

Fix relative path matter

--- src/initializer.coffee
+++ src/initializer.coffee
@@ -1,12 +1,13 @@
-_         = require 'underscore'
-fs        = require 'fs'
-async     = require 'async'
-logger    = require 'loggy'
-promptly  = require 'promptly'
+_        = require 'underscore'
+fs       = require 'fs'
+async    = require 'async'
+logger   = require 'loggy'
+sysPath  = require 'path'
+promptly = require 'promptly'

 module.exports = class Initializer
   fileName     = '.pending-pr'
-  templatePath = '../templates/pending-pr.tpl'
+  templatePath = sysPath.join __dirname, '..', 'templates', 'pending-pr.tpl'

   constructor: ({@global, @filePath}) ->
     @filePath or= if @global


fixing EventEmitter interface on the JavaScript driver for #4192

over-the-shoulder by @deontologician

--- drivers/javascript/cursor.coffee
+++ drivers/javascript/cursor.coffee
@@ -259,50 +259,49 @@
             throw new err.RqlDriverError "You cannot use the cursor interface and the EventEmitter interface at the same time."

-    addListener: (args...) ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.addListener(args...)
-
-    on: (args...) ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.on(args...)
-
-
-    once: ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.once(args...)
-
-    removeListener: ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.removeListener(args...)
-
-    removeAllListeners: ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.removeAllListeners(args...)
-
-    setMaxListeners: ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.setMaxListeners(args...)
-
-    listeners: ->
-        if not @emitter?
-            @_makeEmitter()
-            setImmediate => @_each @_eachCb
-        @emitter.listeners(args...)
-
-    emit: ->
+    addListener: (event, listener) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.addListener(event, listener)
+
+    on: (event, listener) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.on(event, listener)
+
+    once: (event, listener) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.once(event, listener)
+
+    removeListener: (event, listener) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.removeListener(event, listener)
+
+    removeAllListeners: (event) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.removeAllListeners(event)
+
+    setMaxListeners: (n) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.setMaxListeners(n)
+
+    listeners: (event) ->
+        if not @emitter?
+            @_makeEmitter()
+            setImmediate => @_each @_eachCb
+        @emitter.listeners(event)
+
+    emit: (args...) ->
         if not @emitter?
             @_makeEmitter()
             setImmediate => @_each @_eachCb


Tests: Fix wrong URL to NoFlo iframe runtime

Dunno why it did not fail to connect?

--- spec/examples.coffee
+++ spec/examples.coffee
@@ -11,7 +11,7 @@
   examples = window.fbpspec_examples
   runtimeInfo =
     protocol: 'iframe'
-    address: "spec/fixtures/everything.html?fbp_noload=true&fbp_protocol=iframe"
+    address: "fixtures/everything.html?fbp_noload=true&fbp_protocol=iframe"
 else
   examples = require '../examples/bundle.json'
   runtimeInfo =


Add victory interrupt to every gate

Add victory interrupt to every gate

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -21,7 +21,7 @@
       bricks: 1
       goal: { clicks: 0, ticks: 4, bricks: 1 }
       sprites: [
-        { kind: 'gate', position: [5,0] }
+        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [5,0] }
         { kind: 'turtle', actions: {run: ['forward']} }
       ]
     }
@@ -29,7 +29,7 @@
       name: 'Create Program Bricks Using Blue Ovals'
       goal: { clicks: 1, ticks: 4, bricks: 1 }
       sprites: [
-        { kind: 'gate', position: [5,0] }
+        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [5,0] }
         { kind: 'turtle', actions: {} }
       ]
     }
@@ -37,7 +37,7 @@
       name: 'Use "Forward" and "Right" for Diagonals'
       goal: { clicks: 2, ticks: 6, bricks: 2 }
       sprites: [
-        { kind: 'gate', position: [4,4] }
+        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [4,4] }
         { kind: 'turtle', actions: {} }
       ]
     }
@@ -47,7 +47,7 @@
       bricks: 1
       sprites: [
         { kind: 'wall', position: [5,0] }
-        { kind: 'gate', position: [4,4] }
+        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [4,4] }
         { kind: 'turtle', actions: {interrupt: ['right']} }
       ]
     }
@@ -56,7 +56,7 @@
       goal: { clicks: 2, ticks: 10, bricks: 2 }
       sprites: [
         { kind: 'wall', position: [5,0] }
-        { kind: 'gate', position: [4,4] }
+        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [4,4] }
         { kind: 'turtle', actions: {interrupt: []} }
       ]
     }
@@ -68,7 +68,7 @@
         bricks: 6
       }
       sprites: [
-        {kind: 'gate'}
+        {kind: 'gate', actions: {interrupt: ['_victory']}}
         {kind: 'wall'}
         {kind: 'wall', position: [0,2]}
         {kind: 'wall', position: [2,2]}
@@ -91,7 +91,7 @@
       edit_mode: true
       goal: { clicks: 2, ticks: 10, bricks: 2 }
       sprites: [
-        { kind: 'gate' }
+        { kind: 'gate', actions: {interrupt: ['_victory']} }
         { kind: 'turtle' }
       ]
     }


stripe sync -- vastly shrink number of users by only syncing those active in the last month

--- src/smc-hub/stripe/sync.coffee
+++ src/smc-hub/stripe/sync.coffee
@@ -39,10 +39,9 @@
                 logger   : opts.logger
                 cb       : cb
         (cb) ->
-            dbg("get all customers from the database with stripe -- this is a full scan of the database and will take a while")
-            # TODO: we could make this faster by putting an index on the stripe_customer_id field.
+            dbg("get all customers from the database with stripe that have been active in the last month")
             opts.database._query
-                query : 'SELECT account_id, stripe_customer_id, stripe_customer FROM accounts WHERE stripe_customer_id IS NOT NULL'
+                query : "SELECT account_id, stripe_customer_id, stripe_customer FROM accounts WHERE stripe_customer_id IS NOT NULL AND last_active >= NOW() - INTERVAL '1 MONTH'"
                 cb    : (err, x) ->
                     users = x?.rows
                     cb(err)
@@ -67,3 +66,4 @@
             dbg("updated all customer info successfully")
         opts.cb?(err)
     )
+


Added some comments

Added some comments

--- src/typhoon/article.coffee
+++ src/typhoon/article.coffee
@@ -9,19 +9,24 @@

 app = (configs) ->
   configs ?= {}
-
+
+  # Default paging to 10
+  configs.perPage ?= 10
+
+  # Setup base url and data paths
   exports.Article.baseUrl configs.baseUrl
   exports.cache.build configs.articlesDir, configs.encoding || "utf8"
   View.templatesDir configs.templatesDir

+  # Add configs to global template vars
   View.globals
     configs: configs

+  # Preload haml templates
   articleView = new View '/article.haml', configs.encoding || "utf8"
   listView = new View '/list.haml', configs.encoding || "utf8"

-  configs.perPage ?= 10
-
+  # Setup article listing route
   return (app) ->
     app.get /^(?:(?:\/([0-9]{4})(?:\/([0-9]{2})(?:\/([0-9]{2}))?)?)?)(?:\/?page\/([0-9]+))?\/?$/, (req, res, next) ->
       if !exports.cache.ready() then throw new Error(503)
@@ -54,6 +59,7 @@
       listView.render res, locals, (err) ->
         if err then throw new Error(500)

+    # Setup article viewing route
     app.get /^(\/([0-9]{4})\/([0-9]{2})\/([0-9]{2})\/(.*)\/?)$/, (req, res, next) ->
       if !exports.cache.ready() then throw new Error(503)

@@ -66,11 +72,11 @@
       articleView.render res, locals, (err) ->
         if err then throw new Error(500)

+    # Setup RSS feed route
     app.get '/feed.xml', (req, res, next) ->
       if !exports.cache.ready() then throw new Error(503)
       articles = []
-      for entry in exports.cache.getListing 1, 20
-        articles.push exports.cache.getArticle entry.permalink
+      articles.push exports.cache.getArticle entry.permalink for entry in exports.cache.getListing 1, 20
       options =
         locals:
           articles: articles


fix tags action hidden input

fix tags action hidden input

--- app/assets/javascripts/wizard.coffee
+++ app/assets/javascripts/wizard.coffee
@@ -184,6 +184,7 @@
   '.step-tags':

     input: -> this.find("input[type='checkbox']")
+    action: -> this.find('#suggestion_tags_action')

     view: ->
       this.switchViewClass('step-view')


#326: convert csl:authority into institution

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -514,6 +514,7 @@
   'original-publisher': 'origpublisher'
   'original-publisher-place': 'origlocation'
   'original-title': 'origtitle'
+  'authority': 'institution'
 }

 Reference::complete = ->


#381: map more CSL fields

#381: map more CSL fields

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -67,7 +67,13 @@
   #'citation-label':              {}
   #'citation-number':             {}
   'collection-title':             {}
-  'container-title':              { BibLaTeX: -> (if @referencetype == 'video' then 'booktitle' else 'journaltitle') }
+  'container-title':
+    BibLaTeX: ->
+      switch @item.itemType
+        when 'film', 'tvBroadcast', 'videoRecording' then 'booktitle'
+        when 'bookSection' then 'maintitle'
+        else 'journaltitle'
+
   'container-title-short':        {}
   dimensions:                     {}
   DOI:                            {}
@@ -97,11 +103,17 @@
   section:                        {}
   source:                         {}
   status:                         {}
-  title:                          {}
+  title:                          { BibLaTeX: -> (if @referencetype == 'book' then 'maintitle' else null) }
   'title-short':                  {}
   URL:                            {}
   version:                        {}
-  'volume-title':                 {}
+  'volume-title':
+    BibLaTeX: ->
+      switch @item.itemType
+        when 'book' then 'title'
+        when 'bookSection' then 'booktitle'
+        else null
+
   'year-suffix':                  {}
   'chapter-number':               {}
   'collection-number':            {}


Fix regressed JS spec

Fix regressed JS spec

--- spec/javascripts/unit/darkswarm/services/map_spec.js.coffee
+++ spec/javascripts/unit/darkswarm/services/map_spec.js.coffee
@@ -7,6 +7,7 @@
       active: false
       orders_close_at: new Date()
       type: "hub"
+      visible: true
     }
   ]


删除menu中的：Chameleon Center

删除menu中的：Chameleon Center

--- menus/atom-chameleon.cson
+++ menus/atom-chameleon.cson
@@ -97,10 +97,6 @@
             'label': 'Build List'
             'command': 'atom-chameleon:build-list'
           }
-          {
-            'label': 'Chameleon Center',
-            'command': 'atom-chameleon:package-center'
-          }
         ]
       }
       {


8ball

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -23,29 +23,29 @@
       .get() (err, res, body) ->
         msg.send corgi for corgi in JSON.parse(body).corgis

-  ball = [
-  "It is certain",
-  "It is decidedly so",
-  "Without a doubt",
-  "Yes – definitely",
-  "You may rely on it",
-  "As I see it, yes",
-  "Most likely",
-  "Outlook good",
-  "Signs point to yes",
-  "Yes",
-  "Reply hazy, try again",
-  "Ask again later",
-  "Better not tell you now",
-  "Cannot predict now",
-  "Concentrate and ask again",
-  "Don't count on it",
-  "My reply is no",
-  "My sources say no",
-  "Outlook not so good",
-  "Very doubtful",
-]
+   ball = [
+   "It is certain",
+   "It is decidedly so",
+   "Without a doubt",
+   "Yes – definitely",
+   "You may rely on it",
+   "As I see it, yes",
+   "Most likely",
+   "Outlook good",
+   "Signs point to yes",
+   "Yes",
+   "Reply hazy, try again",
+   "Ask again later",
+   "Better not tell you now",
+   "Cannot predict now",
+   "Concentrate and ask again",
+   "Don't count on it",
+   "My reply is no",
+   "My sources say no",
+   "Outlook not so good",
+   "Very doubtful",
+   ]

-module.exports = (robot) ->
-  robot.respond /(eightball|8ball)(.*)/i, (msg) ->
-    msg.reply msg.random ball+   module.exports = (robot) ->
+   robot.respond /(eightball|8ball)(.*)/i, (msg) ->
+     msg.reply msg.random ball


cannot show history if record is empty

cannot show history if record is empty

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -35,8 +35,11 @@
       res.send "えっ？"

   robot.hear /history|\りれき/i, (res) ->
-    records = [0..length - 1].map (i) -> robot.brain.get "records.#{i}"
-    res.send "けんすう: #{length}件\n" + records.join('\n')
+    if length > 0
+      records = [0..length - 1].map (i) -> robot.brain.get "records.#{i}"
+      res.send "けんすう: #{length}件\n" + records.join('\n')
+    else
+      res.send "は？"

   robot.hear /how much|\いくら/i, (res) ->
     if length > 0


Remove a TODO

Remove a TODO

--- admin/static/coffee/namespaces/database.coffee
+++ admin/static/coffee/namespaces/database.coffee
@@ -14,7 +14,6 @@

     # Container for the entire database view
     class @Container extends Backbone.View
-        #TODO Add a class to keep alert when adding multiple databases
         className: 'database-view'
         template: Handlebars.compile $('#database_view-container-template').html()
         alert_tmpl: Handlebars.compile $('#modify_shards-alert-template').html()


Disable inspect--exposes sensitive information

--- scripts/inspect.coffee
+++ scripts/inspect.coffee
@@ -11,11 +11,11 @@

 module.exports = (robot) ->

-  robot.respond /inspect (\S+)(?:\s+(\d+))?/, (msg) ->
-    id = msg.match[1]
-    depth = msg.match[2]
-    console.log 'id = ', id, ' depth = ', depth
-    if (id is 'msg')
-      msg.send inspect msg, depth
-    else
-      msg.send inspect lookup(msg, msg.match[1]), depth
+  # robot.respond /inspect (\S+)(?:\s+(\d+))?/, (msg) ->
+  #   id = msg.match[1]
+  #   depth = msg.match[2]
+  #   console.log 'id = ', id, ' depth = ', depth
+  #   if (id is 'msg')
+  #     msg.send inspect msg, depth
+  #   else
+  #     msg.send inspect lookup(msg, msg.match[1]), depth


Short-circuit if the URL has no anchor

Short-circuit if the URL has no anchor

--- src/turbolinks/location.coffee
+++ src/turbolinks/location.coffee
@@ -10,9 +10,12 @@
     linkWithAnchor.href = url.toString()
     @absoluteURL = linkWithAnchor.href

-    linkWithoutAnchor = linkWithAnchor.cloneNode()
-    linkWithoutAnchor.hash = ""
-    @requestURL = linkWithoutAnchor.href
+    if linkWithAnchor.hash.length < 2
+      @requestURL = @absoluteURL
+    else
+      linkWithoutAnchor = linkWithAnchor.cloneNode()
+      linkWithoutAnchor.hash = ""
+      @requestURL = linkWithoutAnchor.href

   getOrigin: ->
     @absoluteURL.split("/", 3).join("/")


script loader give it more time before timeout

--- src/utils/script-loader.coffee
+++ src/utils/script-loader.coffee
@@ -12,7 +12,7 @@
       error = new Error("Error loading #{opts.src}.\nConnectivity issue?")
       logger.error(error, error.message, error.stack)
       reject(error);
-    , 10000
+    , 60000

     if opts.attributes
       _.map opts.attributes, (value, key)-> sc.setAttribute(key, value)


Another constructor

Another constructor

--- SingularityUI/app/application.coffee
+++ SingularityUI/app/application.coffee
@@ -34,7 +34,7 @@
         @views.nav.render()
         $body.prepend @views.nav.$el

-        @views.globalSearch = new GlobalSearchView
+        @views.globalSearch = new GlobalSearchView()
         @views.globalSearch.render()
         $body.append @views.globalSearch.$el


Prep Network for decaffeination

Prep Network for decaffeination

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -27,26 +27,38 @@
       callback = options
       options = {}
     super node, options, (err, process) =>
-      return callback err if err
+      if err
+        callback err
+        return
       unless options.initial
         @graph.addNode node.id, node.component, node.metadata
       callback null, process
+      return
+    return

   # Remove a process from the network. The node will also be removed
   # from the current graph.
   removeNode: (node, callback) ->
     super node, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       @graph.removeNode node.id
       callback()
+      return
+    return

   # Rename a process in the network. Renaming a process also modifies
   # the current graph.
   renameNode: (oldId, newId, callback) ->
     super oldId, newId, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       @graph.renameNode oldId, newId
       callback()
+      return
+    return

   # Add a connection to the network. The edge will also be registered
   # with the current graph.
@@ -55,18 +67,26 @@
       callback = options
       options = {}
     super edge, options, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       unless options.initial
         @graph.addEdgeIndex edge.from.node, edge.from.port, edge.from.index, edge.to.node, edge.to.port, edge.to.index, edge.metadata
       callback()
+      return
+    return

   # Remove a connection from the network. The edge will also be removed
   # from the current graph.
   removeEdge: (edge, callback) ->
     super edge, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       @graph.removeEdge edge.from.node, edge.from.port, edge.to.node, edge.to.port
       callback()
+      return
+    return

   # Add an IIP to the network. The IIP will also be registered with the
   # current graph. If the network is running, the IIP will be sent immediately.
@@ -75,17 +95,25 @@
       callback = options
       options = {}
     super iip, options, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       unless options.initial
         @graph.addInitialIndex iip.from.data, iip.to.node, iip.to.port, iip.to.index, iip.metadata
       callback()
+      return
+    return

   # Remove an IIP from the network. The IIP will also be removed from the
   # current graph.
   removeInitial: (iip, callback) ->
     super iip, (err) =>
-      return callback err if err
+      if err
+        callback err
+        return
       @graph.removeInitial iip.to.node, iip.to.port
       callback()
+      return
+    return

 exports.Network = Network


use faster u.js

use faster u.js

--- js/umobi.button.coffee
+++ js/umobi.button.coffee
@@ -14,16 +14,16 @@
     btnDownClass = "ui-btn-down-#{ umobi.config.theme }"
     btnHoverClass = "ui-btn-hover-#{ umobi.config.theme }"
     $el.hover ((e) ->
-      $(this).removeClass(btnUpClass).addClass(btnHoverClass)
+      u(this).removeClass(btnUpClass).addClass(btnHoverClass)
     ), ((e) ->
-      $(this).removeClass(btnHoverClass).addClass(btnUpClass)
+      u(this).removeClass(btnHoverClass).addClass(btnUpClass)
     )
     $el.on 'mousedown', (e) ->
-      $(this).removeClass(btnHoverClass)
+      u(this).removeClass(btnHoverClass)
         .removeClass(btnUpClass)
         .addClass(btnDownClass)
     $el.on 'mouseup', (e) ->
-      $(this).removeClass(btnDownClass)
+      u(this).removeClass(btnDownClass)
         .addClass(btnHoverClass)

   u.ready ->


Handle for ajax server error

Handle for ajax server error

--- app/assets/javascripts/ajax_hijacker.js.coffee
+++ app/assets/javascripts/ajax_hijacker.js.coffee
@@ -13,9 +13,9 @@
     e.stopPropagation()

     options = {
-      url: href,
-      success: (result) ->
-        $(target).html(result)
+      url: href
+      complete: (result) ->
+        $(target).html(result.responseText)
         $(document).trigger('ajaxAction')
     }

@@ -36,11 +36,11 @@
     data = $(this).serialize()

     options = {
-      type: method,
-      url: action,
-      data: data,
-      success: (response) ->
-        $(target).html(response)
+      type: method
+      url: action
+      data: data
+      complete: (result) ->
+        $(target).html(result.responseText)
         $(document).trigger('ajaxAction')
     }


fix for prepare format

fix for prepare format

--- src/normalizer.coffee
+++ src/normalizer.coffee
@@ -9,6 +9,11 @@
     'EM'     : 'I'
     'DEL'    : 'S'
     'STRIKE' : 'S'
+  }
+
+  ATTRIBUTES: {
+    'color': 'color'
+    'face' : 'fontFamily'
   }

   STYLES: {
@@ -47,6 +52,11 @@
     return lineNode

   normalizeNode: (node) ->
+    _.each(Normalizer.ATTRIBUTES, (style, attribute) ->
+      if node.hasAttribute(attribute)
+        node.style[style] = node.getAttribute(attribute)
+        node.removeAttribute(attribute)
+    )
     Normalizer.whitelistStyles(node)
     return Normalizer.whitelistTags(node)


Blind optimization: copy data line wise instead of pixel wise

--- src/prerenderer/prerenderer.coffee
+++ src/prerenderer/prerenderer.coffee
@@ -11,10 +11,9 @@
       .pipe new PNG filterType: 4
       .on 'parsed', ->
         for y in [0..31]
-          for x in [0..63]
-            src_idx = @width*y + x << 2
-            dst_idx = 64*y + x << 2
-            @data.copy section.data, dst_idx, src_idx, src_idx+4
+          src_idx = @width*y << 2
+          dst_idx = section.width*y << 2
+          @data.copy section.data, dst_idx, src_idx, src_idx+(section.width << 2)

         section.pack().pipe(response)


Fix clicking on help text label checking all the checkboxes

--- static/coffee/temba.coffee
+++ static/coffee/temba.coffee
@@ -99,6 +99,11 @@

   ele = $(".font-checkbox")

+  helpText = ele.children('.controls').children('.help-block').children('label')
+  helpText.on 'click', (event) ->
+    $(this).parent().parent('.field-input').children('.glyph.notif-checkbox').click()
+    event.preventDefault();
+
   glyphCheck = ele.children('.controls').children('.glyph.notif-checkbox')
   glyphCheck.on 'click', ->
     cell = $(this).parent('.field-input')


Fix temperature

Fix temperature

--- scripts/tonypai.coffee
+++ scripts/tonypai.coffee
@@ -30,7 +30,7 @@
               mt = pad.substring(0, pad.length - mt.length) + mt
               dt = pad.substring(0, pad.length - dt.length) + dt
               # result
-              weather += yr + '-' + mt + '-' + dt + ' ' + val.weather[0].main + '(' + val.weather[0].description + ') ' + val.deg / 10 + '°C\n'
+              weather += yr + '-' + mt + '-' + dt + ' ' + val.weather[0].main + '(' + val.weather[0].description + ') ' + val.temp.day / 10 + '°C\n'

             result.send weather
           else


limit return results to top result

limit return results to top result

--- scripts/youtube.coffee
+++ scripts/youtube.coffee
@@ -9,7 +9,7 @@
     robot.http("http://gdata.youtube.com/feeds/api/videos")
       .query({
         orderBy: "relevance"
-        'max-results': 15
+        'max-results': 1
         alt: 'json'
         q: query
       })
@@ -21,7 +21,7 @@
           msg.send "No video results for \"#{query}\""
           return

-        video  = msg.random videos
+        video  = videos[0]
         video.link.forEach (link) ->
           if link.rel is "alternate" and link.type is "text/html"
             msg.send link.href


Fixes to statistics persistence.

Fixes to statistics persistence.

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -402,29 +402,46 @@
         err
       ).callback callback

-  # Reload statistic data when crating a new statistic with a formula
-  wrapSave sdk, 'saveStatistic', (statistic) ->
-    teamId = statistic.teamId
-    bulkLoadTypes = ['memberStatistic', 'teamStatistic', 'statisticAggregate']
-    sdk.bulkLoad(teamId, bulkLoadTypes)
-
-  , (statistic) ->
-    sdk.loadEventStatistics statisticId: statistic.id
+  # Reload statistic data when updating a statistic
+  wrapMethod sdk, 'saveStatistic', (saveStatistic) ->
+    (statistic, callback) ->
+      saveStatistic.call(this, statistic, callback).then((result) ->
+        teamId = statistic.teamId
+        statisticId = result.id
+        bulkLoadTypes =
+          ['memberStatistic', 'teamStatistic', 'statisticAggregate']
+        promises.when(
+          sdk.bulkLoad(teamId, bulkLoadTypes)
+          sdk.loadEventStatistics statisticId: statisticId
+        ).then -> result
+      ).callback callback

   # Update member statistics when saving statisticData
-  wrapSave sdk, 'bulkSaveStatisticData', (templates) ->
-    if templates[0]? and templates[0].memberId?
-      teamId = templates[0].teamId
-      statisticId = templates[0].statisticId
-      bulkLoadTypes = ['memberStatistic', 'statisticAggregate']
-      sdk.bulkLoad(teamId, bulkLoadTypes)
-      sdk.loadEventStatistics statisticId: statisticId
-
-  wrapSave sdk, 'saveStatisticDatum', (statisticDatum) ->
-    teamId = statisticDatum.teamId
-    bulkLoadTypes = ['memberStatistic', 'statisticAggregate']
-    sdk.bulkLoad(teamId, bulkLoadTypes)
-    sdk.loadEventStatistics statisticId: statisticDatum.statisticId
+  wrapMethod sdk, 'bulkSaveStatisticData', (bulkSaveStatisticData) ->
+    (templates, callback) ->
+      bulkSaveStatisticData.call(this, templates, callback).then((result) ->
+        if result[0]? and result[0].teamId?
+          teamId = result[0].teamId
+          statisticId = result[0].statisticId
+          bulkLoadTypes = ['memberStatistic', 'statisticAggregate']
+          promises.when(
+            sdk.bulkLoad(teamId, bulkLoadTypes)
+            sdk.loadEventStatistics statisticId: statisticId
+          ).then -> result
+      ).callback callback
+
+  wrapMethod sdk, 'saveStatisticDatum', (saveStatisticDatum) ->
+    (statisticDatum, callback) ->
+      saveStatisticDatum.call(this, statisticDatum, callback)
+      .then((result) ->
+        teamId = result.teamId
+        statisticId = result.statisticId
+        bulkLoadTypes = ['memberStatistic', 'statisticAggregate']
+        promises.when(
+          sdk.bulkLoad(teamId, bulkLoadTypes)
+          sdk.loadEventStatistics statisticId: statisticId
+        ).then -> result
+      ).callback callback

   # Remove deleted member statisticData when using bulk delete command
   wrapMethod sdk, 'bulkDeleteStatisticData', (bulkDeleteStatisticData) ->
@@ -439,9 +456,10 @@
       bulkDeleteStatisticData.call(this, member, event).then((result) ->
         promises.when(
           teamId = member.teamId
+          eventId = event.id
           bulkLoadTypes = ['memberStatistic', 'statisticAggregate']
           sdk.bulkLoad(teamId, bulkLoadTypes)
-          sdk.loadEventStatistics eventId: event.id
+          sdk.loadEventStatistics eventId: eventId
         ).then -> result
       ).fail((err) ->
         linking.linkItems toRemove, lookup


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/oscillator.coffee
+++ src/oscillator.coffee
@@ -1,3 +1,13 @@
+###*
+The Sprite class provides a way to load images for use in games.
+
+By default, images are loaded asynchronously. A proxy object is
+returned immediately. Even though it has a draw method it will not
+draw anything to the screen until the image has been loaded.
+
+@name Sprite
+@constructor
+###
 Oscillator = (options={}) ->
   {amplitude, period, offset} = options


Update admin dashboard

Update admin dashboard

--- app/views/admin/AnalyticsView.coffee
+++ app/views/admin/AnalyticsView.coffee
@@ -171,6 +171,7 @@
         groupMap = {}
         dayGroupCountMap = {}
         for payment in data
+          continue unless payment.service in ['paypal', 'stripe']
           if !payment.created
             day = utils.objectIdToDate(payment._id).toISOString().substring(0, 10)
           else
@@ -189,12 +190,39 @@
         @revenueGroups = Object.keys(groupMap)
         @revenueGroups.push 'DRR Total'

+        # Split lifetime values across 8 months based on 12% monthly churn
+        lifetimeDurationMonths = 8 # Needs to be an integer
+        daysPerMonth = 30 #Close enough (needs to be an integer)
+        lifetimeDaySplit = lifetimeDurationMonths * daysPerMonth
+
         # Build list of recurring revenue entries, where each entry is a day of individual group values
         @revenue = []
+        serviceCarryForwardMap = {}
         for day of dayGroupCountMap
           data = {day, groups: []}
           for group in @revenueGroups
-            data.groups.push(dayGroupCountMap[day][group] ? 0)
+            if group in ['DRR intl lifetime', 'DRR usa lifetime']
+              serviceCarryForwardMap[group] ?= []
+              if dayGroupCountMap[day][group]
+                serviceCarryForwardMap[group].push({remaining: lifetimeDaySplit, value: (dayGroupCountMap[day][group] ? 0) / lifetimeDurationMonths})
+              data.groups.push(0)
+            else if group is 'DRR Total'
+              # Add total, minus deferred lifetime values for this day
+              data.groups.push((dayGroupCountMap[day][group] ? 0) - (dayGroupCountMap[day]['DRR intl lifetime'] ? 0) - (dayGroupCountMap[day]['DRR usa lifetime'] ? 0))
+            else
+              data.groups.push(dayGroupCountMap[day][group] ? 0)
+
+          # Add previous lifetime sub contributions
+          for group of serviceCarryForwardMap
+            for carryData in serviceCarryForwardMap[group]
+              # Add deferred lifetime value every 30 days
+              # Deferred value = (lifetime purchase value) / lifetimeDurationMonths
+              if carryData.remaining > 0 and carryData.remaining % 30 is 0
+                data.groups[@revenueGroups.indexOf(group)] += carryData.value
+                data.groups[@revenueGroups.indexOf('DRR Total')] += carryData.value
+              if carryData.remaining > 0
+                carryData.remaining--
+
           @revenue.push data

         # Order present to past
@@ -233,7 +261,8 @@
               @monthMrrMap[month].monthly += revenue.groups[i]
             else if group in ['DRR usa lifetime', 'DRR intl lifetime']
               @monthMrrMap[month].yearly += revenue.groups[i]
-            @monthMrrMap[month].total += revenue.groups['DRR Total']
+            else if group is 'DRR Total'
+              @monthMrrMap[month].total += revenue.groups[i]

         @updateAllKPIChartData()
         @updateRevenueChartData()


Fix record addition testing

Fix record addition testing

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -929,7 +929,7 @@
         for ri, r of slot.volume.records
           rs[ri] = r
         for sr in records
-          if sr.record.id of rs && sr.segment.overlaps(seg)
+          if sr.record.id of rs && sr.overlaps(seg)
             delete rs[sr.record.id]
         $scope.addRecord.records = rs
         rc = {}


Fix slot updateRange in editing mode

Fix slot updateRange in editing mode

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -48,8 +48,8 @@
       l = Infinity
       u = -Infinity
       for t in $scope.tracks.concat(records, $scope.consents)
-        l = t.segment.l if isFinite(t.segment.l) && t.segment.l < l
-        u = t.segment.u if isFinite(t.segment.u) && t.segment.u > u
+        l = t.segment.l if isFinite(t.segment?.l) && t.segment.l < l
+        u = t.segment.u if isFinite(t.segment?.u) && t.segment.u > u
       fullRange.l = finite(slot.segment.l, l, 0)
       fullRange.u = finite(slot.segment.u, u, 0)
       return