add spec for Bacon.spy

add spec for Bacon.spy

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -2675,6 +2675,21 @@
   it "toString", ->
     expect(Bacon.fromBinder(->).toString()).to.equal("Bacon.fromBinder(function,function)")

+describe "Bacon.spy", ->
+  testSpy = (expectedCount, f) ->
+    calls = 0
+    spy = (obs) -> calls++
+    Bacon.spy spy
+    f()
+    expect(calls).to.equal(expectedCount)
+  describe "calls spy function for all created Observables", ->
+    it "EventStream", ->
+      testSpy 1, -> Bacon.once(1)
+    it "Property", ->
+      testSpy 1, -> Bacon.constant(1)
+    it "map", ->
+      testSpy 2, -> Bacon.once(1).map(->)
+
 describe "Infinite synchronous sequences", ->
   describe "Limiting length with take(n)", ->
     expectStreamEvents(


add more spec for scan()

add more spec for scan()

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -439,6 +439,11 @@
     expectPropertyEvents(
       -> repeat(10, [[1], [2]]).take(2).scan([], ".concat")
       [[], [1], [1, 2]])
+  it "yields the seed value immediately", ->
+    outputs = []
+    bus = new Bacon.Bus()
+    bus.scan(0, -> 1).onValue((value) -> outputs.push(value))
+    expect(outputs).toEqual([0])

 describe "combineTemplate", ->
   it "combines streams according to a template object", ->


Change most instances of 'if \!' to 'unless' for consistency.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -199,7 +199,7 @@
   withDescription(Bacon, "zipAsArray", streams..., Bacon.zipWith(streams, (xs...) -> xs))

 Bacon.zipWith = (f, streams...) ->
-  if !isFunction(f)
+  unless isFunction(f)
     [streams, f] = [f, streams[0]]
   streams = _.map(((s) -> s.toEventStream()), streams)
   withDescription(Bacon, "zipWith", f, streams..., Bacon.when(streams, f))
@@ -378,7 +378,7 @@
           Bacon.noMore)

   endOnError: (f, args...) ->
-    f = true if !f?
+    f = true unless f?
     convertArgsToFunction this, f, args, (f) ->
       withDescription(this, "endOnError", @withHandler (event) ->
         if event.isError() and f(event.error)
@@ -390,7 +390,7 @@
   take: (count) ->
     return Bacon.never() if count <= 0
     withDescription(this, "take", count, @withHandler (event) ->
-      if !event.hasValue()
+      unless event.hasValue()
         @push event
       else
         count--
@@ -435,7 +435,7 @@

   skip: (count) ->
     withDescription(this, "skip", count, @withHandler (event) ->
-      if !event.hasValue()
+      unless event.hasValue()
         @push event
       else if (count > 0)
         count--
@@ -446,7 +446,7 @@
   skipDuplicates: (isEqual = (a, b) -> a is b) ->
     withDescription(this, "skipDuplicates",
       @withStateMachine None, (prev, event) ->
-        if !event.hasValue()
+        unless event.hasValue()
           [prev, [event]]
         else if event.isInitial() or prev == None or not isEqual(prev.get(), event.value())
           [new Some(event.value()), [event]]
@@ -482,7 +482,7 @@
       unsub = nop
       reply = Bacon.more
       sendInit = ->
-        if !initSent
+        unless initSent
           acc.forEach (valueF) ->
             initSent = true
             reply = sink(new Initial(valueF))
@@ -760,7 +760,7 @@
     endMarker = {}
     withDescription(this, "takeUntil", stopper, Bacon.groupSimultaneous(@mapEnd(endMarker), stopper.skipErrors())
       .withHandler((event) ->
-        if !event.hasValue()
+        unless event.hasValue()
           @push event
         else
           [data, stopper] = event.value()
@@ -798,7 +798,7 @@
     withDescription(this, "holdWhen", valve,
       # the filter(false) thing is added just to keep the subscription active all the time (improves stability with some streams)
       @filter(false).merge valve_.flatMapConcat (shouldHold) =>
-        if !shouldHold
+        unless shouldHold
           @takeUntil(putToHold)
         else
           @scan([], ((xs,x) -> xs.concat(x)), {eager:true}).sampledBy(releaseHold).take(1).flatMap(Bacon.fromArray))
@@ -1164,7 +1164,7 @@
     triggerFound = false
     for s in patSources
       index = _.indexOf(sources, s)
-      if !triggerFound
+      unless triggerFound
         triggerFound = Source.isTrigger(s)
       if index < 0
         sources.push(s)
@@ -1176,7 +1176,7 @@
     pats.push pat if patSources.length > 0
     i = i + 2

-  if !sources.length
+  unless sources.length
     return Bacon.never()

   sources = _.map Source.fromObservable, sources
@@ -1187,14 +1187,14 @@
     ends = false
     match = (p) ->
       for i in p.ixs
-        if !sources[i.index].hasAtLeast(i.count)
+        unless sources[i.index].hasAtLeast(i.count)
           return false
       return true
     cannotSync = (source) ->
       !source.sync or source.ended
     cannotMatch = (p) ->
       for i in p.ixs
-        if !sources[i.index].mayHave(i.count)
+        unless sources[i.index].mayHave(i.count)
           return true
     nonFlattened = (trigger) -> !trigger.source.flatten
     part = (source) -> (unsubAll) ->
@@ -1346,7 +1346,7 @@

   dependsOn = (orig, o) ->
     myDeps = flatDeps[orig.id]
-    if !myDeps
+    unless myDeps
       myDeps = flatDeps[orig.id] = {}
       collectDeps orig, orig
     myDeps[o.id]
@@ -1585,7 +1585,7 @@
   toString: (obj) ->
     try
       recursionDepth++
-      if !obj?
+      unless obj?
         "undefined"
       else if isFunction(obj)
         "function"


Ensure Vomnibar selection is always within bounds.

Closes #670.

--- content_scripts/vomnibar.coffee
+++ content_scripts/vomnibar.coffee
@@ -24,7 +24,7 @@
       @vomnibarUI.update()

   activate: -> @activateWithCompleter("omni", 100)
-  activateInNewTab: -> @activateWithCompleter("omni", 100, null, true, true)
+  activateInNewTab: -> @activateWithCompleter("omni", 100, null, false, true)
   activateTabSelection: -> @activateWithCompleter("tabs", 0, null, true)
   activateBookmarks: -> @activateWithCompleter("bookmarks", 0, null, true)
   activateBookmarksInNewTab: -> @activateWithCompleter("bookmarks", 0, null, true, true)
@@ -68,8 +68,7 @@
     @update(true)

   updateSelection: ->
-    if (@completions.length > 0)
-      @selection = Math.min(@selection, @completions.length - 1)
+    @selection = Math.min(@selection, @completions.length - 1)
     for i in [0...@completionList.children.length]
       @completionList.children[i].className = (if i == @selection then "vomnibarSelected" else "")


stub out page

stub out page

--- spec/spec.coffee
+++ spec/spec.coffee
@@ -6,6 +6,9 @@
 afterEach ->
   fixtureDiv.empty()

+# disable page
+page = (->)
+
 describe 'Dependencies', ->
   it 'has loDash', ->
     expect(typeof _).to.not.eq 'undefined'


Remove test

Remove test

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -1000,18 +1000,6 @@

           expect($(tabBar2.tabForItem(pane2.getActiveItem())).find('.title')).not.toHaveClass 'temp'

-    describe "when a non-text file is opened", ->
-      it "opens a permanent tab", ->
-        imageView = null
-        waitsForPromise ->
-          atom.workspace.open('sample.png').then (o) ->
-            imageView = o
-            pane.activateItem(imageView)
-
-        runs ->
-          expect(tabBar.tabForItem(imageView)).toExist()
-          expect($(tabBar.tabForItem(imageView)).find('.title')).not.toHaveClass 'temp'
-
     describe "when double clicking a file in the tree view", ->
       it "makes the tab for that file permanent", ->
         editor1 = null


:lipstick:

:lipstick:

--- lib/git-view.coffee
+++ lib/git-view.coffee
@@ -66,8 +66,8 @@
         @update()

   destroy: ->
-    @activeItemSubscription.dispose()
-    @projectPathSubscription.off()
+    @activeItemSubscription?.dispose()
+    @projectPathSubscription?.off() # Still an old subscription, not a disposable

     @savedSubscription?.dispose()
     @statusChangedSubscription?.dispose()


spec for drag leave resize behavior

spec for drag leave resize behavior

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -124,6 +124,16 @@
       expect(tabBar.tabAtIndex(0).style.maxWidth).toBe ''
       expect(tabBar.tabAtIndex(1).style.maxWidth).toBe ''

+  describe "when a drag leave event moves the mouse from the tab bar", ->
+    it "resets the width on every tab", ->
+      jasmine.attachToDOM(tabBar)
+
+      triggerMouseEvent('mouseenter', tabBar)
+      triggerMouseEvent('dragleave', tabBar)
+
+      expect(tabBar.tabAtIndex(0).style.maxWidth).toBe ''
+      expect(tabBar.tabAtIndex(1).style.maxWidth).toBe ''
+
   describe ".initialize(pane)", ->
     it "creates a tab for each item on the tab bar's parent pane", ->
       expect(pane.getItems().length).toBe 3


Fixed bug causing form doc to be an empty object when no doc existed.


:bug: Fix an invalid spec

:bug: Fix an invalid spec

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -48,7 +48,7 @@
       expect(gotError).toBe(true)
     it "works", ->
       regex = 'type:(?<type>.+) message:(?<message>.+)'
-      input = ['type:type message:message']
+      input = 'type:type message:message'
       output = [(
         type: 'type'
         text: 'message'


:art: Use template strings in specs

:art: Use template strings in specs

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -10,38 +10,38 @@
       expect( -> helpers.execNode()).toThrow()
     it 'works', ->
       waitsForPromise ->
-        helpers.execNode(__dirname + '/fixtures/something.js').then (data) ->
+        helpers.execNode("#{__dirname}/fixtures/something.js").then (data) ->
           expect(data).toBe('STDOUT')
       waitsForPromise ->
         helpers.exec('cat', [testFile]).then (text) ->
           expect(text).toBe(testContents)
     it 'lets you choose streams', ->
       waitsForPromise ->
-        helpers.execNode(__dirname + '/fixtures/stderr.js', [], {stream: 'stderr'}).then (data) ->
+        helpers.execNode("#{__dirname}/fixtures/stderr.js", [], {stream: 'stderr'}).then (data) ->
           expect(data).toBe('STDERR')
       waitsForPromise ->
         helpers.exec('cat', [testFile], stream: 'stderr').then (text) ->
           expect(text).toBe('')
     it 'accepts stdin', ->
       waitsForPromise ->
-        helpers.execNode(__dirname + '/fixtures/something.js', ['input'], {stream: 'stdout', stdin: 'Wow'}).then (data) ->
+        helpers.execNode("#{__dirname}/fixtures/something.js", ['input'], {stream: 'stdout', stdin: 'Wow'}).then (data) ->
           expect(data).toBe('STDOUTWow')
       waitsForPromise ->
         helpers.exec('cat', [], stream: 'stdout', stdin: testContents).then (text) ->
           expect(text).toBe(testContents)
     it "throws if stderr is written to but wasn't expected", ->
       waitsForPromise ->
-        helpers.execNode(__dirname + '/fixtures/stderr.js', []).catch (message) ->
+        helpers.execNode("#{__dirname}/fixtures/stderr.js", []).catch (message) ->
           expect(message).toBe('STDERR')
       waitsForPromise ->
-        helpers.exec(__dirname + '/fixtures/stderr.sh', []).catch (message) ->
+        helpers.exec("#{__dirname}/fixtures/stderr.sh", []).catch (message) ->
           expect(message).toBe("STDERR\n")

     describe 'throwOnStdErr option', ->
       it 'throws unexpected error when set to true', ->
         gotError = false
         waitsForPromise ->
-          helpers.exec(__dirname + '/fixtures/stderr.sh', [], throwOnStdErr: true).catch( (message) ->
+          helpers.exec("#{__dirname}/fixtures/stderr.sh", [], throwOnStdErr: true).catch( (message) ->
             gotError = true
             expect(message).toBe("STDERR\n")
           ).then ->
@@ -49,7 +49,7 @@
       it 'suppresses unexpected errors when set to false', ->
         gotError = false
         waitsForPromise ->
-          helpers.exec(__dirname + '/fixtures/stderr.sh', [], throwOnStdErr: false).catch(->
+          helpers.exec("#{__dirname}/fixtures/stderr.sh", [], throwOnStdErr: false).catch(->
             gotError = true
           ).then ->
             expect(gotError).toBe(false)
@@ -81,4 +81,4 @@
         helpers.findFile()
       .toThrow()
     it 'works', ->
-      expect(helpers.findFile(__dirname, 'package.json')).toBe(fs.realpathSync(__dirname + '/../package.json'))
+      expect(helpers.findFile(__dirname, 'package.json')).toBe(fs.realpathSync("#{__dirname}/../package.json"))


Ignore setUpRoutes() if Routes not configured

--- src/AccountsUi.coffee
+++ src/AccountsUi.coffee
@@ -61,6 +61,7 @@
             {fields: {profile: 1, emails: 1, roles: 1, username: 1, enabled: 1}}

       setUpRoutes: ->
+        return unless Routes?.isConfigured()
         config = @config()
         createRoute 'login', config.login
         if config.forgot.enabled == true


Revert "Merge pull request #843 from sharelatex/ew-collabratec"

This reverts commit 223beab491d2f8dcf4c7285267fda355cfce3f05, reversing
changes made to a726537c4372641bfca0eb37fc130ca3a932d433.

--- services/web/test/acceptance/coffee/helpers/User.coffee
+++ services/web/test/acceptance/coffee/helpers/User.coffee
@@ -80,9 +80,6 @@
 			update["features.#{key}"] = value
 		UserModel.update { _id: @id }, update, callback

-	setOverleafId: (overleaf_id, callback = (error) ->) ->
-		UserModel.update { _id: @id }, { 'overleaf.id': overleaf_id }, callback
-
 	logout: (callback = (error) ->) ->
 		@getCsrfToken (error) =>
 			return callback(error) if error?


Scope Fix.

Scope Fix.

--- snippets/php.cson
+++ snippets/php.cson
@@ -1,4 +1,4 @@
-'text.html.php.blade, text.html.php':
+'.text.html.php.blade, .text.html.php':

   'Construct definition':
     'prefix': 'php:construct'


Use composite in flatMap

Use composite in flatMap

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -453,53 +453,33 @@
     f = makeSpawner(f)
     root = this
     new EventStream (sink) ->
-      children = []
-      rootEnd = false
-      unbound = false
-      unsubRoot = ->
-      unbind = ->
-        unsubRoot()
-        for unsubChild in children
-          unsubChild()
-        children = []
-        unbound = true
-      checkEnd = ->
-        if rootEnd and (children.length == 0)
-          sink end()
-      spawner = (event) ->
+      composite = new CompositeUnsubscribe()
+      checkEnd = (unsub) ->
+        unsub()
+        sink end() if composite.empty()
+      composite.add (__, unsubRoot) -> root.subscribe (event) ->
         if event.isEnd()
-          rootEnd = true
-          checkEnd()
+          checkEnd(unsubRoot)
         else if event.isError()
           sink event
-        else if firstOnly and children.length
+        else if firstOnly and composite.count() > 1
           Bacon.more
         else
+          return Bacon.noMore if composite.unsubscribed
           child = f event.value()
           child = Bacon.once(child) if not (child instanceof Observable)
-          unsubChild = undefined
-          childEnded = false
-          removeChild = ->
-            _.remove(unsubChild, children) if unsubChild?
-            checkEnd()
-          handler = (event) ->
+          composite.add (unsubAll, unsubMe) -> child.subscribe (event) ->
             if event.isEnd()
-              removeChild()
-              childEnded = true
+              checkEnd(unsubMe)
               Bacon.noMore
             else
               if event instanceof Initial
                 # To support Property as the spawned stream
                 event = event.toNext()
               reply = sink event
-              if reply == Bacon.noMore
-                unbind()
+              unsubAll() if reply == Bacon.noMore
               reply
-          unsubChild = child.subscribe handler
-          children.push unsubChild if not childEnded
-          if unbound then Bacon.noMore else Bacon.more
-      unsubRoot = root.subscribe(spawner)
-      unbind
+      composite.unsubscribe

   flatMapFirst: (f) -> @flatMap(f, true)


Testpie testing

Testpie testing

--- widgets/testpie/testpie.coffee
+++ widgets/testpie/testpie.coffee
@@ -73,7 +73,7 @@
         .text((d,i) ->
           if data[i].value != 0
             data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)'
-		)
+        )

 # mod
 #	arcs.append("svg:text").attr("transform", (d, i) ->


Testpie testing

Testpie testing

--- widgets/testpie/testpie.coffee
+++ widgets/testpie/testpie.coffee
@@ -68,7 +68,7 @@
         ).attr('font-size', '28px')
         .append('svg:tspan')
         .attr('x', 0)
-        .attr('dy', .9em)
+        .attr('dy', '.9em')
         .attr('font-size', '80%')
         .text((d,i) ->
           if data[i].value != 0


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -218,4 +218,11 @@
       callback = callbacks.add('after', options, this, block)
     state: ->
       return internal_state
-
+
+State = (name) ->
+  I = {}
+
+  I.name = name;
+
+  return I
+


chang aws ip*

chang aws ip*

--- lib/notifier.coffee
+++ lib/notifier.coffee
@@ -18,7 +18,7 @@

   activate: ->
     try
-      @socket = io.connect('http://35.154.96.42:9000', reconnect: true)
+      @socket = io.connect('http://35.154.189.94:9000', reconnect: true)

       @socket.on 'connect', =>
         @socket.emit 'room', @authToken


Now will use native Atom Notification messages instead of Desktop Notification to show Lesson Forked, Test & Submit status to the User

--- lib/notifier.coffee
+++ lib/notifier.coffee
@@ -7,6 +7,7 @@
 io = require 'socket.io-client'
 remote = require 'remote'
 localStorage = require './local-storage'
+{Notification} = require 'atom'
 BrowserWindow = remote.BrowserWindow

 module.exports =
@@ -37,46 +38,28 @@
             # console.log rawData
             if rawData.type == 'notify_ide'
               if rawData.message.type == 'testCasesPassed'
-                notif = new Notification rawData.title,
-                  body: 'Test cases passed successfully'
-
-                notif.onclick = ->
-                  notif.close()
+                atom.notifications.addSuccess(rawData.title, {
+                  description: 'Perfect! You have passed the test cases!',
+                  dismissable: false
+                });

               if rawData.message.type == 'testCasesFailed'
-                notif = new Notification rawData.title,
-                  body: 'Test cases failed'
-
-                notif.onclick = ->
-                  notif.close()
-
-              if rawData.message.type == 'completedReading'
-                notif = new Notification rawData.title,
-                  body: if rawData.message.value == 'true' then 'Reading completed successfully' else 'Reading not completed'
-
-                notif.onclick = ->
-                  notif.close()
+                atom.notifications.addError(rawData.title, {
+                  description: 'Uh-oh! You still have to pass the test cases!',
+                  dismissable: false
+                });

               if rawData.message.type == 'forked'
-                notif = new Notification rawData.title,
-                  body: if rawData.message.value == 'true' then  'Forked successfully' else 'Forked failed'
-
-                notif.onclick = ->
-                  notif.close()
+                atom.notifications.addSuccess(rawData.title, {
+                  description: 'Bingo! You have forked the project successfully!',
+                  dismissable: false
+                });

               if rawData.message.type == 'submittedPr'
-                notif = new Notification rawData.title,
-                  body: if rawData.message.value == 'true' then 'Pull request submitted successfully' else 'Pull request failed'
-
-                notif.onclick = ->
-                  notif.close()
-
-              if rawData.message.type == 'reviewed'
-                notif = new Notification rawData.title,
-                  body: if rawData.message.value == 'true' then 'Review completed successfully' else 'Review failed'
-
-                notif.onclick = ->
-                  notif.close()
+                atom.notifications.addSuccess(rawData.title, {
+                  description: 'Good work! You have submitted successfully!',
+                  dismissable: false
+                });

             if rawData.type == 'pop_image'
               win = new BrowserWindow(


Now searches step_definitions directory first and inserts cursor for params

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -4,6 +4,7 @@
 PROPERTY_PREFIX_PATTERN = /(?:^|\[|\(|,|=|:|\s)\s*((?:And|Given|Then|When)\s(?:[a-zA-Z]+\.?){0,2})$/
 PATH_CONFIG_KEY = 'cucumber-autocomplete.path'
 CUCUMBER_KEYWORDS_PATTERN = /(Given|And|When|Then)(.*)/g
+CUCUMBER_STEP_DEF_PATTERN = /(Given|And|When|Then)\(\/\^(.*?)\$/g

 module.exports =
   selector: '.source.feature, .feature'
@@ -19,17 +20,23 @@

   getCompletions: (line, file) ->
     return [] unless @matchCucumberKeyword(line)
+    results = []

-    results = []
-    regex = /(Given|And|When|Then)(.*)/g
-    while (myRegexArray = regex.exec(file)) != null
-      results.push({"text":myRegexArray[2].replace /^\s+|\s+$/g, ""})
+    stats = fs.lstatSync("#{@rootDirectory()}#{@featuresDirectory()}/step_definitions");
+    if stats.isDirectory()
+      #TODO: first search step definitions for your file
+      for step_def_file in fs.readdirSync("#{@rootDirectory()}#{@featuresDirectory()}/step_definitions")
+        data = fs.readFileSync "#{@rootDirectory()}#{@featuresDirectory()}/step_definitions/#{step_def_file}", 'utf8'
+        while (myRegexArray = CUCUMBER_STEP_DEF_PATTERN.exec(data)) != null
+          results.push({"snippet":@replacedCucumberRegex(myRegexArray[2])})
+    else
+      @featureDirError() unless fs.lstatSync("#{@rootDirectory()}#{@featuresDirectory()}").isDirectory()

-    for feature in fs.readdirSync("#{@rootDirectory()}#{@featuresDirectory()}")
-      continue unless /.feature/.test(feature)
-      data = fs.readFileSync "#{@rootDirectory()}#{@featuresDirectory()}/#{feature}", 'utf8'
-      while (myRegexArray = CUCUMBER_KEYWORDS_PATTERN.exec(data)) != null
-        results.push({"text":myRegexArray[2].replace /^\s+|\s+$/g, ""})
+      for feature in fs.readdirSync("#{@rootDirectory()}#{@featuresDirectory()}")
+        continue unless /.feature/.test(feature)
+        data = fs.readFileSync "#{@rootDirectory()}#{@featuresDirectory()}/#{feature}", 'utf8'
+        while (myRegexArray = CUCUMBER_KEYWORDS_PATTERN.exec(data)) != null
+          results.push({"text":myRegexArray[2].replace /^\s+|\s+$/g, ""})

     return results

@@ -41,3 +48,13 @@

   featuresDirectory: (path=PATH_CONFIG_KEY) ->
     atom.config.get(path)
+
+  replacedCucumberRegex: (step) ->
+    #TODO: figure out how to loop through if there are multiple matches
+    #      eg: 1:numberArgument, 2:numberArgument
+    step = step.replace(/^\s+|\s+$/g, "")
+    step = step.replace(/\(\\d\+\)/g, "${1:numberArgument}")
+    step.replace(/\(\.\*\?\)/g, "${1:textArgument}")
+
+  featureDirError: ->
+    throw new Error("Cannot find features directory at #{@rootDirectory()}#{@featuresDirectory()}")


Destroy cursor line decoration layers in newer versions of the editor

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -116,11 +116,16 @@
     preElement.remove()

     editor = editorElement.getModel()
-    # remove the default selection of a line in each editor
-    editor.getDecorations(class: 'cursor-line', type: 'line')[0].destroy()
     editor.setText(codeBlock.textContent)
     if grammar = atom.grammars.grammarForScopeName(scopeForFenceName(fenceName))
       editor.setGrammar(grammar)
+
+    # Remove line decorations from code blocks.
+    if editor.cursorLineDecorations?
+      for cursorLineDecoration in editor.cursorLineDecorations
+        cursorLineDecoration.destroy()
+    else
+      editor.getDecorations(class: 'cursor-line', type: 'line')[0].destroy()

   domFragment


Guard against top stack frame’s fileName being null in shouldReport

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -62,7 +62,7 @@

   if topFrame = parseStackTrace(error)[0]
     # only report exceptions that originate from the application bundle
-    topFrame.getFileName().indexOf(atom.getLoadSettings().resourcePath) is 0
+    topFrame.getFileName()?.indexOf(atom.getLoadSettings().resourcePath) is 0
   else
     false


refactor

refactor

--- src/build.coffee
+++ src/build.coffee
@@ -129,10 +129,12 @@
     if not callback and typeof settings is "function"
       callback = settings
       settings = {}
-    settings = settings or {}
+
+    settings = settings ? {}
     indexFileDir = resolveFileDir(indexFile)
-    encoding = settings.encoding or "utf8"
-    assetRoot = path.resolve(settings.assetRoot or indexFileDir)
+    encoding = settings.encoding ? "utf8"
+    assetRoot = path.resolve(settings.assetRoot ? indexFileDir)
+
     globalFlags =
       concat: settings.concat
       inline: settings.inline
@@ -145,68 +147,48 @@
     extendedFetchers = hooks.fileFetcher.concat([ defaultFetcher ])
     extendedDataHooks = hooks.data.concat([ defaultSomething ])
     extendedCompilers = hooks.compiler.concat([ defaultCompiler() ])
+
     getData = (file, opraBlock, callback) ->
-      anyTrue = hooks.preventContent.some((hook) ->
-        hook file, opraBlock
-      )
-      unless anyTrue
-        callback null,
-          file: file
-          content: `undefined`
+      anyTrue = hooks.preventContent.some (hook) -> hook(file, opraBlock)
+      return callback(null, { file, content: undefined }) unless anyTrue

-        return
-      helpers.firstNonNullSeries extendedCompilers, ((compiler, callback) ->
-        if _.isUndefined(compiler.from) or _.endsWith(file.name, "." + compiler.from)
-          compiler.compile file.absolutePath, file.encoding, assetRoot, callback
+      helpers.firstNonNullSeries extendedCompilers, (compiler, callback) ->
+        if !compiler.from? or _.endsWith(file.name, "." + compiler.from)
+          compiler.compile(file.absolutePath, file.encoding, assetRoot, callback)
         else
           callback()
-      ), propagate(callback, (data) ->
-        callback null,
-          file: file
-          content: data
-      )
+      , propagate callback, (data) ->
+        callback(null, { file, content: data })

-    parse.parseFile assetRoot, globalFlags, indexFile, indexFileDir, encoding, propagate(callback, (parseResult) ->
+    parse.parseFile assetRoot, globalFlags, indexFile, indexFileDir, encoding, propagate callback, (parseResult) ->
       async.reduce addTypes(parseResult.matches),
         cont: parseResult.content
         files: []
-      , ((cc, d, callback) ->
-        async.map d.files, ((file, callback) ->
-          async.map expandFilters, ((hook, callback) ->
-            hook file, assetRoot, indexFile, callback
-          ), propagate(callback, (suggestions) ->
-            callback null, _.first(_.compact(suggestions))
-          )
-        ), propagate(callback, (expandedFiles) ->
-          async.reduce hooks.preproc, _.flatten(expandedFiles), ((acc, hook, callback) ->
-            hook acc,
-              assetRoot: assetRoot
-              indexFile: indexFile
-            , callback
-          ), propagate(callback, (outs) ->
-            async.mapSeries outs, ((file, callback) ->
-              helpers.firstNonNullSeries extendedFetchers, ((hook, callback) ->
-                hook file, d, getData, callback
-              ), callback
-            ), propagate(callback, (data) ->
-              d2 = reduceArray(_.flatten(data), hooks.file, (acc, hook) ->
-                hook acc
-              )
-              helpers.firstNonNullSeries extendedDataHooks, ((hook, callback) ->
-                hook d2, d, hooks.concatable, callback
-              ), propagate(callback, (out) ->
-                tagify out.tags, propagate(callback, (data) ->
+      , (cc, d, callback) ->
+        async.map d.files, (file, callback) ->
+          async.map expandFilters, (hook, callback) ->
+            hook(file, assetRoot, indexFile, callback)
+          , propagate callback, (suggestions) ->
+            callback(null, _.first(_.compact(suggestions)))
+        , propagate callback, (expandedFiles) ->
+          async.reduce hooks.preproc, _.flatten(expandedFiles), (acc, hook, callback) ->
+            hook(acc, { assetRoot, indexFile }, callback)
+          , propagate callback, (outs) ->
+            async.mapSeries outs, (file, callback) ->
+              helpers.firstNonNullSeries extendedFetchers, (hook, callback) ->
+                hook(file, d, getData, callback)
+              , callback
+            , propagate callback, (data) ->
+              d2 = reduceArray _.flatten(data), hooks.file, (acc, hook) ->
+                hook(acc)
+              helpers.firstNonNullSeries extendedDataHooks, (hook, callback) ->
+                hook(d2, d, hooks.concatable, callback)
+              , propagate callback, (out) ->
+                tagify out.tags, propagate callback, (data) ->
                   callback null,
                     cont: helpers.safeReplace(cc.cont, d.match, data)
                     files: cc.files.concat(out.outfiles or [])
-                )
-              )
-            )
-          )
-        )
-      ), propagate(callback, (r) ->
-        fileSaver r.files, encoding, propagate(callback, ->
-          callback null, r.cont
-        )
-      )
-    )
+
+      , propagate callback, (r) ->
+        fileSaver r.files, encoding, propagate callback, ->
+          callback(null, r.cont)


auto deprecate/remove for obsolete provider config

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -30,10 +30,10 @@
     complimentField(@config)

   get: (param) ->
-    atom.config.get "#{@scope}.#{param}"
+    atom.config.get("#{@scope}.#{param}")

   set: (param, value) ->
-    atom.config.set "#{@scope}.#{param}", value
+    atom.config.set("#{@scope}.#{param}", value)

   toggle: (param) ->
     @set(param, not @get(param))
@@ -49,19 +49,25 @@

   removeDeprecated: ->
     paramsToDelete = []
-    for param in Object.keys(atom.config.get(@scope)) when param not of @config
-      paramsToDelete.push(param)
-    @notifyAndDelete(paramsToDelete...)
+    loaded = atom.config.get(@scope)
+    for param in Object.keys(loaded)
+      if param not of @config
+        paramsToDelete.push(param)
+      else if @config[param].type is 'object' # Provider config
+        providerName = param
+        loadedForProvider = loaded[providerName]
+        definedForProvider = @config[providerName].properties
+        for _param in Object.keys(loadedForProvider) when _param not of definedForProvider
+          paramsToDelete.push(providerName + '.' + _param)
+    if paramsToDelete.length
+      @notifyAndDelete(paramsToDelete...)

   notifyAndDelete: (params...) ->
-    paramsToDelete = (param for param in params when @has(param))
-    return if paramsToDelete.length is 0
-
     content = [
       "#{@scope}: Config options deprecated.  ",
       "Automatically removed from your `config.cson`  "
     ]
-    for param in paramsToDelete
+    for param in params
       @delete(param)
       content.push "- `#{param}`"
     atom.notifications.addWarning content.join("\n"), dismissable: true


Bug Fix: typo

Bug Fix: typo

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -69,7 +69,7 @@
 SupportFire = (enemyShip, support) ->
   list = []
   for damage, i in support
-    continue unless 1 < i < 6
+    continue unless 0 < i < 6
     damage = Math.floor(damage)
     dmg = []
     dmg.push damage


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp.coffee
+++ src/clamp.coffee
@@ -3,7 +3,7 @@
     clamps: {}

   self.bind "update", ->
-    for name, cooldownOptions of I.cooldowns
+    for name, cooldownOptions of I.clamps
       {approachBy, target} = cooldownOptions

       I[name] = I[name].approach(target, approachBy)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -442,7 +442,7 @@
     ###*
     A new color. The calling color mixed with `other` using `amount` as the mixing ratio. If amount is not passed, then the colors are mixed evenly.

-    @name mixWith$
+    @name saturate
     @methodOf Color#
     @param {Color} other the other color to mix
     @param {Number} [amount] the mixing ratio of the calling color to `other`


made parseRGB explicity return undefined to match parseHex

--- src/color.coffee
+++ src/color.coffee
@@ -23,7 +23,7 @@
         undefined

   parseRGB = (colorString) ->
-    return unless bits = rgbParser.exec(colorString)
+    return undefined unless bits = rgbParser.exec(colorString)

     return [
       parseInt(bits[1])


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -873,19 +873,24 @@
       "rgba(#{@r}, #{@g}, #{@b}, #{@a})"

     ###*
-    returns string rgba representation of the color.
+    A copy of the calling color with its alpha reduced by `amount`.

-    red = Color('#ff0000')
-
-    red.toString()
-    # => 'rgba(255, 0, 0, 1)'
+    color = Color(0, 0, 0, 1)
+
+    color.a
+    # => 1
+
+    transparentColor = color.transparentize(0.5)
+
+    transparentColor.a
+    # => 0.5

-    @name toString
+    @name transparentize
     @methodOf Color#

-    @returns {String} The rgba string representation of the color
+    @returns {Color} A copy of the calling color with its alpha reduced by `amount`
     ###
     transparentize: (amount) ->
       @copy().transparentize$(amount)


toggle _rollbar logging to prevent error msgs from 3rd party app developers

--- core/utils.coffee
+++ core/utils.coffee
@@ -577,6 +577,12 @@
       else
         new KDNotificationView type : "mini", title : "There was an error, try again later!"

+  stopLoggingToRollbar: ->
+    KD.getSingleton("mainController").old_rollbar = _rollbar
+    window._rollbar = push:()->
+
+  startLoggingToRollbar: ->
+    window._rollbar = KD.getSingleton("mainController").old_rollbar

 ###
 //     Underscore.js 1.3.1


Revert "Remove newlines before objects in arrays"

This reverts commit 3cbcddc35b3b0392db5b2b2962849177a7a3b254.

This commit was breaking most tests and should not have been merged

--- src/Dumper.coffee
+++ src/Dumper.coffee
@@ -38,7 +38,8 @@

                     output +=
                         prefix +
-                        '- ' +
+                        '-' +
+                        (if willBeInlined then ' ' else "\n") +
                         @dump(value, inline - 1, (if willBeInlined then 0 else indent + @indentation), exceptionOnInvalidType, objectEncoder) +
                         (if willBeInlined then "\n" else '')


Beautified the set method

Beautified the set method

--- src/Matrix.coffee
+++ src/Matrix.coffee
@@ -18,16 +18,17 @@
         @trans = new DivSugar.Vector arguments[9], arguments[10], arguments[11]

   set: (mat) ->
-    if arguments.length is 1
-      @xAxis.set mat.xAxis
-      @yAxis.set mat.yAxis
-      @zAxis.set mat.zAxis
-      @trans.set mat.trans
-    else
-      @xAxis.set arguments[0], arguments[1], arguments[2]
-      @yAxis.set arguments[3], arguments[4], arguments[5]
-      @zAxis.set arguments[6], arguments[7], arguments[8]
-      @trans.set arguments[9], arguments[10], arguments[11]
+    switch arguments.length
+      when 1
+        @xAxis.set mat.xAxis
+        @yAxis.set mat.yAxis
+        @zAxis.set mat.zAxis
+        @trans.set mat.trans
+      when 12
+        @xAxis.set arguments[0], arguments[1], arguments[2]
+        @yAxis.set arguments[3], arguments[4], arguments[5]
+        @zAxis.set arguments[6], arguments[7], arguments[8]
+        @trans.set arguments[9], arguments[10], arguments[11]

     return @


Refactoring Runner to use loader parameter.

--- src/Runner.coffee
+++ src/Runner.coffee
@@ -1,15 +1,14 @@
 _ = require 'lodash'
 wire = require 'wire'
-util = require 'util'

-module.exports = (spec, callback) ->
-  plugin = module: "#{__dirname}/plugins/helper"
+module.exports = (spec, loader) ->
+  plugin = module: "#{__dirname}/plugins/Sub"
   if _.has(spec, '$plugins') and _.isArray(spec['$plugins'])
     spec['$plugins'].push plugin
   else
     spec['$plugins'] = [ plugin ]

-  wire(spec).then(
+  wire(spec, require: loader).then(
     (context) ->
       numberOfBeans = _.keys(context).length
       if _.has context, 'logger'
@@ -17,8 +16,5 @@
         logger.info "Application initialized with #{numberOfBeans} beans"
         logger.info "You can see beans setting env.LOG_LEVEL to debug"
         logger.debug "Beans", _.keys context
-      callback null, context
-    (err) ->
-      console.error util.inspect(err, depth: null, showHidden: true), err.stack
-      callback err
+      context
   )


Fix small bug in setInterval and cam id when switching between cameras with auto-refresh on.

--- prod/src/coffee-script/core.coffee
+++ prod/src/coffee-script/core.coffee
@@ -15,13 +15,15 @@
         @camList = $('ul#cam-list')
         @positionList = $('ul#position-list')

-        $('ul#cam-list').delegate 'li.cam-li', 'click', @camClickHandler
+        @camList.delegate 'li.cam-li', 'click', @camClickHandler
+
+        $('div#cam-detail').live 'pagebeforehide', (event, ui) =>
+            clearInterval @refreshId

         $('a#refresh').click (event) =>
             event.preventDefault()
             $.mobile.pageLoading()
             @load()
-

         $('a#find-me').click (event) =>
             event.preventDefault()
@@ -39,11 +41,12 @@

         $('a#cam-detail-refresh').click (event) => @refreshCamDetail camId

-        $('select#cam-detail-auto-refresh').change (event) =>
+        $('select#cam-detail-auto-refresh').change((event) =>
             clearInterval @refreshId
             selection = $(event.target).find 'option:selected'
             if selection.val() is 'on'
                 @refreshId = setInterval (=> @refreshCamDetail camId), 5000
+        ).change()

         headingText = $(event.currentTarget)
             .find('.ui-li-heading')


destructuring assignment

destructuring assignment

--- ccss.coffee
+++ ccss.coffee
@@ -8,7 +8,7 @@
     declarations = ''
     nested = {}

-    if mixin = pairs.mixin
+    if {mixin} = pairs
       delete pairs.mixin
       unless mixin instanceof Array
         mixin = [mixin]


adding new chapeau badge

adding new chapeau badge

--- src/badges.coffee
+++ src/badges.coffee
@@ -53,6 +53,7 @@
   'oliver-lynton-wolfe2',
   'obsidian',
   'p-a-chapeau',
+  'p-a-chapeau2',
   'persepolis',
   'pioneer1', 'pioneer2', 'pioneer3', 'pioneer4', 'pioneer5',
   'purifier1', 'purifier2', 'purifier3', 'purifier4', 'purifier5',
@@ -120,6 +121,7 @@
   'oliver-lynton-wolfe2': 1,
   'obsidian': 1,
   'p-a-chapeau': 1,
+  'p-a-chapeau2': 1,
   'persepolis': 1,
   'pioneer': 5,
   'purifier': 5,


Small tidy up. Also favour the previously set parent, in case we're moving between nestings.

--- src/batman.coffee
+++ src/batman.coffee
@@ -2582,19 +2582,21 @@
   @urlNestsUnder: (keys...) ->
     parents = {}
     for key in keys
-      parents[key] = Batman.helpers.pluralize(key)
+      parents[key + '_id'] = Batman.helpers.pluralize(key)
     children = Batman.helpers.pluralize(Batman._functionName(@).toLowerCase())

     @url = (options) ->
       for key, plural of parents
-        parentID = options.data[key + '_id']
+        parentID = options.data[key]
         if parentID
-          delete options.data[key + '_id']
+          delete options.data[key]
           return "#{plural}/#{parentID}/#{children}"

     @::url = ->
       for key, plural of parents
-        parentID = @get(key + '_id')
+        parentID = @dirtyKeys.get(key)
+        if parentID is undefined
+          parentID = @get(key)
         if parentID
           url = "#{plural}/#{parentID}/#{children}"
           if id = @get('id')


Make private members truly private in class AllExpectations.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -77,38 +77,41 @@
 class AllExpectations

 	@register_expectation: (expectation) ->
-		@_expectations.push(expectation)
+		_expectations.push(expectation)

 	@find_expectation: (object, method_name, args...) ->
-	  for expectation in @_expectations when expectation.matches(object, method_name, args...)
+	  for expectation in _expectations when expectation.matches(object, method_name, args...)
 	    return expectation
 	  undefined

 	@check_for_duplicate_expectations: ->
 	  # TODO: use each with index and slice to avoid last element
-	  return if @_expectations.length < 2
-	  for outer in [0..@_expectations.length-2]
-	    for inner in [outer+1..@_expectations.length-1]
-	      if @_expectations[outer].equals( @_expectations[inner] )
-	        throw_duplicate_expectation("#{@_expectations[outer]._method_name}(#{@_expectations[outer]._args})")
+	  return if _expectations.length < 2
+	  for outer in [0.._expectations.length-2]
+	    for inner in [outer+1.._expectations.length-1]
+	      if _expectations[outer].equals( _expectations[inner] )
+	        throw_duplicate_expectation("#{_expectations[outer]._method_name}(#{_expectations[outer]._args})")

 	@verify_all_expectations: ->
-		errors = @_find_all_errors()
+		errors = _find_all_errors()
 		throw new Error(errors) unless errors == ""

 	# TODO: write comment about uninstalling in reverse
+	# TODO: does this belong in unregister_all_expectsions?
 	@uninstall_all_mocked_methods: ->
-	  expectation.uninstall_mocked_method() for expectation in @_expectations by -1
+	  expectation.uninstall_mocked_method() for expectation in _expectations by -1

+	# TODO: should this be called remove_all_expectations?  What about register_expectations?
+	#       How about install_expectation() and uninstall_all_expectations()?
 	@unregister_all_expectations: ->
-		@_expectations.length = 0
+		_expectations.length = 0

 	# private

-	@_expectations: []
+	_expectations = []

-	@_find_all_errors: ->
-  	  ( expectation.find_errors() for expectation in @_expectations ).join("")
+	_find_all_errors = ->
+  	  ( expectation.find_errors() for expectation in _expectations ).join("")

 #


add pitch constellation to each chord page

--- intervals.coffee
+++ intervals.coffee
@@ -27,8 +27,8 @@
   {name: 'Major 7th', abbr: 'maj7', pitch_classes: '047e'},
   {name: 'Minor 7th', abbr: 'min7', pitch_classes: '037t'},
   {name: 'Dom 7 b5', abbr: '7b5', pitch_classes: '046t'},
-  {name: 'Min 7th b5', abbr: 'm7b5', pitch_classes: '036t'},
-  {name: 'Half-diminished 7th', abbrs: ['ø', 'Ø'], pitch_classes: '036t'},
+  # following is also half-diminished 7th
+  {name: 'Min 7th b5', abbr: ['ø', 'Ø', 'm7b5'], pitch_classes: '036t'},
   {name: 'Dim Maj 7th', abbr: '°Maj7', pitch_classes: '036e'},
   {name: 'Min Maj 7th', abbrs: ['min/maj7', 'min(maj7)'], pitch_classes: '037e'},
   {name: '6th', abbrs: ['6', 'M6', 'M6', 'maj6'], pitch_classes: '0479'},
@@ -329,7 +329,7 @@
   {barres} = options
   draw_strings()
   draw_frets()
-  if barres and (pos for pos in positions when pos.fret > 0).length >= 4
+  if barres and (pos for pos in positions when pos.fret > 0).length > 4
     fret_rows = for fn in FretNumbers
       (for sn in StringNumbers
         if _.find(positions, (pos)-> pos.string == sn and pos.fret > fn)
@@ -461,6 +461,29 @@
     for fingering, i in fingerings
       cell -> draw_fingerboard(fingering)

+draw_pitch_diagram = (pitch_classes, degree_colors) ->
+  r = 10
+  pitch_names = '1 2b 2 3b 3 4 T 5 6b 6 7b 7'.split(/\s/)
+  pitch_names = 'R m2 M2 m3 M3 P4 TT P5 m6 M6 m7 M7'.split(/\s/)
+  pitch_class_angle = (pitch_class) -> (-3 + pitch_class) * 2 * Math.PI / 12
+  for pitch_class, degree_index in pitch_classes
+    a = pitch_class_angle(pitch_class)
+    ctx.beginPath()
+    ctx.moveTo 0, 0
+    ctx.lineTo r * Math.cos(a), r * Math.sin(a)
+    ctx.stroke()
+    ctx.beginPath()
+    ctx.arc r * Math.cos(a), r * Math.sin(a), 2, 0, 2 * Math.PI, false
+    ctx.fillStyle = degree_colors[degree_index]
+    ctx.fill()
+  ctx.font = '4pt Times' if draw_diagrams
+  ctx.fillStyle = 'black'
+  for class_name, pitch_class in pitch_names
+    a = pitch_class_angle(pitch_class)
+    r2 = r + 7
+    m = ctx.measureText(class_name)
+    ctx.fillText class_name, r2 * Math.cos(a) - m.width / 2, r2 * Math.sin(a) + m.emHeightDescent
+
 chord_page = (chord, options) ->
   {best_fingering} = options || {}

@@ -484,9 +507,16 @@
   , cell_height: padded_fretboard_height + diagram_gutter
   , header_height: diagram_title_height
   , (cell) ->
+
     ctx.font = '20px Impact'
     ctx.fillStyle = 'rgb(128, 128, 128)'
     ctx.fillText "#{chord.name} Chords", diagram_gutter / 2, header_height / 2
+
+    ctx.save()
+    ctx.translate 205, 20
+    ctx.scale 0.85,0.85
+    draw_pitch_diagram chord.pitch_classes, degree_colors
+    ctx.restore()

     pitches = ((i * 5 + 3) % 12 for i in [0...12])
     for pitch in pitches
@@ -507,12 +537,13 @@

 chord_book = (options) ->
   page_count = options.pages
-  book "Combined Fretboard Chords", pages: options.pages, (page) ->
+  title = if options.best_fingering then "Fretboard Chords" else "Combined Fretboard Chords"
+  book title, pages: options.pages, (page) ->
     for chord in Chords
       page -> chord_page chord, options

 # chord_page Chords[0], best_fingering: true
 # intervals_book by_root: true
 # intervals_book by_root: false
-chord_book best_fingering: 1, pages: 0
+chord_book best_fingering: 0, pages: 0
 # chord_fingerings_page Chords[0], 44 + 3


added logging

added logging

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -9,6 +9,7 @@
 _ = require "lodash"
 moment = require "moment"
 metrics = require "../metrics"
+logger = require "winston"

 # Overall Metrics

@@ -126,6 +127,7 @@
     url: 'http://' + statsd_server.host + path
   };
   var response = yield request(options);
+  logger.info('Request to statsd: '+ path);

   var info = JSON.parse(response.body);
   if (info.length) {


Add Range.isEqual

Add Range.isEqual

--- src/atom/range.coffee
+++ src/atom/range.coffee
@@ -17,6 +17,12 @@
   copy: (range) ->
     new Range(_.clone(@start), _.clone(@end))

+  isEqual: (other) ->
+    if other instanceof Array and other.length == 2
+      other = new Range(other...)
+
+    other.start.isEqual(@start) and other.end.isEqual(@end)
+
   inspect: ->
     "[#{@start.inspect()} - #{@end.inspect()}]"


rearrange elements

rearrange elements

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -137,86 +137,86 @@
                     onClick: @focusHypeInput

           if mapCanBeNominated || mapIsQualified
-            div null,
-              div
-                className: "#{bn}__row"
-                key: 'nominations',
-                div className: "#{bn}__row-left",
-                  div className: "#{bn}__header",
-                    span
-                      className: "#{bn}__title"
-                      osu.trans 'beatmaps.nominations.title'
-                    span null,
-                      " #{nominations.current}/#{nominations.required}"
-                  @renderLights(nominations.current, nominations.required)
-
-                if @props.currentUser.id? && !@userIsOwner()
-                  div className: "#{bn}__row-right",
-                    if mapIsQualified && @userCanDisqualify()
-                      el BigButton,
-                        modifiers: ['full']
-                        text: osu.trans 'beatmaps.nominations.disqualify'
-                        icon: 'fas fa-thumbs-down'
-                        props:
-                          onClick: @focusNewDiscussionWithModeSwitch
-                    else if mapCanBeNominated && @userCanNominate()
-                      div null,
-                        if @props.currentDiscussions.unresolvedIssues > 0
-                          # wrapper 'cuz putting a title/tooltip on a disabled button is no worky...
-                          div title: osu.trans('beatmaps.nominations.unresolved_issues'),
-                            @nominationButton true
-                        else
-                          @nominationButton @props.beatmapset.nominations.nominated
-
-          if mapCanBeLoved
             div
               className: "#{bn}__row"
-              div className: "#{bn}__row-left"
-              div className: "#{bn}__row-right",
-                el BigButton,
-                  modifiers: ['full']
-                  text: osu.trans 'beatmaps.nominations.love'
-                  icon: 'fas fa-heart'
-                  props:
-                    onClick: @love
-
-
-          div
-            className: "#{bn}__footer #{if mapCanBeNominated then "#{bn}__footer--extended" else ''}",
-            key: 'footer'
-            div className: "#{bn}__note #{bn}__note--disqualification",
-              if mapIsQualified
-                if rankingETA
+              key: 'nominations',
+              div className: "#{bn}__row-left",
+                div className: "#{bn}__header",
+                  span
+                    className: "#{bn}__title"
+                    osu.trans 'beatmaps.nominations.title'
                   span null,
-                    osu.trans 'beatmaps.nominations.qualified',
-                      date: moment(rankingETA).format(dateFormat)
-                else
-                  span null, osu.trans 'beatmaps.nominations.qualified_soon'
-
-              # implies mapCanBeNominated
+                    " #{nominations.current}/#{nominations.required}"
+                @renderLights(nominations.current, nominations.required)
+
+              if @props.currentUser.id? && !@userIsOwner()
+                div className: "#{bn}__row-right",
+                  if mapIsQualified && @userCanDisqualify()
+                    el BigButton,
+                      modifiers: ['full']
+                      text: osu.trans 'beatmaps.nominations.disqualify'
+                      icon: 'fas fa-thumbs-down'
+                      props:
+                        onClick: @focusNewDiscussionWithModeSwitch
+                  else if mapCanBeNominated && @userCanNominate()
+                    div null,
+                      if @props.currentDiscussions.unresolvedIssues > 0
+                        # wrapper 'cuz putting a title/tooltip on a disabled button is no worky...
+                        div title: osu.trans('beatmaps.nominations.unresolved_issues'),
+                          @nominationButton true
+                      else
+                        @nominationButton @props.beatmapset.nominations.nominated
+        ]
+
+      if mapCanBeLoved
+        div
+          className: "#{bn}__row"
+          key: 'love'
+          div className: "#{bn}__row-left"
+          div className: "#{bn}__row-right",
+            el BigButton,
+              modifiers: ['full']
+              text: osu.trans 'beatmaps.nominations.love'
+              icon: 'fas fa-heart'
+              props:
+                onClick: @love
+
+      if showHype
+        div
+          className: "#{bn}__footer #{if mapCanBeNominated then "#{bn}__footer--extended" else ''}",
+          key: 'footer'
+          div className: "#{bn}__note #{bn}__note--disqualification",
+            if mapIsQualified
+              if rankingETA
+                span null,
+                  osu.trans 'beatmaps.nominations.qualified',
+                    date: moment(rankingETA).format(dateFormat)
               else
-                span null,
-                  if disqualification?
-                    span null,
-                      span
-                        dangerouslySetInnerHTML:
-                          __html: @resetReason(disqualification)
-                      ' ' # spacer
-                  if nominationReset?
+                span null, osu.trans 'beatmaps.nominations.qualified_soon'
+
+            # implies mapCanBeNominated
+            else
+              span null,
+                if disqualification?
+                  span null,
                     span
                       dangerouslySetInnerHTML:
-                        __html: @resetReason(nominationReset)
-            if nominators.length > 0
-              div
-                className: "#{bn}__note #{bn}__note--nominators"
-                dangerouslySetInnerHTML:
-                  __html: osu.trans 'beatmaps.nominations.nominated_by',
-                    users: osu.transArray nominators.map (user) ->
-                        osu.link laroute.route('users.show', user: user.id), user.username,
-                          classNames: ['js-usercard']
-                          props:
-                            'data-user-id': user.id
-        ]
+                        __html: @resetReason(disqualification)
+                    ' ' # spacer
+                if nominationReset?
+                  span
+                    dangerouslySetInnerHTML:
+                      __html: @resetReason(nominationReset)
+          if nominators.length > 0
+            div
+              className: "#{bn}__note #{bn}__note--nominators"
+              dangerouslySetInnerHTML:
+                __html: osu.trans 'beatmaps.nominations.nominated_by',
+                  users: osu.transArray nominators.map (user) ->
+                      osu.link laroute.route('users.show', user: user.id), user.username,
+                        classNames: ['js-usercard']
+                        props:
+                          'data-user-id': user.id

   renderLights: (lightsOn, lightsTotal) ->


Update to use new translation system

Update to use new translation system

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -66,21 +66,21 @@
         span null,
           "#{@disqualificationEvent.reason}"
         span className: "#{bdn}__disqualification-time", dangerouslySetInnerHTML:
-          __html: Lang.get 'beatmaps.nominations.disqualifed-at', time_ago: osu.timeago(@disqualificationEvent.created_at)
+          __html: osu.trans 'beatmaps.nominations.disqualifed-at', time_ago: osu.timeago(@disqualificationEvent.created_at)

     nominationsRequiredText =
       if @mapCanBeNominated
-        Lang.get 'beatmaps.nominations.required-text',
+        osu.trans 'beatmaps.nominations.required-text',
           current: @currentNominations,
           required: @nominationsRequired
       else if @mapIsQualified
         if @rankingETA
           date = osu.timeago(@rankingETA)
         else
-          date = Lang.get 'beatmaps.nominations.soon'
+          date = osu.trans 'beatmaps.nominations.soon'

         span dangerouslySetInnerHTML:
-          __html: Lang.get 'beatmaps.nominations.qualified', date: date
+          __html: osu.trans 'beatmaps.nominations.qualified', date: date

     div className: bdn,
       div className: 'osu-layout__row osu-layout__row--sm1 osu-layout__row--page-compact',
@@ -99,7 +99,7 @@
                   onClick: => $.publish 'beatmapset:disqualify', all: 'disqualify'
                   el Icon, name: 'thumbs-down'
                   span className: "#{bd}s__collapse-button-text",
-                    Lang.get 'beatmaps.nominations.disqualify'
+                    osu.trans 'beatmaps.nominations.disqualify'
               else if @mapCanBeNominated
                 button
                   className: "btn-osu-lite btn-osu-lite--green"
@@ -107,7 +107,7 @@
                   onClick: => $.publish 'beatmapset:nominate', all: 'nominate'
                   el Icon, name: 'thumbs-up'
                   span className: "#{bd}s__collapse-button-text",
-                    Lang.get 'beatmaps.nominations.nominate'
+                    osu.trans 'beatmaps.nominations.nominate'

   reboot: ->
     osu.pageChange()


Fix elementBind

Fix elementBind

--- hooks.coffee
+++ hooks.coffee
@@ -77,14 +77,16 @@
       this.viewmodel.onDestroyed this
     this.viewmodel.dispose()

-binds = { }
+htmls = { }
 Blaze.Template.prototype.elementBind = (selector, data) ->
-  if not data and binds[this.name]
-    return binds[this.name]
-
-  html = $("

").append($(Blaze.toHTMLWithData(this, data)))
-  bind = ViewModel.parseBind(html.find(selector).data("bind"))
-  if not data
-    binds[this.name] = bind
-
-  return bind
+  name = this.viewName
+  html = null
+  if data
+    html = $("

").append($(Blaze.toHTMLWithData(this, data)))
+  else if htmls[name]
+    html = htmls[name]
+  else
+    html = $("

").append($(Blaze.toHTML(this)))
+    htmls[name] = html
+
+  ViewModel.parseBind(html.find(selector).data("bind"))


Selectively change the state of the "Save" button

--- scripts/views/workspace/menu/auth.coffee
+++ scripts/views/workspace/menu/auth.coffee
@@ -32,10 +32,11 @@

     initialize: () ->
       @listenTo(session, 'login logout', @render)
-      @listenTo(content, 'add remove', @changed)
+      @listenTo content, 'add remove', (model, collection, options) =>
+        @changed() if not (options.loading or options.parse)
       @listenTo content, 'change', (model, options) =>
         # A change event can occur (ie setting a title during parsing but the changed set is still empty)
-        @changed() if model?.hasChanged()
+        @changed() if model?.hasChanged() and not (options.loading or options.parse)

       # Bind a function to the window if the user tries to navigate away from this page
       $(window).on 'beforeunload', () ->


Added version tag support.

Added version tag support.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -323,6 +323,10 @@
       output += "#{section.description}\n"
       omitList.push "description"

+    if section.version?
+      output += "Version: `#{section.version}`  \n\n"
+      omitList.push "version"
+
     if section.author? and not index
       footer += _formatKeyValue "author", section.author, false, 2


Fixing typo: snaphot -> snapshot

Fixing typo: snaphot -> snapshot

--- src/client/doc.coffee
+++ src/client/doc.coffee
@@ -30,7 +30,7 @@
     # Any of these can be null / undefined at this stage.
     openData ||= {}
     @version = openData.v
-    @snapshot = openData.snaphot
+    @snapshot = openData.snapshot
     @_setType openData.type if openData.type

     @state = 'closed'


added more logging

added more logging

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -257,11 +257,12 @@
 					logger.error err: error, doc_id: doc_id, project_id: project_id, lines: lines, "doc not found while updating doc lines"
 					return callback(error)

+				logger.log project_id: project_id, doc_id: doc_id, "telling docstore manager to update doc"
 				DocstoreManager.updateDoc project_id, doc_id, lines, (err, modified, rev) ->
 					if err?
 						logger.error err: err, doc_id: doc_id, project_id:project_id, lines: lines, "error sending doc to docstore"
 						return callback(err)
-
+					logger.log project_id: project_id, doc_id: doc_id, modified:modified, "finished updating doc lines"
 					if modified
 						# Don't need to block for marking as updated
 						projectUpdateHandler.markAsUpdated project_id


add method partial to tags

add method partial to tags

--- src/cache.coffee
+++ src/cache.coffee
@@ -40,6 +40,11 @@
     Tag = xml.Tag
     # create new tag class to extend it with a schema
     class ExtendedTag extends Tag
+        partial: (partial) ->
+            return this unless partial.run? # tag should be already added then
+            @add(partial)
+            partial.run() # guess this is a template
+            return this
     # create tag method shortcuts defined by schema
     ff ExtendedTag::, opts.schema
     # write it back so builder can use it to instantiate a new tag


dont replace underscores in PSN id's

dont replace underscores in PSN id's

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -97,12 +97,12 @@

 tryPlayerId = (res, membershipType, displayName, robot) ->
   deferred = new Deferred()
-  # replaces underscores with spaces (for xbox)
-  # safe to call on PSN IDs because underscores are not allowed
-  displayName = displayName.split('_').join(' ')

   if membershipType
     networkName = if membershipType is '1' then 'xbox' else 'playstation'
+    # replaces underscores with spaces (for xbox)
+    displayName = displayName.split('_').join(' ') if networkName is 'xbox'
+
     return getPlayerId(res, membershipType, displayName, robot)
     .then (results) ->
       if !results
@@ -113,7 +113,7 @@
       deferred.promise
   else
     return Q.all([
-      getPlayerId(res, '1', displayName, robot),
+      getPlayerId(res, '1', displayName.split('_').join(' '), robot),
       getPlayerId(res, '2', displayName, robot)
     ]).then (results) ->
       if results[0] && results[1]


remove blank function

remove blank function

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -5,7 +5,6 @@
 exec = require("child_process").exec

 module.exports = (robot) ->
-	robot.on 'github_deployment_event', ->
 	robot.respond /deploy2/i, (msg) ->

 		unless robot.auth.hasRole(msg.envelope.user, 'deploy')


updating a description of a script

updating a description of a script

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Prem, Prem, Prem
+#   Dr Matt sayzz
 #
 # Dependencies:
 #   None


Add text to console.log on page load

Add text to console.log on page load

--- scripts/global.coffee
+++ scripts/global.coffee
@@ -128,3 +128,32 @@
         ad.innerHTML = "Sorry! You won't see any ads again. If you change your mind, opt-in here." for ad in document.querySelectorAll('.advertisement')
     , false) for link in document.querySelectorAll('.ad-opt-out')
 , false)
+
+
+console.log("""
+      ........
+     /hdddddddyyyy///`
+    /ddddddddddddddddddyo+-`
+   /dddddddddddddddddddddddhy//.
+  /hdddddddddddddddddddddddddddds+-`
+ .yddddddddddddddddddddddddddddddddh+-
+`oddddddddddddddddddddddddddddddddddddy-            Want to read the unobsfucated source? Check it out
+.dddddddddddhyysssssyhdhyysssssyhdddddddhs-         at https://github.com/MediaCrush/MediaCrush
+ydddddddddhs-`       .:-`       .sdddddddddo.
+dddddddddds                      :hdddddddddds-     Send us some pull requests!
+dddddddddd-   `+yyy:    /yyy+    :hdddddddddddh+
+dddddddddy`   -dddh:   .yddd+   `+ddddddddddddddy:
+ddddddddd+`   odddy`   :hddd-   `ydddddddddddddddd
+ddddddddh:    ydddo    +dddy`   -hddddddddddddddy:
+ddddddddy.   -hddh:   .sdddo`   +dddddddddddddy-
+ddddddddo`   +hddh`   -hddh:    ydddddddddddds`
++ddddddh/   `sddds   `+dddy.   -hddddddddddo.
+.ddddddhyyyyyhdddhyyyyhdddhyyyyyddddddddy:.
+`+ddddddddddddddddddddddddddddddddddddy-
+ .yddddddddddddddddddddddddddddddddh/-
+  /hdddddddddddddddddddddddddddds+-
+   /dddddddddddddddddddddddhys/.
+    /ddddddddddddddddddhs+--
+     /hdddddddhyyy///:
+      .........
+""")


遅刻についてhearする文言を追加

遅刻についてhearする文言を追加

--- scripts/secret.coffee
+++ scripts/secret.coffee
@@ -1,6 +1,7 @@
 module.exports = (robot) ->
-  robot.hear /遅刻します|ちこくします|遅刻しそう|ちこくしそう|遅れます|おくれます|おくれそう|遅れそう|まにあわな|間に合わな|間にあわな/, (msg) ->
+  robot.hear /遅刻します|ちこくします|遅刻しそう|ちこくしそう|遅れる|遅れます|おくれます|おくれそう|遅れそう|まにあわな|間に合わな|間にあわな/, (msg) ->
     messages = ["は？", "え？", "本気で言ってんの？", "3分間待ってやる", "ボードゲームの提供、誠にありがとうございます。"]
     index = Math.floor(Math.random() * messages.length)
     message = messages[index]
     msg.send "@#{msg.envelope.user.name} #{message}"
+


resolve path. refs #61

resolve path. refs #61

--- lib/atom-ternjs-reference.coffee
+++ lib/atom-ternjs-reference.coffee
@@ -51,7 +51,7 @@
       @references = data
       for ref in data.refs
         ref.file = ref.file.replace(/^.\//, '')
-        ref.file = dir.relativize(ref.file)
+        ref.file = path.resolve(__dirname, ref.file)
       data.refs = _.uniq(data.refs, (item) =>
         JSON.stringify item
       )
@@ -62,9 +62,7 @@

   gatherMeta: (data) ->
     for item, i in data.refs
-      projectRoot = @manager.server.rootPath
-      file = path.resolve(__dirname, projectRoot.path + '/' + item.file)
-      content = fs.readFileSync(file, 'utf8')
+      content = fs.readFileSync(item.file, 'utf8')
       buffer = new TextBuffer({ text: content })
       item.position = buffer.positionForCharacterIndex(item.start)
       item.lineText = buffer.lineForRow(item.position.row)


Remove unneeded rootView variable

Remove unneeded rootView variable

--- src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
+++ src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
@@ -6,7 +6,7 @@
 fs = require 'fs'

 describe 'FuzzyFinder', ->
-  [rootView, finder] = []
+  [finder] = []

   beforeEach ->
     rootView = new RootView(require.resolve('fixtures/sample.js'))


+ data model (base)

+ data model (base)

--- src/controller.coffee
+++ src/controller.coffee
@@ -19,9 +19,6 @@
 {Readable} = require 'stream'
 querystring = require 'querystring'

-# First char upper
-String::ucfirst = -> @charAt(0).toUpperCase() + @substr(1)
-
 # function -> script
 Function::script = (argv) ->
   env = ''
@@ -37,32 +34,55 @@
 # Application script
 application = ->
   # application object
-  app = window.app = (event) ->
-    type = event.type
-    id = if event.target?.id then event.target.id else ''
-    unless app[id]
-      app[id] = event.target
-      app[id]._sync = true
-      app[id]._remote = {}
-    sender = app[id]
+  app = window.app = (event, selector) ->
     window.event = event
-    return sender._remote[type]() if (typeof sender._remote[type] is 'function') and (sender._sync isnt true)
-
+    app._url = "#{location.pathname}?selector=#{selector}&_id=event&_key="
+    return app._selector[selector]() if typeof app._selector[selector] is 'function'
     # getting event script
-    app._callback = null
-    $.getScript location.pathname +
-      '?_id=EVENT' +
-      '&_key=' +
-      '&id=' + id +
-      '&type=' + type
-    .done ->
-      sender._sync = app._sync
-      sender._remote[type] = app._callback
-      sender._remote[type]() if typeof sender._remote[type] is 'function'
+    $.getScript app._url + 'script'
+    .done -> app._selector[selector]() if typeof app._selector[selector] is 'function'

   # application events
-  app.click = -> app _event
-  app.keypress = -> app _event
+  app._selector = {}
+  app.selector = (selector) -> app _event, selector
+
+  # core data
+  app.core = {
+    # base data carrier and catcher
+    base: (model) ->
+      # get value from dom element
+      get = (elem, name) ->
+        switch name.toString()
+          when 'text' then value = elem.text()
+          when 'html' then value = elem.html()
+          when 'value' then value = elem.val()
+          else value = elem.attr name
+        return value
+      # user data
+      data = {}
+      for id, attrs of model
+        elem = $('#' + id)
+        data[id] = {}
+        unless $.isArray attrs then data[id] = get elem, attrs
+        else data[id][name] = get elem, name for name in attrs
+      # set data
+      carrier = (data) ->
+        # set value to dom element
+        set = (elem, name, value) ->
+          switch name.toString()
+            when 'text' then elem.text value
+            when 'html' then elem.html value
+            when 'value' then elem.val value
+            else elem.attr name, value
+        # user data
+        for id, attrs of model
+          elem = $('#' + id)
+          continue unless data[id]
+          unless $.isArray attrs then set elem, attrs, data[id]
+          else set elem, name, data[id][name] for name in attrs
+      # post data
+      $.post app._url + 'data', (JSON.stringify data), carrier, 'json'
+  }

   # getting user event
   window.constructor::__defineGetter__ '_event', ->
@@ -84,28 +104,26 @@
       @_finish = null
       @_error? err
     # application's event callback
-    @_sync = false
     @_callback = ''

   # End buffer
   end: ->
-    if @_callback.length > 0
-      @_buffer?.write "
-        window.app._sync = #{@_sync.toString()};
-        window.app._callback = function() {
-          #{@_callback}
-        };
-      "
+    return if @_end is true
+    switch @query._key
+      when 'script' then @_buffer?.write "app._selector['#{@query.selector}'] = function() { #{@_callback} };"
+      when 'data' then @_buffer?.write JSON.stringify @data
     @_buffer?.end.apply @_buffer, arguments
+    @_end = true

   # Write chunk to buffer
-  write: -> @_buffer?.write.apply @_buffer, arguments
+  write: -> @_buffer?.write.apply @_buffer, arguments if @_end isnt true

   # Write status and headers
-  writeHead: -> @_buffer?.writeHead.apply @_buffer, arguments
+  writeHead: -> @_buffer?.writeHead.apply @_buffer, arguments if @_end isnt true

   # Receive stream
   receive: (stream, encoding = 'utf8') ->
+    return if @_end is true
     return unless stream instanceof Readable
     stream.setEncoding = encoding if typeof encoding is 'string'
     stream.pipe @_buffer if @_buffer
@@ -125,14 +143,35 @@
       throw new TypeError typeof(callback) + ' is not a function'
     @_error = callback

-  # Sync client callback
-  sync: -> @_sync = true
-
   # User event script
-  script: (callback, argv) ->
+  remote: (callback, argv) ->
     if typeof callback isnt 'function'
       throw new TypeError typeof(callback) + ' is not a function'
     @_callback += callback.script argv
+
+  # parse data raw
+  parse: (raw) -> return JSON.parse raw
+
+  # User date model
+  model: (template, model) ->
+    # user template
+    if (typeof template is 'object') and (template isnt null)
+      model = template
+      template = 'base'
+    # data model
+    if (typeof model is 'object') and (model isnt null) and (!util.isArray model)
+      return if @query._key isnt 'script'
+      # user template
+      if typeof template is 'function'
+        @remote (-> func model), func: template, model: model
+      # template library
+      else
+        switch template
+          when 'base' #, ... more templates
+            @remote (-> app.core[key] model), key: template, model: model
+          # default
+          else @remote (-> app.core['base'] model), model: model
+      @end()

   # Respond client
   _do: (req, @_buffer) ->
@@ -141,33 +180,21 @@
     urls = url.parse req.url
     @query = querystring.parse urls.query
     @query.raw = urls.query
-
     # response buffer
     @_buffer.removeAllListeners 'error'
     @_buffer.on 'error', (err) => @_error? err
-
     # default content
+    @writeHead 200, "Content-Type": "text/plain;charset=utf-8"
     return @render() if !@query.raw or @query.raw.length < 1
-
     # application content
-    @writeHead 200, "Content-Type": "text/javascript;charset=utf-8"
     return @end application.script() if @query.raw is 'script'
     # post data
-    data = ''
-    req.addListener 'data', (chunk) -> data += chunk
-    req.addListener 'end', =>
-      @_form = querystring.parse data
-      @_form.raw = data
+    raw = ''
+    req.on 'data', (chunk) -> raw += chunk
+    req.on 'end', =>
+      @data = @parse raw if raw.length > 0
       switch @query._id
-         when 'EVENT' then @_event()
-
-  # Application's event
-  _event: ->
-    type = @query.type
-    index = type.length
-    index = 3 if (type.indexOf('key') is 0) or (type.indexOf('dbl') is 0)
-    index = 5 if type.indexOf('mouse') is 0
-    methodName = @query.id + type.substr(0, index).ucfirst() + type.substr(index).ucfirst()
-    return @end() if typeof @[methodName] isnt 'function'
-    @query = null
-    @[methodName]()
+        when 'event'
+          selector = @query.selector
+          @[selector]() if typeof @[selector] is 'function'
+          @end()


Fix floating blocks, kind of (undo doesn't stop for floating blocks)

--- src/controller.coffee
+++ src/controller.coffee
@@ -678,7 +678,10 @@
       (@undoStack[@undoStack.length - 1] is 'CAPTURE' and
       @getValue() isnt currentValue)
     operation = @popUndo()
-    @tree.perform(operation, 'backward') unless operation is 'CAPTURE'
+    if operation instanceof FloatingOperation
+      @performFloatingOperation(operation, 'backward')
+    else
+      @tree.perform(operation, 'backward') unless operation is 'CAPTURE'

   @popUndo()
   @redrawMain()
@@ -705,7 +708,10 @@
       (@redoStack[@redoStack.length - 1] is 'CAPTURE' and
       @getValue() isnt currentValue)
     operation = @popRedo()
-    @tree.perform(operation, 'forward') unless operation is 'CAPTURE'
+    if operation instanceof FloatingOperation
+      @performFloatingOperation(operation, 'forward')
+    else
+      @tree.perform(operation, 'forward') unless operation is 'CAPTURE'

   @popRedo()
   @redrawMain()
@@ -1250,7 +1256,7 @@

     # Add the undo operation associated
     # with creating this floating block
-    @addMicroUndoOperation new ToFloatingOperation @draggingBlock, renderPoint, this
+    @pushUndo new FloatingOperation @draggingBlock, renderPoint, 'create'

     # Add this block to our list of floating blocks
     @floatingBlocks.push new FloatingBlockRecord(
@@ -1267,6 +1273,21 @@
     @redrawMain()
     @redrawHighlights()

+Editor::performFloatingOperation = (op, direction) ->
+  console.log 'performing floating operation'
+  if (op.type is 'create') is (direction is 'forward')
+    @floatingBlocks.push new FloatingBlockRecord(
+      op.block.clone()
+      op.position
+    )
+  else
+    @floatingBlocks = @floatingBlocks.filter (record) ->
+      not (record.block.stringify() is op.block.stringify() and
+           record.position.equals(op.position))
+
+class FloatingOperation
+  constructor: (@block, @position, @type) ->
+
 # On mousedown, we can hit test for floating blocks.
 hook 'mousedown', 5, (point, event, state) ->
   # If someone else has already taken this click, pass.
@@ -1291,7 +1312,9 @@
   if @clickedBlock? and point.from(@clickedPoint).magnitude() > MIN_DRAG_DISTANCE
     for record, i in @floatingBlocks
       if record.block is @clickedBlock
-        @addMicroUndoOperation new FromFloatingOperation record, this
+        @undoCapture()
+
+        @pushUndo new FloatingOperation record.block, record.position, 'delete'

         @floatingBlocks.splice i, 1


cleaned up post query in publish.

cleaned up post query in publish.

--- server/publish.coffee
+++ server/publish.coffee
@@ -14,15 +14,10 @@
     for item in Posts.find( author_id: user_id ).fetch()
       ids.push( item['parent_id'] ) if 'parent_id' of item
       ids.push( item['_id'] )
-    # query for the children and posts from above
-    q = Posts.find(
-      {
-        '$or':
-          [{_id: { '$in': ids }},
-            {parent_id: { '$in': ids }}
-          ]
-      }
-    )
+    # query for posts or children of my posts or parents
+    in_ids = { '$in': ids }
+    in_or_child_of_ids = { '$or': [ {_id: in_ids}, {parent_id: in_ids} ] }
+    q = Posts.find( in_or_child_of_ids )

     action = (doc, idx) =>
       client_post = new ClientPost( doc )


minor corrections to the debug messages

minor corrections to the debug messages

--- library/exposure/navigate.coffee
+++ library/exposure/navigate.coffee
@@ -90,7 +90,7 @@
         attaching = "no hasher library has been found"
         assert _.isObject(hasher or null), attaching
         assert _.isObject(crossroads or null), router
-        logger.info "setting up the hashing navigation"
+        logger.info "Setting up the hashing navigation"
         parser = (landed, old) -> crossroads.parse landed
         begin = _.find @, (x) -> x?.meta?.options?.default
         hasher.setHash begin.meta.endpoint if begin?.meta
@@ -101,5 +101,5 @@
             return unless _.isFunction value or null
             return unless value?.meta?.endpoint or 0
             assert endpoint = try value.meta.endpoint
-            logger.debug "mount endpoint #{endpoint}"
+            logger.debug "Mount endpoint #{endpoint}"
             crossroads.addRoute "#{endpoint}", value


Added some more possible locations that chrome might be installed on Windows

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -47,22 +47,27 @@
         return null if chromeVersions.length is 0 # Broken Chrome...
         chromeVer = chromeVersions.reduce getNewerVersion
         chromeFlashPath = join chromeVersionsDir, chromeVer,
-          'Google Chrome Framework.framework/Internet Plug-Ins/PepperFlash/PepperFlashPlayer.plugin'
+          'Google Chrome Framework.framework/Internet Plug-Ins/PepperFlash', FILENAME
         fs.accessSync chromeFlashPath
         chromeFlashPath
       catch
         null
     when 'win32'
-      try
-        chromeVersionsDir = join app.getPath('appData'), 'Local', 'Google', 'Chrome', 'Application'
-        chromeVersions = fs.readdirSync chromeVersionsDir
-        return null if chromeVersions.length is 0 # Broken Chrome...
-        chromeVer = chromeVersions.reduce getNewerVersion
-        chromeFlashPath = join chromeVersionsDir, chromeVer, 'PepperFlash', FILENAME
-        fs.accessSync chromeFlashPath
-        chromeFlashPath
-      catch
-        null
+      chromeInstallDirs = [
+        join(app.getPath('appData'), 'Local')
+        'C:\\Program Files (x86)'
+        'C:\\Program Files'
+      ]
+      for chromeDir in chromeInstallDirs
+        chromeVersionsDir = join chromeDir, 'Google', 'Chrome', 'Application'
+        try
+          chromeVersions = fs.readdirSync chromeVersionsDir
+          if chromeVersions.length > 0
+            chromeVer = chromeVersions.reduce getNewerVersion
+            chromeFlashPath = join chromeVersionsDir, chromeVer, 'PepperFlash', FILENAME
+            fs.accessSync chromeFlashPath
+            return chromeFlashPath
+      null
     else null

 findSystemFlashPath = ->


add something little in here

add something little in here

--- library/exposure/navigate.coffee
+++ library/exposure/navigate.coffee
@@ -71,6 +71,8 @@
         endpoint = _.find(arguments, _.isString) or 0
         implement = _.find(arguments, _.isFunction) or 0
         options = _.find(arguments, _.isPlainObject) or {}
+        m = "Mounting client site navigation path %s to %s"
+        assert identify = try @identify().underline or null
         assert _.isString(endpoint), "got invalid endpoint"
         assert _.isFunction(implement), "no implementation"
         assert not implement.remote?, "is already external"
@@ -78,6 +80,7 @@
         assert _.isFunction externed = external implement
         assert _.isObject externed.remote.meta ?= Object()
         assert ptr = endpoint: endpoint, options: options
+        logger.debug m.grey, endpoint.underline, idenity
         _.extend externed.remote.meta, ptr; externed

     # This is an external autocall routine that when invoked on the


minor changes to the abstract widget

minor changes to the abstract widget

--- library/semantic/abstract.coffee
+++ library/semantic/abstract.coffee
@@ -70,23 +70,23 @@
     # internal APIs and shorthands for convenient access to some
     # of the most basic functionality related to a visual widget.
     # The reference has to be a bare (without a dot) HTML class.
-    constructor: (@container, @reference, payload) ->
+    constructor: (@container, @reference, @payload) ->
         msg = "Constructing widget %s with reference %s"
         ptf = "no valid template-function for an element"
         noContainer = "no valid container object supplied"
         noReference = "no valid reference string supplied"
         noPayload = "something wrong with payload function"
-        try payload = (-> return undefined) unless payload
+        try @payload = (-> return undefined) unless @payload
         assert _.isObject(@container or null), noContainer
         assert _.isString(@reference or null), noReference
-        assert _.isFunction(payload or null), noPayload
+        assert _.isFunction(@payload or null), noPayload
         assert @show = => this.element.show() # shortcut
         assert @hide = => this.element.hide() # shortcut
         assert _.isFunction(@constructor::element), ptf
         @element = $ renderable(@constructor::element) @
         @element.addClass "semantic-widget", @reference
         @element.appendTo (try @container or undefined)
+        @payload.call this, @element, @reference or 0
         assert identify = try @constructor.identify()
-        assert ref = @reference.toString().bold or 0
-        payload.call this, @element, @reference or 0
+        assert ref = try @reference.toString().bold
         logger.debug msg, identify.bold, ref.bold


better assertion

better assertion

--- library/semantic/formular.coffee
+++ library/semantic/formular.coffee
@@ -68,8 +68,8 @@
         fieldTwo = ".field.#{fieldTwo}" if selectorTwo
         fieldOne = @container.find fieldOne if selectorOne
         fieldTwo = @container.find fieldTwo if selectorTwo
-        assert _.isObject(fieldOne), "invalid object A given"
-        assert _.isObject(fieldTwo), "invalid object B given"
+        assert fieldOne.length > 0, "invalid object A given"
+        assert fieldTwo.length > 0, "invalid object B given"
         assert union = try $ "<div>", class: "two fields"
         union.insertBefore fieldOne # place before A field
         fieldOne.appendTo union; fieldTwo.appendTo union


Add atom.syntax

Add atom.syntax

--- lib/grammar-view.coffee
+++ lib/grammar-view.coffee
@@ -20,7 +20,7 @@
   updateGrammarText: =>
     grammar = @getActiveView()?.getGrammar?()
     if grammar?
-      if grammar is syntax.nullGrammar
+      if grammar is atom.syntax.nullGrammar
         grammarName = 'Plain Text'
       else
         grammarName = grammar.name


利用bulkCreate批量插入

利用bulkCreate批量插入

--- lib/helper/batch.coffee
+++ lib/helper/batch.coffee
@@ -5,17 +5,6 @@
 async   = require 'async'
 utils   = require '../../lib/utils'
 errors  = require '../../lib/errors'
-
-PARALLELLIMIt = (require('os')).cpus().length or 8
-
-save = (model, callback) ->
-  model.save().done((error, mod) ->
-    return callback(error) if error
-    mod.reload().done((error) ->
-      return callback(error) if error
-      callback(null, mod)
-    )
-  )

 rest =

@@ -56,9 +45,9 @@
       )

   # 报错
-  save: (hook) ->
+  save: (hook, Model) ->
     (req, res, next) ->
-      async.mapLimit(req.hooks[hook], PARALLELLIMIt, save, (error, results) ->
+      Model.bulkCreate(req.hooks[hook]).done((error, results) ->
         err = errors.sequelizeIfError error
         return next(err) if err
         req.hooks[hook] = results
@@ -70,7 +59,7 @@
   add: (Model, cols, hook = "#{Model.name}s", attachs = null) ->
     [
       rest.validate(Model, cols, hook)
-      rest.save(hook)
+      rest.save(hook, Model)
       rest.detail(hook, attachs, 201)
     ]


forgot to remove this

forgot to remove this

--- src/glaze.coffee
+++ src/glaze.coffee
@@ -1,35 +0,0 @@
-fs          = require 'fs'
-path        = require 'path'
-stylus      = require 'stylus'
-nib         = require 'nib'
-{exists,read}    = require './utils'
-
-minify = (css) ->
-  uglifycss = require 'uglifycss'
-  uglifycss.processString css,
-    maxLineLen: 0
-    expandVars: false
-    cuteComments: false
-
-cssWrap = (style, name) ->
-  name+"()\n  @css{\n" + style + "\n  }" # => can do import 'name', name() in stylus
-
-module.exports = (o) ->
-  throw new Error('brownie.glaze requires a target and an entryPoint') if !o.target or !o.entryPoint
-  throw new Error('brownie.glaze: entryPoint not found: tried: '+o.entryPoint) if !exists(o.entryPoint)
-
-  stylus(read(o.entryPoint))
-  .set('filename',o.entryPoint)
-  .use(nib())
-  .import('nib')
-  .render (err, css) ->
-    throw err if err
-
-    if o.minify
-      minifier = o.minifier ? minify
-      minifier = minifier
-      throw new Error("brownie.glaze: minifier must be a function") if !minifier instanceof Function
-      css = minifier(css)
-
-    fs.writeFileSync(o.target, css)
-    return


teehee

teehee

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -1,4 +1,4 @@
-module.exports = LinterCrystal =
+module.exports = LinterClang =
   config:
     command:
       type: 'string'
@@ -79,7 +79,7 @@
           Content = []
           for line in Data
             Content.push XRegExp.exec(line, regex)
-            console.log line# if atom.inDevMode()
+            console.log line if atom.inDevMode()
           ToReturn = []
           Content.forEach (regex) ->
             if regex


add constructor and destroy method

add constructor and destroy method

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -20,14 +20,14 @@
   # A regex pattern used to extract information from the executable's output.
   regex: '.+:(?<line>\\d+):.+: .*((?<error>error)|(?<warning>warning)): (?<message>.*)'

-#   constructor: (editor) ->
-#     super(editor)
-#
-#     atom.config.observe 'linter-clang.clangExecutablePath', =>
-#       @executablePath = atom.config.get 'linter-clang.clangExecutablePath'
-#
-#   destroy: ->
-#     atom.config.unobserve 'linter-clang.clangExecutablePath'
+  constructor: (editor) ->
+    super(editor)
+
+    atom.config.observe 'linter-clang.clangExecutablePath', =>
+      @executablePath = atom.config.get 'linter-clang.clangExecutablePath'
+
+  destroy: ->
+    atom.config.unobserve 'linter-clang.clangExecutablePath'

   createMessage: (match) ->
     # message might be empty, we have to supply a value


:art: Make bubble follow cursor

:art: Make bubble follow cursor

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -72,7 +72,7 @@
     try @_messages.forEach (message) =>
       return unless message.currentFile
       return unless message.range?.containsPoint? point
-      @_bubble = activeEditor.markBufferRange(message.range, {invalidate: 'never'})
+      @_bubble = activeEditor.markBufferRange([point, point], {invalidate: 'never'})
       activeEditor.decorateMarker(
         @_bubble
         {


:art: Simplify validation in LinterViews::render

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -34,8 +34,7 @@

   # This message is called in editor-linter.coffee
   render: ->
-    return @setPanelVisibility(false) unless @linter.activeEditor
-    return @setPanelVisibility(false) unless @linter.activeEditor.getPath?()
+    return @setPanelVisibility(false) unless @linter.activeEditor?.getPath?()

     counts = {project: 0, file: 0}
     activeLinter = @linter.getActiveEditorLinter()


Write refs, update the server info, add _Git metadata to files

--- src/index.coffee
+++ src/index.coffee
@@ -77,21 +77,18 @@
   # cleanup
   rm '-rf', tmpdir

+console.log "Calculate info-refs"
+exec 'git update-server-info'
+
 server = new mongo.Server argv.host, argv.port
 db = new mongo.Db argv.db, server, {w:1}
 db.open (err) ->
   console.error "Couldn't open database connection, #{err}" if err
   console.log "Connected to mongo!"
   grid = new gfs db, mongo, argv.gridfs
-  objList = []
-  for dir in ls '.git/objects/*/' when dir.length is 15
-    for obj in ls dir # when obj.length is 15
-      console.log "Obj: #{dir}/#{obj}"
-      objList.push "#{dir}/#{obj}"
-  console.dir objList

-  doIt = (obj, cb) ->
-    console.log "Doin' it for #{argv.name}#{obj}"
+  writeObj = (obj, cb) ->
+    console.log "Processing #{argv.name}#{obj}"
     # Check for object in gridfs
     grid.exist { _id: "#{argv.name}#{obj}" }, (err, found) ->
       if err or found
@@ -102,7 +99,8 @@
           filename: "#{argv.name}#{obj}"
           content_type: 'application/octet-stream'
           alias: []
-          metadata: {}
+          metadata:
+            _Git: {}
           chunkSize: argv.chunksize
           mode: 'w'
         ,
@@ -118,11 +116,52 @@
               ws.lockReleased (err, ld) ->
                 cb null, file

+  copyFile = (filename, cb) ->
+    console.log "Copying #{argv.name}#{filename}"
+    # Overwrite object in gridfs
+    grid.createWriteStream
+        _id: "#{argv.name}#{filename}"
+        filename: "#{argv.name}#{filename}"
+        content_type: 'application/octet-stream'
+        alias: []
+        metadata:
+          _Git: {}
+        chunkSize: argv.chunksize
+        mode: 'w'
+      ,
+        (err, ws) ->
+          return cb err if err
+          rs = fs.createReadStream obj
+          rs.pipe(ws)
+          ws.on 'error', (err) ->
+            cb err
+          ws.on 'close', (file) ->
+            console.log "Wrote file:"
+            console.dir file
+            ws.lockReleased (err, ld) ->
+              cb null, file
+
   async.series [
     (cb) ->
-      async.eachLimit objList, 1, doIt, cb
-  ] , (err) ->
-    throw err if err
-    db.close (err) ->
-      console.error "Couldn't close database connection, #{err}" if err
-      console.log "Disconnected from mongo!"
+      objList = []
+      for dir in ls '.git/objects/*/' when dir.length is 15
+        for obj in ls dir # when obj.length is 15
+          console.log "Obj: #{dir}/#{obj}"
+          objList.push "#{dir}/#{obj}"
+      console.dir objList
+      async.eachLimit objList, 1, writeObj, cb
+    (cb) ->
+      # Copy refs
+      tags = (dir for dir in ls '.git/refs/tags/*')
+      branches = (dir for dir in ls '.git/refs/heads/*')
+      refs = branches.concat tags
+      refs.push '.git/info/refs'
+      refs.push '.git/HEAD'
+      console.log "Here are the refs!"
+      console.dir refs
+      async.eachLimit refs, 1, writeObj, cb
+    ], (err) ->
+      throw err if err
+      db.close (err) ->
+        console.error "Couldn't close database connection, #{err}" if err
+        console.log "Disconnected from mongo!"


fix cb

fix cb


Don't strip first char of path

Don't strip first char of path

sysPath.join already takes care of removing extra slashes

--- src/index.coffee
+++ src/index.coffee
@@ -74,7 +74,7 @@
 					path
 			.map (path) ->
 				if path[0] is '/' or not parent
-					sysPath.join rootPath, path[1..]
+					sysPath.join rootPath, path
 				else
 					sysPath.join parent, path


First (untested) pass at playing the game

--- src/index.coffee
+++ src/index.coffee
@@ -0,0 +1,84 @@
+###
+# The algorithm goes like so:
+# 1. Create an array of squences.
+#   1. Index 0 is the first column
+#   2. Index 4 is the last column
+#   3. Index 5 is the first row
+#   4. Index 9 is the last row
+#   5. Index 10 is the top-left to bottom-right diagonal
+#   6. Index 11 is the top-right to bottom-left diagonal
+# 2. Zero out the values at each sequence index. This represents how many 'numbers' in
+#    this sequence have been seen
+# 3. Build up a mapping of number -> [array of sequences this number is in]
+#   1. The array can contain duplicates (for when there are multiples of the
+#      same number in a sequence)
+# 4. When a number is received
+#   1. If that number is contained in the mapping
+#     1. Loop through the mapped sequences and increment the value by 1 for each.
+#     2. If the value is equal to 5, trigger 'win'
+#     3. Remove that number from the mapping
+###
+
+BOARD_WIDTH = 5
+BOARD_HEIGHT = 5
+TL_BR_DIAGONAL_INDEX = BOARD_WIDTH + BOARD_HEIGHT
+TR_BL_DIAGONAL_INDEX = BOARD_WIDTH + BOARD_HEIGHT + 1
+
+io = Require('socket.io')
+_ = Require 'underscore'
+
+client = io.connect 'ws://yahoobingo.herokuapp.com'
+
+sequences = undefined # The possible bingo sequences
+numbers = undefined # A mapping of my card numbers to an array of which possible 'bingo' sequences
+tryAgain = false
+
+client.on 'connect', ->
+
+  client.emit 'register',
+    name: "Jess Telford"
+    email: "jess+yahoobingo@jes.st"
+    url: "https://github.com/jesstelford/yahoo-bingo-node"
+
+client.on 'card', (payload) ->
+
+  sequences = (0 for i in [0..(BOARD_WIDTH + BOARD_HEIGHT + 1)])
+  numbers = {}
+
+  colIndex = 0
+  _(payload.slots).each (columnOfNumbers) ->
+
+    rowIndex = 0
+    _(columnOfNumbers).each (number) ->
+      numbers[number] ?= []
+      numbers[number].push colIndex
+      numbers[number].push rowIndex
+      numbers[number].push TL_BR_DIAGONAL_INDEX if colIndex is rowIndex # We're on the top-left to bottomr-right diagonal
+      numbers[number].push TR_BL_DIAGONAL_INDEX if colIndex is BOARD_WIDTH - rowIndex # We're on the top-left to bottomr-right diagonal
+
+      rowIndex++
+
+    colIndex++
+
+client.on 'number', (number) ->
+  _(numbers[number]).each (sequenceIndex) ->
+    return unless sequences[sequenceIndex]?
+    sequences[sequenceIndex]++
+    if sequences[sequenceIndex] >= BOARD_WIDTH
+      client.emit 'bingo' # WIN condition
+      sequences[sequenceIndex] = undefined
+
+client.on 'win', (message) ->
+  console.log "WIN!", message
+  tryAgain = false # We won, no need to try again
+
+client.on 'lose', (message) ->
+  console.log "LOSE!", message
+  tryAgain = true # We want to try win another one
+
+client.on 'disconnect', ->
+  if tryAgain
+    client = io.connect 'ws://yahoobingo.herokuapp.com'
+    tryAgain = false
+  else
+    # TODO: Shut down the server somehow


Change size of the window

Change size of the window

--- src/index.coffee
+++ src/index.coffee
@@ -36,7 +36,7 @@
   resizable: false
   center: true
   width: 800
-  height: 400
+  height: 450
   title: "LP-Tracker"
 mainWindow.webContents.on 'will-navigate', (event) ->
   event.preventDefault()


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -35,14 +35,14 @@
       console.log "curentMeeting found : ", currentMeeting
       meetingStartTime = _.get event, 'genisys.currentMeeting.startTime'
       noShowLimit = moment(meetingStartTime).utc().add(@limitMinutes, 'minute')
-      if (moment().isBefore(noShowLimit) && moment(@validUntil).isBefore(moment()))
-        @validUntil = noShowLimit
-        console.log "Initializing valid until to no show limit as : #{@validUntil.toISOString()}"
+      if (moment().isBefore(noShowLimit) && moment(@getValidUntil()).isBefore(moment()))
+        @updateValidUntil
+        console.log "Initializing valid until to no show limit as : #{@getValidUntil().toISOString()}"

-      if (moment(@validUntil).isBefore(moment().utc()) && moment().utc().isAfter(noShowLimit))
+      if (moment(@getValidUntil()).isBefore(moment().utc()) && moment().utc().isAfter(noShowLimit))
         meetingId = _.get currentMeeting, 'meetingId'
         console.log "====================================="
-        console.log "Ending meeting, validUntil: ", moment(@validUntil).toISOString()
+        console.log "Ending meeting, validUntil: ", moment(@getValidUntil()).toISOString()
         console.log "Ending meeting, current time: ", moment().toISOString()
         console.log "meetingStartTime: #{meetingStartTime} and No show limit :#{noShowLimit.toISOString()}" if moment().utc().isAfter(noShowLimit)
         @endMeeting meetingId
@@ -76,6 +76,10 @@
   updateValidUntil: () =>
     @validUtil = moment().add(@limitMinutes, 'minutes').utc()
     console.log 'updateValidUntil Valid Until : ', @validUtil.toISOString()
+    return
+
+  getValidUntil: () =>
+    return @validUtil

   isOnline: (callback) =>
     callback null, running: true


bug fix: state and scheduleState attributes missing for class EdimaxSmartPlug, refactoring

--- src/index.coffee
+++ src/index.coffee
@@ -155,7 +155,7 @@
           is called after 'interval' milliseconds. Repeated call will
           remove any previous schedule.
           @param {Function} func - update function to be called
-          @param {Number} interval - interval in milliseconds
+          @param {Number} interval - interval in milliseconds greater than 0
           @param [...] - additional parameters which are passed through
                          to the function specified by func once the
                          timer expires
@@ -163,6 +163,8 @@
         scheduleUpdate: (func, interval) ->
           members.cancelUpdate()

+          if (typeof func is 'undefined')
+            throw new Error "Missing function parameter"
           if interval > 0
             members.debug "Next Request in #{interval} ms"
             args = Array.prototype.splice.call arguments, 2
@@ -171,6 +173,8 @@
               func.apply device, args
             , interval
             )
+          else
+            throw new Error 'Missing or invalid interval parameter'

         ###
           Normalize a given value to match the given lowerRange and


improve trakless performance

improve trakless performance

--- src/index.coffee
+++ src/index.coffee
@@ -186,12 +186,12 @@
         if !self.uuid
             self.uuid = uuid()

-        if self.store?
-          self.store.get('trakless-uuid').then (id) ->
-            if !id
+          if self.store?
+            self.store.get('trakless-uuid').then (id) ->
+              if !id
                 self.store.set('trakless-uuid', self.uuid)
-            self.uuid = id or self.uuid
-            self._trackit(myData, pixel)
+              self.uuid = id or self.uuid
+              self._trackit(myData, pixel)
         else
           self._trackit(myData, pixel)
       @ # chaining


Also dynamically set contrast for scaler

--- src/index.coffee
+++ src/index.coffee
@@ -12,6 +12,7 @@

   bg = chroma(bg)
   fg = chroma(bg)
+

   # If the bg color is in the lighter side of the spectrum
   if chroma(bg).luminance() > 0.5
@@ -33,7 +34,7 @@
       # we should change to avoid osscilating back and forth over our
       # desired constraints.
       contrast = chroma.contrast(bg, fg)
-      scaler = (6-contrast)/7
+      scaler = (options.contrast - contrast)/(options.contrast + 1)
       debug "scaler:", scaler

       # Constraints:
@@ -62,7 +63,7 @@
       debug "cycle:", cycleCount

       contrast = chroma.contrast(bg, fg)
-      scaler = (6-contrast)/7
+      scaler = (options.contrast - contrast)/(options.contrast + 1)
       debug "scaler:", scaler

       if fg.luminance() < 0.98


Fixed code casing for require

Fixed code casing for require

--- src/index.coffee
+++ src/index.coffee
@@ -1,15 +1,15 @@
 ( ( factory ) ->
     if typeof exports is "object"
         module.exports = factory(
-            require "madLib-console"
+            require "madlib-console"
             require "q"
-            require "madLib-object-utils"
+            require "madlib-object-utils"
         )
     else if typeof define is "function" and define.amd
         define( [
-            "madLib-console"
+            "madlib-console"
             "q"
-            "madLib-object-utils"
+            "madlib-object-utils"
         ], factory )

 )( ( console, Q, objectUtils ) ->


Avoid throwing null pointer exceptions when package update fails

Also, send assertion failures back to bugsnag when this happens.

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -467,10 +467,20 @@

   update: ->
     return unless @newVersion or @newSha
-    @packageManager.update @installablePack ? @pack, @newVersion, (error) =>
+    pack = @installablePack ? @pack
+    version = if @newVersion then "v#{@newVersion}" else "##{@newSha.substr(0, 8)}"
+    @packageManager.update pack, @newVersion, (error) =>
       if error?
-        version = if @newVersion then "v#{newVersion}" else "##{@newSha.substr(0, 8)}"
-        console.error("Updating #{@type} #{@pack.name} to #{version} failed", error.stack ? error, error.stderr)
+        atom.assert false, "Package update failed", (assertionError) =>
+          assertionError.metadata = {
+            type: @type,
+            name: pack.name,
+            version: version,
+            errorMessage: error.message,
+            errorStack: error.stack,
+            errorStderr: error.stderr
+          }
+        console.error("Updating #{@type} #{pack.name} to #{version} failed:\n", error, error.stderr ? '')

   uninstall: ->
     @packageManager.uninstall @pack, (error) =>