Bunch of modifications, removed string prototype extension

--- src/esy.coffee
+++ src/esy.coffee
@@ -13,20 +13,32 @@
       @log property

   log: (str) ->
-    $.write "#{str?.toString()}\n"
+    try $.write "#{str?.toString()}\n"

-  dump: (obj) ->
+  dump: (obj, prefix = true) ->
     if obj
-      $.write "\n Dumping: \n --- \n"
+      $.write "\n Dumping: \n --- \n" if prefix
       for propertyName, propertyValue of obj
-        log "#{propertyName}: #{propertyValue?.toString()} \n"
+        if typeof propertyValue is "object"
+          @dump propertyValue, false
+        else
+          @log "#{propertyName}: #{propertyValue?.toString()} \n"

 class Esy.file

+  root: (File($.fileName).parent).path
+
   delete: (filepath) ->
     file = File filepath
     file.remove()
+
+  append: (filepath, content) ->
+    file = File filepath
+    file.open "a"
+    file.write content
+    file.close()
+    return file

   create: (filepath, content = "") ->
     file = new File filepath
@@ -36,14 +48,15 @@
     return file

   read: (filepath) ->
-    file = new File filepath
+    file = File filepath
     file.open "r"
     content = file.read()
     file.close()
     return content

   filename: (filepath) ->
-    return filepath.substr filepath.lastIndexOf('/') + 1
+    filename = filepath.substr filepath.lastIndexOf('/') + 1
+    return filename

 class Esy.ui

@@ -91,8 +104,4 @@

     return reply.substr(reply.indexOf("\r\n\r\n") + 4)

-String::capitalize = ->
-  @replace /(?:^|\s)\S/g, (a) ->
-    a.toUpperCase()
-
 esy = new Esy()


Added storeKeyPress method to Game class

--- game.coffee
+++ game.coffee
@@ -83,6 +83,8 @@
     render_game: ->
       @canvas.renderAll()

+    storeKeyPress: (keyCode) ->
+      @key = keyCode

     timer_tick: ->
       new_time = SynchronizedTime.getTime()


Giving up on the dots.

Giving up on the dots.

--- source/assets/app/javascripts/base.js.coffee
+++ source/assets/app/javascripts/base.js.coffee
@@ -5,6 +5,7 @@
   po = org.polymaps

   radius = 10
+  tips = {}

   map = po.map()
   .container(document.getElementById("map").appendChild(po.svg("svg")))
@@ -17,13 +18,10 @@
   map.add(po.image()
   .url(po.url("http://{S}tile.cloudmade.com/1a1b06b230af4efdbb989ea99e9841af/998/256/{Z}/{X}/{Y}.png")
   .hosts(["a.", "b.", "c.", ""])))
-
-  map.add(po.compass()
-  .pan("none"))
-
+
   map.add(po.geoJson()
-    .on("load", load)
-    .on("show", show)
+    #.on("load", load)
+    #.on("show", show)
     .features([
       {
         "id": "stanford",
@@ -47,10 +45,11 @@
       }
     ]))

-
+  map.add(po.compass()
+  .pan("none"))

-  tips = {}
+

   load = (e) ->
     for f in e.features


Revert "Response file with 304 Not Modified"

This reverts commit e58338c5a2c099013500b170d577d78060c99097.

--- lib/zen.response.coffee
+++ lib/zen.response.coffee
@@ -56,29 +56,25 @@
     @run JSON.stringify(data, null, 0), code, "application/json", headers, audit

   # -- STATIC files ------------------------------------------------------------
-  file: (url, maxage = 60, if_modified = null) ->
+  file: (url, maxage = 60, last_modified = null) ->
     is_valid = false
     if fs.existsSync(url) and stat = fs.statSync(url)
       is_valid = true if stat?.isFile()

     if is_valid
       mime_type = CONST.MIME[path.extname(url)?.slice(1)] or CONST.MIME.html
-      last_modified = fs.statSync(url).mtime
-      if if_modified? and __time(last_modified) is __time(if_modified)
-        @run undefined, 304, mime_type
+      headers =
+        "Content-Type"  : mime_type
+        "Content-Length": stat.size
+        "Cache-Control" : "max-age=#{maxage.toString()}"
+        "Last-Modified" : last_modified
+      if mime_type.match(/audio|video/)?
+        @writeHead 200, headers
+        readableStream = fs.createReadStream url
+        readableStream.pipe @
+        __output @request, 200, mime_type
       else
-        headers =
-          "Content-Type"  : mime_type
-          "Content-Length": stat.size
-          "Cache-Control" : "max-age=#{maxage.toString()}"
-          "Last-Modified" : last_modified
-        if mime_type.match(/audio|video/)?
-          @writeHead 200, headers
-          readableStream = fs.createReadStream url
-          readableStream.pipe @
-          __output @request, 200, mime_type
-        else
-          fs.readFile url, (error, result) => @run result, 200, mime_type, headers
+        fs.readFile url, (error, result) => @run result, 200, mime_type, headers
     else
       @page "404", undefined, undefined, 404

@@ -143,6 +139,3 @@
       type  : type
       ms    : latence
       size  : body?.length
-
-__time = (value) -> (new Date(value)).getTime()
-


Update lib/zombie/forms.coffee

Update lib/zombie/forms.coffee

document is undefined

--- lib/zombie/forms.coffee
+++ lib/zombie/forms.coffee
@@ -193,7 +193,7 @@

 # The element in focus.
 HTML.HTMLDocument.prototype.__defineGetter__ "activeElement", ->
-  return document._focused
+  @parentWindow.document._focused

 # Change the current element in focus
 focus = (document, element)->


use new API

use new API

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -14,7 +14,7 @@

 	class M
 		debug: 	false
-
+
 		@CLS: 			'mailfred'
 		#@CLS_NAV: 		M.CLS + '-nav'
 		@CLS_THREAD: 	M.CLS + '-thread'
@@ -56,7 +56,7 @@

 		constructor: ->
 			window.addEventListener "message", @messageListener, false
-
+
 			@initSettings()

 			# Get the service URL
@@ -120,9 +120,6 @@
 				return
 			return

-		@isAuthorisationErrorPage: (contents) ->
-			/https?:\/\/www\.google\.com\/accounts\/OAuthAuthorizeToken/i.test contents
-
 		@isAuthorisationErrorResponse: (resp) ->
 			resp?.toLowerCase().indexOf "authorization" isnt -1

@@ -133,14 +130,13 @@
 				url: 			url
 				dataType: 		'json'
 				data:			action: 'status'
-				success:		(data, textStatus, jqXHR) =>
+				success:		(data, textStatus, jqXHR) ->
 									log '...user is still authorised'
 									resp true
 									return
-				error:			(jqXHR, textStatus, errorThrown) =>
-									isError = M.isAuthorisationErrorPage jqXHR.responseText
-									log '...user is not authorised' if isError
-									resp !isError
+				error:			(jqXHR, textStatus, errorThrown) ->
+									log '...user is not authorised'
+									resp false
 									return

 		firstInstall: (version) ->
@@ -189,7 +185,7 @@
 								message =
 									from: 'MAILFRED'
 									type: 'debug.enable'
-								window.postMessage message, "*"
+								window.postMessage message, "*"

 						when 'viewThread'
 						# User moves to previous or next convo
@@ -407,7 +403,7 @@
 			timeSection.append new GMailUI.Separator
 			timeSection.append new GMailUI.PopupLabel __msg 'menuTime'

-
+
 			#unless _.isEmpty @lastUsed
 			#	lastUsedSection = timeSection.append new GMailUI.Section

@@ -429,7 +425,7 @@
 			#		return
 			#	lastUsedSection.append new GMailUI.Separator

-
+
 			#presetItem = timeSection.append (new GMailUI.PopupMenuItem presetMenu, (__msg 'menuTimePresetCloseFuture'), 	'', '',	true)

 			# Presets
@@ -466,7 +462,6 @@
 			entry =
 				key: key
 				time: time
-
 			lastUsed = _.reject @lastUsed, (item) -> _.isEqual item, entry

 			lastUsed.unshift entry
@@ -511,14 +506,14 @@
 						other = new Date
 						other.setMonth (now.getMonth() + month)
 						@_specified other
-
+
 		getMessageId: ->
 			if @isPreviewPaneEnabled()
 				id = @selectedConversationId
-			else
+			else
 				id = /\/([0-9a-f]{16})/.exec window.location.hash
 				id = id?[1]
-
+
 			if id is null
 				throw __msg 'errorNotWithinAConversation'
 			id
@@ -534,17 +529,14 @@

 			loadingIcon?() unless archive

-			data =
-				action:		'schedule'
-				messageId:	messageId
+			data =
+				msgId:	messageId
 				when:		props.when
-				unread:		!!props.unread
-				star:		!!props.star
-				noanswer:	!!props.noanswer
-				inbox:		!!props.inbox
-				archive:	archive
-				email:		@currentGmail
-				#callback:	'alert'
+				markUnread:		!!props.unread
+				starIt:		!!props.star
+				onlyIfNoAnswer:	!!props.noanswer
+				moveToInbox:		!!props.inbox
+				archiveAfterScheduling:	archive

 			# remove false values to transmit less data over the wire
 			_.each data, (val, key) ->
@@ -552,9 +544,10 @@
 				return

 			log 'scheduling mail...', data
-
+
 			$.ajax
 				url: 			@getServiceURL()
+				type: 		'POST'
 				dataType: 		'json'
 				data:			data
 				success:		(resp, textStatus, jqXHR) =>
@@ -565,7 +558,7 @@
 									@onScheduleError textStatus, data, errorThrown, jqXHR.responseText
 									return
 				complete:		(jqXHR, textStatus) ->
-									resetIcon?() unless data.archive
+									resetIcon?() unless data.archiveAfterScheduling
 									return

 			if archive
@@ -607,7 +600,7 @@
 		welcome: ->
 			extName = __msg 'extName'
 			[dialog, okButton, cancelButton, container, footer] = @createDialog (__msg 'welcomeDialogTitle', extName), [(__msg 'welcomeDialogButtonOk'), (__msg 'welcomeDialogButtonOkTooltip')], [(__msg 'welcomeDialogButtonCancel'), (__msg 'welcomeDialogButtonCancelTooltip', extName)]
-
+
 			container.append	"""

@@ -652,7 +645,7 @@

 		gettingStarted: (params) ->
 			[dialog, okButton, cancelButton, container, footer] = @gettingStartedDialog()
-
+
 			okButton.on 'click', =>
 				@openAuthWindow params
 				dialog.close()
@@ -691,4 +684,4 @@
 	mb = new M
 	return

-) jQuery, window if top.document is document+) jQuery, window if top.document is document


dev URL

dev URL

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -6,6 +6,7 @@

 	class MailButler
 		debug: true
+		dev: true
 		port: null
 		@MB_CLASS: 'mailbutler'
 		@MB_CLASS_NAV: MailButler.MB_CLASS + '-nav'
@@ -20,10 +21,10 @@
 		@TYPE_NAV: 'nav'

 		# production URL
-		url: "https://script.google.com/macros/s/AKfycbxf5DLvznehMYEK5u3p9d-f1F_iwIIqs11SCw_loUDogp3iDg/exec"
+		prodUrl: "https://script.google.com/macros/s/AKfycbxf5DLvznehMYEK5u3p9d-f1F_iwIIqs11SCw_loUDogp3iDg/exec"

 		# dev URL
-		# url: "https://script.google.com/a/macros/feth.com/s/AKfycbztqUX2xb2_w4NnlsaUP_f5sdLl8h9Fsc5AORb9Pg/dev"
+		devUrl: "https://script.google.com/a/macros/feth.com/s/AKfycbztqUX2xb2_w4NnlsaUP_f5sdLl8h9Fsc5AORb9Pg/dev"

 		constructor: ->
 			window.addEventListener "message", @gmailrListener, false
@@ -41,7 +42,8 @@
 								@injectThread()
 			return

-		getServiceURL: -> @url
+		getServiceURL: ->
+			if @dev then @devUrl else @prodUrl

 		injectCompose: ->
 			navs = ($ ".dW.E[role=navigation] > .J-Jw").filter (index) ->


remove lingering console output

remove lingering console output

--- bin/op/input-file.coffee
+++ bin/op/input-file.coffee
@@ -33,7 +33,7 @@
 checkDefaultLocation = (op, info) ->
   # generate the default location based on the basedir
   inputFile = join op.options.basedir, 'node_modules', 'browserify.js'
-  console.log 'default location:',inputFile
+
   # if it exists then put it in the op
   if fs.existsSync inputFile then info.inputFileName = op.inputFile = inputFile

@@ -131,7 +131,7 @@
   return

 module.exports = (op, info) ->
-  console.log 'input file:',op.inputFile
+
   unless op.inputFile? then checkDefaultLocation op, info

   unless op.inputFile? then generateInput op, info


Don't check installed languages on Windows CI

--- spec/keyboard-layout-spec.coffee
+++ spec/keyboard-layout-spec.coffee
@@ -28,7 +28,10 @@
     it "returns an array of string keyboard languages", ->
       languages = KeyboardLayout.getInstalledKeyboardLanguages()
       expect(Array.isArray(languages)).toBe true
-      expect(languages.length).toBeGreaterThan 0
+
+      # Keyboard languages do not appear to be available on AppVeyor
+      unless process.platform is 'win32' and process.env.CI
+        expect(languages.length).toBeGreaterThan 0

       for language in languages
         expect(typeof language).toBe 'string'


resin sync: add --skip-gitignore option. Improve help section wording

--- lib/actions/sync.coffee
+++ lib/actions/sync.coffee
@@ -61,13 +61,12 @@
 			ignore:
 				- .git
 				- node_modules/
-			progress: true
-			verbose: false

-		Notice that explicitly passed command options override the ones set in the configuration file.
+		Command line options have precedence over the ones saved in '.resin-sync.yml'.

-		Also, if '.gitignore' is found in the source directory then all explicitly listed files will be
-		excluded from the syncing process.
+		If '.gitignore' is found in the source directory then all explicitly listed files will be
+		excluded from the syncing process. You can choose to change this default behavior with the
+		'--skip-gitignore' option.

 		Examples:

@@ -95,20 +94,24 @@
 			description: 'comma delimited paths to ignore when syncing'
 			alias: 'i'
 		,
+			signature: 'skip-gitignore'
+			boolean: true
+			description: 'do not parse excluded/included files from .gitignore'
+		,
 			signature: 'before'
 			parameter: 'command'
 			description: 'execute a command before syncing'
 			alias: 'b'
 		,
+			signature: 'port'
+			parameter: 'port'
+			description: 'ssh port'
+			alias: 't'
+		,
 			signature: 'progress'
 			boolean: true
 			description: 'show progress'
 			alias: 'p'
-		,
-			signature: 'port'
-			parameter: 'port'
-			description: 'ssh port'
-			alias: 't'
 		,
 			signature: 'verbose'
 			boolean: true


resin sync: remove quotes from --source/--destination help as it implies they're required

Closes #372

--- lib/actions/sync.coffee
+++ lib/actions/sync.coffee
@@ -71,9 +71,9 @@

 		Examples:

-			$ resin sync 7cf02a6 --source '.' --destination '/usr/src/app'
-			$ resin sync 7cf02a6 -s '/home/user/myResinProject' -d '/usr/src/app' --before 'echo Hello' --after 'echo Done'
-			$ resin sync --ignore 'lib/'
+			$ resin sync 7cf02a6 --source . --destination /usr/src/app
+			$ resin sync 7cf02a6 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
+			$ resin sync --ignore lib/
 			$ resin sync --verbose false
 			$ resin sync
 	'''


:construction: Try another test setup for failing tests

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -279,7 +279,8 @@
           atom.config.set 'minimap.scrollAnimation', false
           canvas = minimapElement.canvas
           mousedown(canvas)
-          nextAnimationFrame()
+          waitsFor -> nextAnimationFrame isnt noAnimationFrame
+          runs -> nextAnimationFrame()

         it 'scrolls the editor to the line below the mouse', ->
           expect(editor.getScrollTop()).toEqual(360)
@@ -291,12 +292,13 @@
           atom.config.set 'minimap.scrollAnimation', true
           canvas = minimapElement.canvas
           mousedown(canvas)
+          waitsFor -> nextAnimationFrame isnt noAnimationFrame

         it 'scrolls the editor gradually to the line below the mouse', ->
           # wait until all animations run out
           waitsFor ->
             nextAnimationFrame()
-            return nextAnimationFrame == noAnimationFrame
+            return nextAnimationFrame is noAnimationFrame

           runs ->
             expect(editor.getScrollTop()).toEqual(360)


Guard against snippet not existing

Guard against snippet not existing

--- spec/snippet-loading-spec.coffee
+++ spec/snippet-loading-spec.coffee
@@ -144,7 +144,7 @@
         """

         waitsFor "snippets to be changed", ->
-          atom.config.get('snippets.foo', scope: ['.foo']).body is 'bar2'
+          atom.config.get('snippets.foo', scope: ['.foo'])?.body is 'bar2'

         runs ->
           fs.writeFileSync path.join(configDirPath, 'snippets.cson'), ""


more tests for corner cases

more tests for corner cases

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1282,6 +1282,18 @@
         f = (x, y, z) -> x + y + z
         Bacon.combineWith(f, stream, Bacon.constant(10), 100)
       [111, 112])
+  describe "works with single input", ->
+    expectPropertyEvents(
+      ->
+        stream = series(1, [1, 2])
+        f = (x) -> x * 2
+        Bacon.combineWith(f, stream)
+      [2, 4])
+  describe "works with 0 inputs (results to a constant)", ->
+    expectPropertyEvents(
+      ->
+        Bacon.combineWith(-> 1)
+      [1])

 describe "Boolean logic", ->
   describe "combines Properties with and()", ->


:shit:

:shit:

--- spec/atom/vim-mode-spec.coffee
+++ spec/atom/vim-mode-spec.coffee
@@ -1,7 +1,7 @@
 Editor = require 'editor'
 VimMode = require 'vim-mode'

-fdescribe "VimMode", ->
+describe "VimMode", ->
   editor = null

   beforeEach ->


add failing test

add failing test

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -954,6 +954,13 @@
         valve = series(2, [true, false, true, false]).delay(1).toProperty()
         src.holdWhen(valve)
       [[2, 1], [5, 2], [6, 3], [9, 4]])
+  describe "Holds forever when the property ends with truthy value", ->
+    th.expectStreamTimings(
+      ->
+        src = series(2, [1,2,3,4])
+        valve = series(2, [true, false, true]).delay(1).toProperty()
+        src.holdWhen(valve)
+      [[2, 1], [5, 2], [6, 3]])
   it "toString", ->
     expect(Bacon.once(1).holdWhen(Bacon.constant(true)).toString()).to.equal(
       "Bacon.once(1).holdWhen(Bacon.constant(true))")


:green_heart: Change the waitsFor condition in recurrent failing test

--- spec/color-project-spec.coffee
+++ spec/color-project-spec.coffee
@@ -493,7 +493,7 @@
           project.onDidUpdateVariables(spy)
           project.setIgnoredNames(['vendor/*', '**/*.styl'])

-          waitsFor -> spy.callCount > 0
+          waitsFor -> project.getVariables().length < 12

         it 'clears all the variables as there is no legible paths', ->
           expect(project.getPaths().length).toEqual(0)


improve test

improve test

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -2755,8 +2755,8 @@
       ["cc"])
   describe "accepts constants instead of functions too", ->
     expectStreamEvents(
-      -> Bacon.when(Bacon.once(1), 2)
-      [2])
+      -> Bacon.when(Bacon.once(1), 2, Bacon.once(2), 3)
+      [2, 3])
   describe "works with synchronous sources", ->
     expectStreamEvents(
       ->


Expect an error when image is 1x1

Expect an error when image is 1x1

--- spec/GetColors.coffee
+++ spec/GetColors.coffee
@@ -148,12 +148,11 @@
         ins.send canvas
         ins.endGroup()
   describe 'when given a small image', ->
-    it 'should output no colors', (done) ->
+    it 'should return an error', (done) ->
       input = '1x1.gif'
       id = null
-      colors.once "data", (colors) ->
-        chai.expect(colors).to.be.an 'array'
-        chai.expect(colors).to.have.length 0
+      error.on "data", (err) ->
+        chai.expect(err).to.be.an 'object'
         done()
       id = getCanvasWithImage input, (canvas) ->
         ins.send canvas


Ensure we're using proper dom ids in specs

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -3,7 +3,7 @@
 path = require 'path'
 ThemeManager = require '../src/theme-manager'

-describe "the `atom` global", ->
+fdescribe "the `atom` global", ->
   beforeEach ->
     window.rootView = new RootView

@@ -200,6 +200,11 @@
               one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css")
               two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less")
               three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css")
+
+              one = atom.themes.stringToId(one)
+              two = atom.themes.stringToId(two)
+              three = atom.themes.stringToId(three)
+
               expect(atom.themes.stylesheetElementForId(one)).not.toExist()
               expect(atom.themes.stylesheetElementForId(two)).not.toExist()
               expect(atom.themes.stylesheetElementForId(three)).not.toExist()
@@ -216,6 +221,12 @@
               one = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/1.css")
               two = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/2.less")
               three = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/3.css")
+
+
+              one = atom.themes.stringToId(one)
+              two = atom.themes.stringToId(two)
+              three = atom.themes.stringToId(three)
+
               expect(atom.themes.stylesheetElementForId(one)).not.toExist()
               expect(atom.themes.stylesheetElementForId(two)).not.toExist()
               expect(atom.themes.stylesheetElementForId(three)).not.toExist()


add test for contiguous newlines

add test for contiguous newlines

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -277,6 +277,19 @@
           runs ->
             expect(issueTitle).not.toContain "\n"
             expect(issueTitle).toBe "Uncaught Error: Cannot read property 'object' of undefinedTypeError: Cannot read property 'objec..."
+
+      describe "when the message contains continguous newlines", ->
+        it "removes the newlines when generating the issue title", ->
+          message = "Uncaught Error: Cannot do the thing\n\nSuper sorry about this"
+          atom.notifications.addFatalError(message)
+          notificationContainer = workspaceElement.querySelector('atom-notifications')
+          fatalError = notificationContainer.querySelector('atom-notification.fatal')
+
+          waitsForPromise ->
+            fatalError.getRenderPromise().then ->
+              issueTitle = fatalError.issue.getIssueTitle()
+          runs ->
+            expect(issueTitle).toBe "Uncaught Error: Cannot do the thingSuper sorry about this"

       describe "when there are multiple packages in the stack trace", ->
         beforeEach ->


fix spec

fix spec

--- spec/sync-settings-spec.coffee
+++ spec/sync-settings-spec.coffee
@@ -252,9 +252,9 @@
             SyncSettings.restore cb
           , ->
             for file in atom.config.get 'sync-settings.extraFiles'
-              expect(fs.existsSync("#{atom.config.configDirPath}/#{file}")).toBe(true)
-              expect(SyncSettings.fileContent("#{atom.config.configDirPath}/#{file}")).toBe("# #{file} (not found) ")
-              fs.unlink "#{atom.config.configDirPath}/#{file}"
+              expect(fs.existsSync("#{atom.getConfigDirPath()}/#{file}")).toBe(true)
+              expect(SyncSettings.fileContent("#{atom.getConfigDirPath()}/#{file}")).toBe("# #{file} (not found) ")
+              fs.unlink "#{atom.getConfigDirPath()}/#{file}"

     describe "::check for update", ->


wrong mathjax path https://github.com/readium/readium-js-viewer/issues/378

--- package/package_scripts_dist_cloudReaderLite.cson
+++ package/package_scripts_dist_cloudReaderLite.cson
@@ -1,3 +1,4 @@
+

 #
 # See package.cson for more information
@@ -51,7 +52,7 @@
         &&
         npm run cpy
             node_modules/MathJax-grunt-concatenator/MathJax.js
-            dist/chrome-app/scripts/mathjax
+            dist/cloud-reader-lite/scripts/mathjax
         &&
         npm run ncp
             node_modules/bootstrap/fonts/


Clean up Compass benchmark

Clean up Compass benchmark

--- benchmark/compass.coffee
+++ benchmark/compass.coffee
@@ -3,7 +3,6 @@

 module.exports = (css, callback) ->
   path = __dirname + '/compass'
-  file = path + '/sass/test.scss'

   css = css.replace(/\stransform: ([^;]+)/g, ' @include transform($1)').
             replace(/\stransition: ([^;]+)/g, ' @include transition($1)').
@@ -11,7 +10,9 @@
                     ' @include background($2)').
             replace(/\sbox-sizing: ([^;]+)/g, '@include box-sizing($1)')
   css = "@import 'compass/css3';\n" + css
-  fs.writeFileSync(file, css)
+
+  fs.mkdirpSync(path + '/sass')
+  fs.writeFileSync(path + '/sass/test.scss', css)

   start = new Date()
   exec "cd #{path}; bundle exec compass compile", (error, stdout, stderr) ->
@@ -20,7 +21,7 @@
     process.stderr.write(stderr)
     process.exit(1) if error

-    fs.unlinkSync(file)
-    fs.unlinkSync(path + '/stylesheets/test.css')
+    fs.removeSync(path + '/sass')
+    fs.removeSync(path + '/stylesheets')
     fs.removeSync(path + '/.sass-cache')
     callback(now - start)


:construction: Clear text editor markers on restart after state reset

--- lib/color-buffer.coffee
+++ lib/color-buffer.coffee
@@ -25,6 +25,12 @@

     if variableMarkers? and colorMarkers?
       @restoreMarkersState(variableMarkers, colorMarkers)
+    else
+      # FIXME this is only needed because some markers can persist in the
+      # text editor when running atom without pigments running.
+      # As pigments state won't persist but markers will, we end up with a bunch
+      # of unused markers that will never get destroy.
+      @cleanUpTextEditorMarkers()

     @initialize()

@@ -70,6 +76,10 @@
         color
         text: state.text
       }
+
+  cleanUpTextEditorMarkers: ->
+    @editor.findMarkers(type: 'pigments-color').forEach (m) -> m.destroy()
+    @editor.findMarkers(type: 'pigments-variable').forEach (m) -> m.destroy()

   variablesAvailable: ->
     return @variablesPromise if @variablesPromise?


change the overlay to right

change the overlay to right

--- views/titlesList.cjsx
+++ views/titlesList.cjsx
@@ -80,7 +80,7 @@
           if @state.showData?
             if @state.showData.titles? and @state.showData.titles isnt []
               for title, index in @state.showData.titles
-                <OverlayTrigger key={index} placement='bottom' overlay={
+                <OverlayTrigger key={index} placement='right' overlay={
                   <Popover>

{title}


todo touch rotate

todo touch rotate

--- src/canvas.coffee
+++ src/canvas.coffee
@@ -239,6 +239,7 @@

       if e.touches.length == 2
         touchZoom e.touches
+        # touchRotate e.touches # TODO!
       else
         drawDuringDrag e.touches[0]
     , false


Update chosen.coffee

Update chosen.coffee

--- src/chosen.coffee
+++ src/chosen.coffee
@@ -50,7 +50,10 @@

       # Init chosen on the next loop so ng-options can populate the select
       $timeout -> element.chosen options
-
+
+      #Watch the underlying ng-model for updates and trigger an update when they occur.
+      ctrl.$render = -> $timeout -> element.trigger('chosen:updated')
+
       # Watch the collection in ngOptions and update chosen when it changes.  This works with promises!
       if attr.ngOptions
         match = attr.ngOptions.match(NG_OPTIONS_REGEXP)
@@ -63,6 +66,4 @@
           unless newVal is oldVal
             stopLoading()
             disableWithMessage(options.no_results_text || 'No values available') if isEmpty(newVal)
-        #Watch the underlying ng-model for updates and trigger an update when they occur.
-        ctrl.$render = -> $timeout -> element.trigger('chosen:updated')
 ]


Status and presence methods

Status and presence methods

--- src/client.coffee
+++ src/client.coffee
@@ -160,6 +160,36 @@
     @_apiCall 'groups.create', params, @_onCreateGroup

   _onCreateGroup: (data) =>
+    console.log data
+
+  setPresence: (presence) ->
+    if presence is not 'away' and presence is not 'active' then return null
+
+    params = {
+      "presence": presence
+    }
+
+    @_apiCall 'presence.set', params, @_onSetPresence
+
+  _onSetPresence: (data) =>
+    console.log data
+
+  setActive: ->
+    params = {}
+
+    @_apiCall 'users.setActive', params, @_onSetActive
+
+  _onSetActive: (data) =>
+    console.log data
+
+  setStatus: (status) ->
+    params = {
+      "status": status
+    }
+
+    @_apiCall 'status.set', params, @_onSetStatus
+
+  _onSetStatus: (data) =>
     console.log data

   #


fix beer-song tests

fix beer-song tests

--- exercises/beer-song/beer-song.spec.coffee
+++ exercises/beer-song/beer-song.spec.coffee
@@ -1,25 +1,29 @@
 Beer = require './beer-song'
 describe 'Beer', ->
   it 'prints an arbitrary verse', ->
-    expect(Beer.verse 8).toEqual '
+    beer = new Beer()
+    expect(beer.verse 8).toEqual '
     8 bottles of beer on the wall, 8 bottles of beer.
     Take one down and pass it around, 7 bottles of beer on the wall.
     '

   xit 'handles 1 bottle', ->
-    expect(Beer.verse 1).toEqual '
+    beer = new Beer()
+    expect(beer.verse 1).toEqual '
     1 bottle of beer on the wall, 1 bottle of beer.
     Take it down and pass it around, no more bottles of beer on the wall.
     '

   xit 'handles 0 bottles', ->
-    expect(Beer.verse 0).toEqual '
+    beer = new Beer()
+    expect(beer.verse 0).toEqual '
     No more bottles of beer on the wall, no more bottles of beer.
     Go to the store and buy some more, 99 bottles of beer on the wall.
     '

   xit 'sings several verses', ->
-    expect(Beer.sing 8, 6).toEqual '
+    beer = new Beer()
+    expect(beer.sing 8, 6).toEqual '
     8 bottles of beer on the wall, 8 bottles of beer.
     Take one down and pass it around, 7 bottles of beer on the wall.

@@ -31,7 +35,8 @@
     '

   xit 'sings the rest of the verses', ->
-    expect(Beer.sing 3).toEqual '
+    beer = new Beer()
+    expect(beer.sing 3).toEqual '
     3 bottles of beer on the wall, 3 bottles of beer.
     Take one down and pass it around, 2 bottles of beer on the wall.


Wait for the device type compatibility check before showing the form

Also now fetches the device type from the image/API only once.

Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- lib/actions/os.coffee
+++ lib/actions/os.coffee
@@ -147,23 +147,28 @@
 			console.info('The image was downloaded successfully')
 		.nodeify(done)

-buildConfig = (image, deviceType, advanced = false) ->
-	Promise = require('bluebird')
+buildConfigForDeviceType = (deviceType, advanced = false) ->
 	form = require('resin-cli-form')
 	helpers = require('../utils/helpers')

-	Promise.resolve(helpers.getManifest(image, deviceType))
-	.get('options')
-	.then (questions) ->
-		if not advanced
-			advancedGroup = _.find questions,
-				name: 'advanced'
-				isGroup: true
-
-			if advancedGroup?
-				override = helpers.getGroupDefaults(advancedGroup)
-
-		return form.run(questions, { override })
+	questions = deviceType.options
+	if not advanced
+		advancedGroup = _.find questions,
+			name: 'advanced'
+			isGroup: true
+
+		if advancedGroup?
+			override = helpers.getGroupDefaults(advancedGroup)
+
+	return form.run(questions, { override })
+
+buildConfig = (image, deviceTypeSlug, advanced = false) ->
+	Promise = require('bluebird')
+	helpers = require('../utils/helpers')
+
+	Promise.resolve(helpers.getManifest(image, deviceTypeSlug))
+	.then (deviceTypeManifest) ->
+		buildConfigForDeviceType(deviceTypeManifest, advanced)

 exports.buildConfig =
 	signature: 'os build-config <image> <device-type>'
@@ -305,7 +310,9 @@
 				if options.config
 					return readFileAsync(options.config, 'utf8')
 						.then(JSON.parse)
-				return buildConfig(params.image, deviceType, options.advanced)
+				return manifestPromise.then (deviceTypeManifest) ->
+					buildConfigForDeviceType(deviceTypeManifest, options.advanced)
+
 			Promise.join answersPromise, manifestPromise, (answers, manifest) ->
 				answers.version = options.version


Refactor the code

Refactor the code

Make the args matching function a lot more explicit that
"yo these are where your matchers do their thing"

--- lib/args-match.coffee
+++ lib/args-match.coffee
@@ -3,12 +3,22 @@
 module.exports = (expectedArgs, actualArgs) ->
   return true if _.eq(expectedArgs, actualArgs)
   return false if expectedArgs.length != actualArgs.length
+  satisfiesEqualityPlusAnyArgumentMatchers(expectedArgs, actualArgs)

+satisfiesEqualityPlusAnyArgumentMatchers = (expectedArgs, actualArgs) ->
   _.eq expectedArgs, actualArgs, (expected, actual) ->
     _.all expectedArgs, (expectedArg, i) ->
-      return true if _.eq(expectedArg, actualArgs[i])
-      if _.isFunction(expectedArg?.__matches)
-        expectedArg.__matches(actualArgs[i])
-      else
-        false
+      argumentMatchesExpectation(expectedArg, actualArgs[i])

+argumentMatchesExpectation = (expectedArg, actualArg) ->
+  if _.eq(expectedArg, actualArg)
+    true
+  else if matcher = matcherHidingInExpectedArgument(expectedArg)
+    matcher(actualArg)
+  else
+    false
+
+# This is literally all that's needed to implement argument matchers & captors
+matcherHidingInExpectedArgument = (expectedArg) ->
+  if _.isFunction(expectedArg?.__matches)
+    expectedArg.__matches


Prevent opening empty file if file path doesn't already exist. Fix for Windows file paths by using node's path module.

--- lib/atom-utils.coffee
+++ lib/atom-utils.coffee
@@ -1,4 +1,6 @@
 TagGenerator = require 'symbols-view/lib/tag-generator'
+fs           = require 'fs'
+path         = require 'path'

 ABSOLUTE_PATH_PATTERN = '^(\\w+(?:/\\w+)+)(?:.*?)?$'
 RELATIVE_PATH_PATTERN = '^((?:..?/)+(?:\\w+/)*\\w+)(?:.*?)?$'
@@ -14,6 +16,7 @@
     cursor: null
   paths:
     currentFile: {}
+    root: null

 module.exports = AtomUtils =
   options: {}
@@ -63,11 +66,13 @@
     return cachedPath or filePath

   getAbsoluteCurrentFileRoot: ->
-    return @getRelativeCurrentFilePath()[0]
+    rootPath = @getRelativeCurrentFilePath()[0]
+    @cache.paths.root = rootPath
+    return rootPath

   getRelativeCurrentFileRoot: ->
     fileRoot = @getRelativeCurrentFilePath()[1]
-    return fileRoot.substring(0, fileRoot.lastIndexOf('/'))
+    return path.dirname(fileRoot)

   getProjectName: (currentFileRoot) ->
     return @readPackageJson(currentFileRoot).then (contents) => @cacheProjectName currentFileRoot, @extractProjectName(contents)
@@ -189,7 +194,7 @@
     filePath

   buildRelativePath: (filePath) ->
-    @getRelativeCurrentFileRoot() + '/' + @appendJSExtension filePath
+    path.join @getRelativeCurrentFileRoot(), @appendJSExtension filePath

   overridePaths: (filePath) ->
     finalPath    = filePath
@@ -206,10 +211,12 @@

     _replacePath = (target) =>
       return if pathReplaced
+
       replaced = target.replace '$PROJECT', @currentProjectName

       if filePath.indexOf(replaced) > -1
-        finalPath = filePath.replace replaced, replacements[target]
+        finalPath    = filePath.replace replaced, replacements[target]
+        pathReplaced = true

     unless @options.disablePathOverrides
       _extractTargetFrom target for target in @options.pathOverrides
@@ -239,11 +246,11 @@
   openFile: (filePath, fileMethod) ->
     finalPath = @buildFinalFilePath filePath
     return unless finalPath
-    @openEditor finalPath, fileMethod
+    fullSystemPath = path.join @cache.paths.root, finalPath
+    fs.exists(fullSystemPath, (exists) => if exists then @openEditor(finalPath, fileMethod))

   openEditor: (filePath, fileMethod) ->
     openedEditor = atom.workspace.open filePath
-
     return unless openedEditor

     openedEditor.then (editor) =>


config: Add code for serializing

config: Add code for serializing

Needed by last commit

--- src/config.coffee
+++ src/config.coffee
@@ -38,6 +38,9 @@
   o = yaml.safeLoad str
   o = {} if not o
   return o
+
+serialize = (parsed) ->
+  return yaml.safeDump parsed

 configFormat = () ->
   varFormat =
@@ -143,4 +146,5 @@

 exports.parse = parseConfig
 exports.parseOnly = parse
+exports.serialize = serialize
 exports.defaults = addDefaults


Remove game signals

Remove game signals

--- src/config.coffee
+++ src/config.coffee
@@ -29,10 +29,6 @@
     right: {name: "->", do: "turn", dir: -1}
     forward: {name: "^", do: "go", dir: 1}
     reverse: {name: "v", do: "go", dir: -1}
-    step: {name: ">", do: "step", n: 1}
-    run: {name: ">>", do: "run", speed: 1}
-    stop: {name: "||", do: "run", speed: 0}
-    reset: {name: "|<", do: "reset"}
   }
   _CHILDREN: [
     game


Clean up main module header stuff.

Clean up main module header stuff.

--- lib/dart-tools.coffee
+++ lib/dart-tools.coffee
@@ -2,13 +2,7 @@
 AutoCompletePlusProvider = require './autocomplete/provider'

 module.exports =
-  # spooky ( ͡° ͜ʖ ͡°)
-  analysisComponent: null
-
-  pubComponent: null
-  pubStatusView: null
-
-
+
   # Wizardry
   config:
     automaticPubGet:


refactor unflood improvements

refactor unflood improvements

--- src/irc.coffee
+++ src/irc.coffee
@@ -96,12 +96,16 @@
       throw new Error("HUBOT_IRC_SERVER is not defined: try: export HUBOT_IRC_SERVER='irc.myserver.com'")

   unfloodProtection: (unflood) ->
-    if unflood? and (unflood == 'true' or !isNaN(parseInt(unflood))) then true else false
+    unflood == 'true' or !isNaN(parseInt(unflood))

   unfloodProtectionDelay: (unflood) ->
-    switch @unfloodProtection(unflood)
-      when true then (if !isNaN(parseInt(unflood)) then parseInt(unflood) else 1000)
-      when false then 0
+    unfloodProtection = @unfloodProtection(unflood)
+    unfloodValue = parseInt(unflood) or 1000
+
+    if unfloodProtection
+      unfloodValue
+    else
+      0

   run: ->
     self = @


Adding comments to demo

Adding comments to demo

--- Demo.framer/app.coffee
+++ Demo.framer/app.coffee
@@ -1,3 +1,5 @@
+## Dynamic Layout Demo
+
 ## Import Module
 ios = require 'iOSKit'
 whiteBG = new BackgroundLayer backgroundColor:"white"
@@ -8,7 +10,7 @@

 button = new ios.Button
 	constraints:
-		bottom:10 # This constraint fixes the button to the bottom
+		bottom:10 # This fixes the button to 10 pts from the bottom
 	buttonType:"big"
 	text:"I agree, sloths are cool"

@@ -17,19 +19,20 @@

 h1 = new ios.Text
 	constraints:
-		top:40
-		leading:20
-		trailing:20
+		top:40 # This fixes the heading line to 40 pts from the top of the ScrollComponent
+		leading:20 # This sets the leading edge to 20 pts from the left side
+		trailing:20 # This sets the trailing edge to 20 pts from the right
+	# Because the leading & trailing were set, the layer's width will be adjusted to honor both constraints
 	fontSize:32
 	fontWeight:200
 	lineHeight:50
-	text:"Sloth are cool, right?"
+	text:"Sloths are cool, right?"

 slothImage = new Layer
 	image:"http://media.mnn.com/assets/images/2015/02/sloth.jpg"

 slothImage.constraints =
-	top:[h1, 40]
+	top:[h1, 40] # This will place the image 40 pts below the h1
 	leading:0
 	trailing:0
 	height:200
@@ -48,20 +51,23 @@
 	text:"1) Sloths are arboreal animals. This means that they live or spend their days mainly on trees. They only come down if it is time for them to urinate or defecate, which only happens once a week.

 2) It was once thought that sloths spend about 15 to 20 hours a day sleeping. It was recently discovered, however, that sloths actually only sleep around 10 hours a day.

 3) They are vulnerable to predation because they urinate and defecate at the same spot each time. There have been a lot of speculations regarding this behavior. Some says it is because it is easier for them to find one another especially on mating seasons. It was also observed that they defecate near the trunk of the tree, probably to fertilize the tree, which is their source of food. "

 button.on Events.TouchEnd, ->
+
 	sheet = new ios.Sheet actions:["Completely agree", "Totally agree"], animated:true, description:"Choose your agreement"
+
 	sheet.actions["Completely agree"].on Events.TouchEnd, ->
 		alert = new ios.Alert title:"Nice!", message:"👍"
 		alert.on Events.TouchEnd, ->
 			alert.destroy()
+
 	sheet.actions["Totally agree"].on Events.TouchEnd, ->
 		alert = new ios.Alert title:"Sweet!", message:"🙌"
 		alert.on Events.TouchEnd, ->
 			alert.destroy()

-
+
 scroll.constraints =
-	top:status
-	bottom:button
+	top:status # This fixes the scroll component to the bottom of the StatusBar
+	bottom:button #This fixes the

 scroll.content.addSubLayer(h1)
 scroll.content.addSubLayer(slothImage)


Use more uniqueIds

Use more uniqueIds

--- workers/social/lib/social/models/emailnotification.coffee
+++ workers/social/lib/social/models/emailnotification.coffee
@@ -116,7 +116,7 @@
       if not err and count is 0
         notification = new JEmailNotificationGG {
           event, sender, receiver, eventFlag, contentId, activity, \
-          unsubscribeId: getUniqueId()
+          unsubscribeId: getUniqueId()+getUniqueId()+''
         }
         # console.log "OK good to go."
         notification.save (err)->


Pass canvas to Player class

Pass canvas to Player class

--- game.coffee
+++ game.coffee
@@ -37,10 +37,10 @@

     start: ->
-      @player0 = new Player("Player0", new Position([Game.WIDTH/2, 0], Game.SOUTH, 0))
-      @player1 = new Player("Player1", new Position([Game.WIDTH, Game.HEIGHT/2], Game.WEST, 0))
-      @player2 = new Player("Player2", new Position([Game.WIDTH/2, Game.HEIGHT], Game.NORTH, 0))
-      @player3 = new Player("Player3", new Position([0, Game.HEIGHT/2], Game.EAST, 0))
+      @player0 = new Player("Player0", new Position([Game.WIDTH/2, 0], Game.SOUTH, 0), @canvas)
+      @player1 = new Player("Player1", new Position([Game.WIDTH, Game.HEIGHT/2], Game.WEST, 0), @canvas)
+      @player2 = new Player("Player2", new Position([Game.WIDTH/2, Game.HEIGHT], Game.NORTH, 0), @canvas)
+      @player3 = new Player("Player3", new Position([0, Game.HEIGHT/2], Game.EAST, 0), @canvas)

       @players = [@player0, @player1, @player2, @player3]


Adjusted quadrant scoring

Adjusted quadrant scoring

--- game.coffee
+++ game.coffee
@@ -155,8 +155,8 @@
     # Return a player index for the play who owns the current sector
     {X, Y} = position
     switch
-      when X < 0 and Y >= 0 then 0
-      when X >= 0 and Y >= 0 then 1
+      when X >= 0 and Y >= 0 then 0
+      when X < 0 and Y >= 0 then 1
       when X < 0 and Y < 0 then 2
       when X >= 0 and Y < 0 then 3


bump version to 1.1.4

bump version to 1.1.4

--- src/sc.coffee
+++ src/sc.coffee
@@ -1,5 +1,5 @@
 window.SC = SC.Helper.merge SC || {},
-  _version: "1.1.3"
+  _version: "1.1.4"
   _baseUrl: "//connect.soundcloud.com"
   options:
     site: "soundcloud.com"


Set upper case hex at qhm.coffee

Set upper case hex at qhm.coffee

--- scripts/qhm.coffee
+++ scripts/qhm.coffee
@@ -15,7 +15,7 @@
   stringToHex = (msg) ->
     string = msg.match[1]
     hex = conv string, {in: "utf8", out: "hex"}
-    msg.send hex
+    msg.send hex.toUpperCase()

   hexToString = (msg) ->
     username = msg.envelope.user.name
@@ -70,5 +70,7 @@

   robot.respond /qhm:edit(?: (.+))?/i, editPage

+  robot.
+
   robot.brain.on "loaded", ->
     robot.brain.data.qhm = robot.brain.data.qhm or {}


Say - remove undefined variable

Say - remove undefined variable

--- scripts/say.coffee
+++ scripts/say.coffee
@@ -12,4 +12,4 @@
       [room, message] = msg.match[1..2]
       robot.messageRoom room, message
     else
-      msg.reply "Denied because you #{msg.message.user.name} are not of list #{whitelistedUsers}"
+      msg.reply "Denied because you #{msg.message.user.name} are not in the access list."


Deactivate and unload lang packages after use

--- spec/syntax-spec.coffee
+++ spec/syntax-spec.coffee
@@ -4,7 +4,6 @@

 describe "the `syntax` global", ->
   beforeEach ->
-
     waitsForPromise ->
       atom.packages.activatePackage('language-text')

@@ -16,6 +15,19 @@

     waitsForPromise ->
       atom.packages.activatePackage('language-ruby')
+
+  afterEach ->
+    atom.packages.deactivatePackage('language-text')
+    atom.packages.unloadPackage('language-text')
+
+    atom.packages.deactivatePackage('language-javascript')
+    atom.packages.unloadPackage('language-javascript')
+
+    atom.packages.deactivatePackage('language-coffee-script')
+    atom.packages.unloadPackage('language-coffee-script')
+
+    atom.packages.deactivatePackage('language-ruby')
+    atom.packages.unloadPackage('language-ruby')

   describe "serialization", ->
     it "remembers grammar overrides by path", ->


:green_heart: Fix race condition on window-spec

...caused by an afterEach block which was called before the SUT.

--- spec/window-spec.coffee
+++ spec/window-spec.coffee
@@ -285,19 +285,35 @@
       it "adds it to the project's paths", ->
         pathToOpen = __filename
         atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
-        expect(atom.project.getPaths()[0]).toBe __dirname
+
+        waitsFor ->
+          atom.project.getPaths().length is 1
+
+        runs ->
+          expect(atom.project.getPaths()[0]).toBe __dirname

     describe "when the opened path does not exist but its parent directory does", ->
       it "adds the parent directory to the project paths", ->
         pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
         atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
-        expect(atom.project.getPaths()[0]).toBe __dirname
+
+        waitsFor ->
+          atom.project.getPaths().length is 1
+
+        runs ->
+          expect(atom.project.getPaths()[0]).toBe __dirname

     describe "when the opened path is a file", ->
       it "opens it in the workspace", ->
         pathToOpen = __filename
         atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
-        expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
+
+        waitsFor ->
+          atom.workspace.open.callCount is 1
+
+        runs ->
+          expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
+

     describe "when the opened path is a directory", ->
       it "does not open it in the workspace", ->


Pass absolute paths to requireStylesheet

--- spec/window-spec.coffee
+++ spec/window-spec.coffee
@@ -117,9 +117,9 @@
       $('head style[id*="sample.less"]').remove()

     it "supports requiring css and less stylesheets without an explicit extension", ->
-      requireStylesheet 'fixtures/css'
+      requireStylesheet path.join(__dirname, 'fixtures', 'css')
       expect($('head style[id*="css.css"]').attr('id')).toBe project.resolve('css.css')
-      requireStylesheet 'fixtures/sample'
+      requireStylesheet path.join(__dirname, 'fixtures', 'sample')
       expect($('head style[id*="sample.less"]').attr('id')).toBe project.resolve('sample.less')

       $('head style[id*="css.css"]').remove()
@@ -127,7 +127,7 @@

   describe ".removeStylesheet(path)", ->
     it "removes styling applied by given stylesheet path", ->
-      cssPath = require.resolve(path.join("fixtures", "css.css"))
+      cssPath = require.resolve('./fixtures/css.css')

       expect($(document.body).css('font-weight')).not.toBe("bold")
       requireStylesheet(cssPath)


initial text change event implementation

--- src/editor.coffee
+++ src/editor.coffee
@@ -9,6 +9,12 @@
     this.enable()
     @delta = this.getDelta()
     @observer = new MutationObserver(this.update.bind(this))
+    @observer.observe(@domNode,
+      attributes: true
+      characterData: true
+      childList: true
+      subtree: true
+    )

   deleteAt: (index, length) ->
     [first, firstOffset] = @children.find(index)
@@ -47,8 +53,13 @@
       child.remove()
     )

-  update: ->
-    return new Delta()
+  update: (mutations = @observer.takeRecords()) ->
+    return new Delta() unless mutations.length > 0
+    oldDelta = @delta
+    this.build()
+    @delta = this.getDelta()
+    change = oldDelta.diff(@delta)
+    this.onUpdate(change) if change.length() > 0

 module.exports = Editor


Cleanup.

Cleanup.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -20,9 +20,8 @@
     initialize: (serializeState) ->
         atom.workspaceView.command "jumpy:toggle", => @toggle()
         atom.workspaceView.command "jumpy:clear", => @clear()
-        that = this
         for c in characters
-            atom.workspaceView.command "jumpy:#{c}", (c) -> that.getKey(c)
+            atom.workspaceView.command "jumpy:#{c}", (c) => @getKey(c)
         # TODO: consider moving this into toggle for new bindings.
         @backedUpKeyBindings = _.clone(atom.keymap.keyBindings)


Fixes issue with view saving labels.

Fixes issue with view saving labels.

- Adds some logging.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -11,7 +11,7 @@
 module.exports =
 class JumpyView extends View
   @content: ->
-    @div '', class: 'jumpy label'
+    @div ''

   initialize: (serializeState) ->
     @pixels = @getAllPixelLocations()
@@ -39,8 +39,8 @@
   clearJumpMode: ->
       @clearKeys()
       $('#status-bar-jumpy').html("")
+      atom.workspaceView.find('.jumpy').remove()
       atom.workspaceView.eachEditorView (e) -> e.removeClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
-      atom.workspaceView.find('.jumpy').remove()
       @detach()

   jump: ->
@@ -64,21 +64,29 @@
       nearestCursor =
           left: nearestMultiple(labelLocation.left, cursor.clientWidth)
           top: nearestMultiple(labelLocation.bottom - labelLocation.height, cursor.clientHeight)
-      console.log nearestCursor.left, nearestCursor.top
+      console.log nearestCursor.left, nearestCursor.top, cursor.clientHeight, labelLocation, labelElement

       lines = atom.workspaceView.find('.lines')
       offsetTop = lines.get(0).offsetTop
+      console.log offsetTop
       #offsetLeft = lines.get(0).offsetLeft
-      offsetLeft = 0 # TODO: this needs to be replace with scroll to the right info.
+      offsetLeft = 0 # TODO: this needs to be replaced with scroll to the right info.
       scrollViewOffset = $('.editor .scroll-view').offset()
       for line, lineIndex in @pixels
           line = _.compact line
           for char, charIndex in line
               #console.log lineIndex, charIndex, char
               isAtLeft = (nearestCursor.left ==
-                  char.left + scrollViewOffset.left + offsetLeft)
+                  char.left + scrollViewOffset.left - offsetLeft)
               isAtTop = (nearestCursor.top ==
-                  char.top + scrollViewOffset.top + offsetTop)
+                  char.top + scrollViewOffset.top - offsetTop)
+                  #char.top + scrollViewOffset.top - 42 - offsetTop)
+
+              if lineIndex == @pixels.length - 2 && charIndex == 0
+                  # TODO: handle changing y coord after 2nd run...found class="cc aa"!
+                  # TODO: missing offset
+                  console.log "Last one:", lineIndex, charIndex, char.left, char.top, isAtLeft, isAtTop
+
               if isAtLeft && isAtTop
                   return [lineIndex, charIndex]

@@ -99,17 +107,16 @@

   # Tear down any state and detach
   destroy: ->
+    console.log 'Jumpy: "destroy" called. Detaching.'
+    @clearJumpMode()
     @detach()

   toggle: ->
-    if @hasParent()
-      @detach()
-
     $('#status-bar-jumpy').html("Jumpy: Jump Mode!")
     atom.workspaceView.eachEditorView (e) -> e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'

     relevantClasses = ['variable', 'keyword', 'method', 'string.quoted']
-    atom.workspaceView.find((".line .source .#{c}" for c in relevantClasses).join()).prepend(this)
+    atom.workspaceView.find((".line .source .#{c}" for c in relevantClasses).join()).prepend('

')

     nextKeys = _.clone keys


Removing webWorkerURL from the global namespace

--- src/p3d.coffee
+++ src/p3d.coffee
@@ -90,15 +90,16 @@
 workerReturnedKeys = ['normals', 'vertices', 'indices', 'nOfTriangles', 'blob']
 if !isWorker
   webWorkerFn = arguments.callee
+  _webWorkerURL = undefined
   # Getting a blob url reference to this script's closure
-  webWorkerURL = =>
-    return @webWorkerURL if @webWorkerURL?
+  webWorkerURL = ->
+    return _webWorkerURL if _webWorkerURL?
     # Removing the closure from the worker's js because it caused syntax issues in chrome 24
     str = webWorkerFn.toString()
     str = str.replace(/^\s*function\s*\(\) {/, "").replace(/}\s*$/, '')

     webWorkerBlob = new Blob [str], type: "text/javascript"
-    @webWorkerURL = (window.URL || window.webkitURL).createObjectURL webWorkerBlob
+    _webWorkerURL = (window.URL || window.webkitURL).createObjectURL webWorkerBlob
 else
   parserPipeline = null
   data = null


Don't need to shim 'events'

Don't need to shim 'events'

--- src/events.coffee
+++ src/events.coffee
@@ -1,23 +0,0 @@
-class module.exports.EventEmitter
-  handlersFor: (event) ->
-    (@handlers ?= {})[event] ?= []
-
-  on: (event, handler) ->
-    @handlersFor(event).push handler
-
-  once: (event, handler) ->
-    outerHandler = (args...) =>
-      @removeListener(event, outerHandler)
-      handler(args...)
-    @on(event, outerHandler)
-
-  removeListener: (event, handler) ->
-    list = @handlersFor(event)
-    i = list.indexOf(handler)
-    if i != -1
-      delete list[i]
-
-  emit: (event, args...) ->
-    for handler in @handlersFor(event)
-      handler(args...)
-


Calculate selection direction by comparing the boundary points of the previous and current ranges

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -246,3 +246,10 @@
     result = originalTop is getRectTop()
     window.scrollBy(0, -1)
     result
+
+  getDirectionFromDOMRanges = (range, previousRange) ->
+    return unless range? and previousRange?
+    if range.compareBoundaryPoints(Range.START_TO_START, previousRange) is -1
+      "backward"
+    else if range.compareBoundaryPoints(Range.END_TO_END, previousRange) is 1
+      "forward"


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
 velocity so that it will approach another object.

-# follow
+# follow relies
 player = GameObject
   x: 50
   y: 50


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -53,7 +53,7 @@

   enemy.follow(player)

-  # => The enemy no
+  # => The enemy now has it's d


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,6 +1,6 @@
 ###*
 The Follow module provides a simple method to set an object's
-so that it will
+so that it will approach d

 # Health regeneration


TEMP: BehaviorTree: Set .continuation on Choice, not Tree

Fixes infinite recursion in previous testcase

--- lib/BehaviorTree.coffee
+++ lib/BehaviorTree.coffee
@@ -36,7 +36,7 @@
   onSubtree: (choice, name, continuation, callback) =>
     tree = new BehaviorTree name, @options
     tree.parentOnBranch = choice.parentOnBranch or @parentOnBranch
-    tree.continuation = continuation
+    choice.continuation = continuation
     t = new Thenable tree
     choice.subtrees = [] unless choice.subtrees
     choice.subtrees.push tree


console modules

console modules

--- lib/ui/console.coffee
+++ lib/ui/console.coffee
@@ -5,9 +5,13 @@
 notifications = require './notifications'
 views =         require './views'

+{evalrepl} = {}
+
 module.exports =
   activate: (@client) ->
     @create()
+
+    {evalrepl} = client.import 'evalrepl'

     @subs = new CompositeDisposable

@@ -55,7 +59,7 @@
       @client.boot()
       @c.logInput()
       @c.done()
-      @client.rpc('evalrepl', code: editor.getText(), mode: mode?.name)
+      evalrepl(code: editor.getText(), mode: mode?.name, mod: @c.juliaModule)
         .then (result) =>
           @c.input()
           notifications.show "Evaluation Finished"


debug iteration ifo

debug iteration ifo

--- graphics/001-terrain-gen/generator.coffee
+++ graphics/001-terrain-gen/generator.coffee
@@ -8,15 +8,18 @@
   'cs!/wahlque/universe/planet/terrain'
 ], (exports, terrain) ->
     handle = 0
+    counter = 0
     start = () ->
         seeds = terrain.seeds()
         evolve = ->
             seeds = terrain.gen(seeds)
+            counter = counter + 1
             #if seeds[0] == 4096
             #    self.postMessage({msg: 'done!'})
             #    clearInterval(handle)
             #else
             #    self.postMessage({trn: seeds})
+            self.postMessage({msg: counter})
             self.postMessage({trn: seeds})
         handle = setInterval(evolve, 5000)


openModal and closeModal implementations

--- src/bridges/TizenBridge.coffee
+++ src/bridges/TizenBridge.coffee
@@ -39,6 +39,10 @@
         window.open(message.parameters.url)
       when "popLayer"
         window.close()
+      when "openModal"
+        window.open(message.parameters.url)
+      when "closeModal"
+        window.close()
       else
         console.log "TizenBridge: unsupported API method: #{message.method}"
         failed = true


refactoring HoleManager.resetGoals

refactoring HoleManager.resetGoals

--- lib/hole/manager.coffee
+++ lib/hole/manager.coffee
@@ -44,8 +44,7 @@
     markers = @agda.editor.findMarkers type: 'hole'
     markers.map (marker) => marker.destroy()

-  resetGoals: (goalIndices) ->
-    @agda.saveCursor()
+  convertHoles: (goalIndices) ->
     @destroyHoles()
     text = @agda.editor.getText()
     # make hole {! !}
@@ -73,6 +72,12 @@
     @agda.editor.setText text
     @agda.emit 'hole-manager:buffer-modified'

+    return text
+
+  resetGoals: (goalIndices) ->
+    @agda.saveCursor()
+    text = @convertHoles goalIndices
+
     # get positions of all holes
     headIndices = @indicesOf text, /\{!/
     tailIndices = @indicesOf text, /!\}/


support middleware in all handlers

support middleware in all handlers

--- src/wx.coffee
+++ src/wx.coffee
@@ -65,6 +65,14 @@
   click_handlers = {}
   scan_handlers  = {}
   text_handlers  = []
+  @image_handlers = []
+  @voice_handlers = []
+  @video_handlers = []
+  @location_handlers = []
+  @link_handlers = []
+  @subscribe_handlers = []
+  @unsubscribe_handlers = []
+  @templatesendjobfinish_handlers = []

   # ### 二维码参数
   #
@@ -130,7 +138,9 @@
           # 3. `桌面`回掉：模拟成桌面端注册回调方法。
           mobile  = reply req, message.id
           desktop_callback = -> res.send [message.msg_id].concat Array::slice.call arguments
-          scan_handler req, mobile, desktop_callback
+          async.eachSeries scan_handler, (handler, callback) ->
+            handler req, mobile, desktop_callback, callback
+          , ->

         # 否则（有桌面端监听扫码结果，服务器端未定义如何处理时），
         # （默认）向桌面端发送扫码用户。
@@ -431,7 +441,9 @@
                     # 3. `桌面`回掉：模拟成桌面端注册回调方法。
                     mobile  = res
                     desktop_callback = -> redis_client.publish 'WX:SEND:DESKTOP', JSON.stringify id: id, content: [message.msg_id].concat Array::slice.call(arguments)
-                    scan_handler req, mobile, desktop_callback
+                    async.eachSeries scan_handler, (handler, callback) ->
+                      handler req, mobile, desktop_callback, callback
+                    , ->

                 # 未注册永久二维码处理流程时：
                 # + 响应微信`200`
@@ -457,8 +469,9 @@
                 # 将二维码的查询参数增补至请求对象查询参数中，在当前进程内处理。
                 else if scan_handler = scan_handlers[name]
                   _(req.query).extend(query)
-                  scan_handler(req, res, ->)
-
+                  async.eachSeries scan_handler, (handler, callback) ->
+                    handler req, res, callback
+                  , ->
                 # 未注册处理句柄时，向微信端响应`OK`。
                 else res.ok()

@@ -496,8 +509,10 @@

             # 收到图片、语音、视频、地理位置、链接时，如注册了处理句柄，使用该句柄处理：
             when 'image', 'voice', 'video', 'location', 'link'
-              if handler = @["#{msg_type}_handler"] then handler req, res
-              else res.ok()
+              async.eachSeries @["#{msg_type}_handlers"], (handler, callback) ->
+                handler req, res, callback
+              , ->
+                res.ok()

             # 收到事件消息时，判断事件类型：
             when 'event'
@@ -510,9 +525,10 @@
                   # 2. 退化至订阅句柄处理；
                   # 3. 无订阅句柄时发`OK`。
                   subscribe = ->
-                    if @subscribe_handler
-                      @subscribe_handler req, res
-                    else res.ok()
+                    async.eachSeries @subscribe_handlers, (handler, callback) ->
+                      handler req, res, callback
+                    , ->
+                      res.ok()

                   # 收到订阅消息时，如果由扫码产生，并且有对应名称二维码的处理句柄，
                   # 使用二维码处理句柄处理，否则，使用订阅处理句柄处理。
@@ -546,14 +562,17 @@
                 # + 如注册了取消订阅处理句柄，使用该句柄处理；
                 # + 否则响应`200`。
                 when 'unsubscribe'
-                  if @unsubscribe_handler
-                    @unsubscribe_handler req, res
-                  else res.ok()
+                  async.eachSeries @unsubscribe_handler, (handler, callback) ->
+                    handler req, res, callback
+                  , ->
+                    res.ok()

                 # 收到点击按钮消息时，
                 when 'click'
-                  if click_handler = click_handlers[req.event_key]
-                    click_handler(req, res)
+                  if handlers = click_handlers[req.event_key]
+                    async.eachSeries handlers, (handler, callback) ->
+                      handler req, res, callback
+                    , ->
                   else res.ok()

                 # 如为二维码扫描事件，
@@ -561,9 +580,10 @@

                 # 模板消息发送成功时，
                 when 'templatesendjobfinish'
-                  if @template_handler
-                    @template_handler(req, res)
-                  else res.ok()
+                  async.eachSeries @template_handler, (handler, callback) ->
+                    handler req, res, callback
+                  , ->
+                    res.ok()

                 # 尚无法处理的事件，直接响应`200`。
                 else res.ok()
@@ -910,38 +930,70 @@
       @

     # ### 注册图片消息处理句柄
-    image: (@image_handler) => @
+    image: (args...) =>
+      for handler in args
+        @image_handlers.push handler
+      @

     # ### 注册语音消息处理句柄
-    voice: (@voice_handler) => @
+    voice: (args...) =>
+      for handler in args
+        @voice_handlers.push handler
+      @

     # ### 注册视频消息处理句柄
-    video: (@video_handler) => @
+    video: (args...) =>
+      for handler in args
+        @video_handlers.push handler
+      @

     # ### 注册地理位置处理句柄
-    location: (@location_handler) => @
+    location: (args...) =>
+      for handler in args
+        @location_handlers.push handler
+      @

     # ### 注册链接处理句柄
-    link: (@link_handler) => @
+    link: (args...) =>
+      for handler in args
+        @link_handlers.push handler
+      @

     # ### 注册关注与取消关注处理句柄
-    subscribe   : (@subscribe_handler) => @
-    unsubscribe : (@unsubscribe_handler) => @
+    subscribe   :  (args...) =>
+      for handler in args
+        @subscribe_handlers.push handler
+      @
+    unsubscribe : (args...) =>
+      for handler in args
+        @unsubscribe_handlers.push handler
+      @

     # ### 注册模板消息处理句柄
-    templatesendjobfinish: (@templatesendjobfinish_handler) => @
+    templatesendjobfinish: (args...) =>
+      for handler in args
+        @templatesendjobfinish_handlers.push handler
+      @

     # ### 注册点击菜单按钮处理句柄
-    click: (key, handler) =>
-      click_handlers[key] = handler
+    click: (key, args...) =>
+      unless click_handlers[key]
+        click_handlers[key] = []
+      for handler in args
+        click_handlers[key].push handler
       @

     # ### 注册二维码处理句柄
-    scan: (channel, handler) =>
-      if typeof channel is 'function'
-        handler = channel
-        channel = ''
-      scan_handlers[channel.toLowerCase()] = handler
+    scan: (args...) =>
+      switch typeof _.first(args)
+        when 'function'
+          channel = ''
+        when 'string'
+          channel = args.shift().toLowerCase()
+      unless click_handlers[channel]
+        scan_handlers[channel] = []
+      for handler in args
+        scan_handlers[channel].push handler
       @

     # ### 检索用户基本信息


添加打开文件对话框方法

添加打开文件对话框方法

--- lib/utils/util.coffee
+++ lib/utils/util.coffee
@@ -3,6 +3,7 @@
 zlib = require 'zlib'
 fs = require 'fs-extra'
 pathM = require 'path'
+dialog = require('remote').require 'dialog'
 {File,Directory} = require 'atom'
 # client = require './client'
 module.exports = Util =
@@ -160,6 +161,31 @@

   readDir: (path, cb) ->
     fs.readdir path, cb
+
+  openDialog = (options,cb) ->
+    dialog.showOpenDialog options, (destPath) ->
+      cb destPath
+
+  # openDirectory title: 'Select Path', (path) ->
+  #   console.log path
+  openDirectory = (options,cb) ->
+
+    options = _.extend({
+      defaultPath: atom.project.path
+      properties: ['openDirectory']
+      }, options)
+
+    @openDialog(options,cb)
+
+  openFile = (options,cb) ->
+
+    options = _.extend({
+      defaultPath: atom.project.path
+      properties: ['openFile']
+      }, options)
+
+    @openDialog(options,cb)
+

   store: (namespace, data) ->
     if data


Bind editor

Bind editor

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -11,12 +11,13 @@
   # @param content [String] an initial string
   # @param editor [Editor] an editor instance
   # @param author [String] the name of the local author
-  constructor: () ->
+  constructor: (editor) ->
     # TODO: generate a UID (you can get a unique id by calling
     # `@_model.getUid()` - is this what you mean?)
     # @author = author
     # TODO: assign an id / author name to the rich text instance for authorship
-
+    if editor?
+      @bindEditor editor
   #
   # Bind the RichText type to a rich text editor (e.g. quilljs)
   #


Fixes arg name inconsistency

Fixes arg name inconsistency

This was causing stopwatch to fail

--- src/hrtime.coffee
+++ src/hrtime.coffee
@@ -9,7 +9,7 @@
   seconds = Math.floor clockTime
   ns = Math.floor((clockTime % 1) * 1e9)

-  if prevTimestamp
+  if prevTime
     seconds = seconds - prevTime[0]
     ns = ns - prevTime[1]
     if ns < 0


Revert "Merge pull request #82 from radicaled/bugfix/change_update_content_directive"

This reverts commit c1ad300fcd34457a47ce9178a68173ed825bb1ff, reversing
changes made to 5b3482cb200b71bedb4bbbe9232109bd19f19036.

--- lib/analysis_api.coffee
+++ lib/analysis_api.coffee
@@ -32,12 +32,8 @@
     updateFile: (path, contents) =>
       files = {}
       files[path] =
-        type: 'change'
-        edits: [
-          offset: 0
-          length: contents?.length or 0
-          replacement: contents
-        ]
+        type: 'add'
+        content: contents

       @perform 'analysis.updateContent',
         {files}


:lipstick:

:lipstick:

--- lib/autocomplete.coffee
+++ lib/autocomplete.coffee
@@ -7,4 +7,3 @@
     rootView.eachEditor (editor) =>
       if editor.attached and not editor.mini
         @autoCompleteViews.push new AutocompleteView(editor)
-


 Merge branch 'master.imageCacheFix' into 'master'

Master.Image Cache Fix

--- server/lib/server/image_cache.coffee
+++ server/lib/server/image_cache.coffee
@@ -29,8 +29,8 @@
   noExt    = splitUrl.join(".")

   # arbitary limit to prevent ENAMETOOLONG errors
-  if ext.length > 10
-    ext = ext.substring(0, 10)
+  if ext.length > 20
+    ext = ext.substring(0, 20)

   # deal with extensions like:
   #   'com/LbobbpWTGJSa45Mhrb6g_y3YjLn5OthdnugrHZJQqom1eduFCnFmqdmOOZmUttP8hLg=h310'
@@ -40,8 +40,13 @@
   # replace nonalphanumeric characters
   ext = ext.replace(/\W+/g, "")

-  md5      = crypto.createHash("md5").update(noExt).digest("hex")
-  filename = "#{imagePath}/#{md5}.#{ext}"
+  md5 = crypto.createHash("md5")
+  for i in [noExt, width, height]
+    md5.update(i)
+
+  digest = md5.digest("hex")
+
+  filename = "#{imagePath}/#{digest}.#{ext}"

   serveFile = (filename, res)->
     fileStream = fs.createReadStream filename


Added Calamity to Collection and added iteration function

--- src/Collection.coffee
+++ src/Collection.coffee
@@ -1,4 +1,6 @@
 Antifreeze.Collection = class Collection
+	Calamity.emitter @prototype
+
 	constructor: ->
 		@_items = []

@@ -36,6 +38,12 @@
 	size: (obj) ->
 		return @_items.length

+	# Iterator.
+	each: (fn) ->
+		for entry in @_items
+			fn.apply @, [entry]
+		return @
+
 	toJSON: ->
 		json = []
 		for o in @_items


Added logic for setting the date modified field for projects, duplicating projects, and removing project files on removal.

--- src/ProjectUtils.coffee
+++ src/ProjectUtils.coffee
@@ -162,3 +162,60 @@
       @error(e)
       return false unless callback?

+Meteor.startup ->
+  return unless Meteor.isServer
+
+  ##################################################################################################
+  # PROJECT DATE
+  ##################################################################################################
+
+  # Updating project or models in the project will update the modified date of a project.
+
+  getCurrentDate = -> moment().toDate()
+
+  Projects.before.insert (userId, doc) ->
+    unless doc.dateModified
+      doc.dateModified = getCurrentDate()
+      doc.userModified = userId
+
+  Projects.before.update (userId, doc, fieldNames, modifier) ->
+    modifier.$set ?= {}
+    delete modifier.$unset?.dateModified
+    modifier.$set.dateModified = getCurrentDate()
+    modifier.$set.userModified = userId
+
+  _.each _.without(CollectionUtils.getAll(), Projects), (collection) ->
+    _.each ['insert', 'update'], (operation) ->
+      collection.after[operation] (userId, doc) ->
+        projectId = doc[SchemaUtils.projectIdProperty]
+        return unless projectId
+        Projects.update projectId,
+          $set: {dateModified: getCurrentDate(), userModified: userId}
+
+  ##################################################################################################
+  # DUPLICATE
+  ##################################################################################################
+
+  Meteor.methods
+    'projects/duplicate': (id) ->
+      userId = @userId
+      ProjectUtils.assertAuthorization(id, userId)
+      Promises.runSync -> ProjectUtils.duplicate(id).then Meteor.bindEnvironment (idMaps) ->
+        newId = idMaps[Collections.getName(Projects)][id]
+        Projects.update newId, $set: {dateModified: new Date, userModified: userId}
+
+  ##################################################################################################
+  # REMOVAL
+  ##################################################################################################
+
+  Projects.after.remove (userId, doc) ->
+    Logger.info('Cleaning up after removing project', doc)
+    id = doc._id
+    selector = {projectId: id}
+    _.each CollectionUtils.getAll(), (collection) ->
+      count = collection.remove(selector)
+      if count > 0
+        console.log('Removed ' + count + ' ' + Collections.getName(collection))
+    files = Files.find(selector).fetch()
+    Logger.info('Removing files for project', doc, files)
+    Files.remove(selector)


Jump to hash without triggering turbolinks

Behaves differently compared to browser's.

--- resources/assets/coffee/turbolinks-overrides.coffee
+++ resources/assets/coffee/turbolinks-overrides.coffee
@@ -18,3 +18,13 @@

 Turbolinks.BrowserAdapter::showProgressBarAfterDelay = ->
   @progressBarTimeout = Timeout.set 0, @showProgressBar
+
+
+$(document).on 'click', 'a[href^="#"]', (e) ->
+  targetId = e.currentTarget.getAttribute('href')[1..]
+  target = document.getElementById targetId
+
+  return if !target?
+
+  e.preventDefault()
+  $.scrollTo target


Paginator is now using history api #77

Paginator is now using history api #77

--- wzk/ui/grid/base.coffee
+++ wzk/ui/grid/base.coffee
@@ -62,8 +62,14 @@
       query = [query, '&', wzk.uri.addFragmentParam('base', event.target.base)].join('')
       win.location.hash = query

+  # setup history handling
+  wzk.history.historyHandler (historyEvent)->
+    {base, page} = wzk.ui.grid.parseFragment(historyEvent.token)
+    paginator.goToPage(base, page)
+
   watcher = new wzk.ui.grid.FilterWatcher grid, query
   watcher.watchOn table
+
   grid

 ###*


Removing unneeded Canvas reference from Map

--- src/js/Map.coffee
+++ src/js/Map.coffee
@@ -1,9 +1,7 @@
 class Map
-  constructor: (size, canvasID) ->
+  constructor: (size) ->
     ### @constant ###
     @size    =            size
-    ### @constant ###
-    canvasID ?= '#main-canvas'

     ### @constant ###
     # initialize grid


:bug: Prevent error raised when checking a destroyed marker

--- lib/color-marker.coffee
+++ lib/color-marker.coffee
@@ -51,7 +51,7 @@
     out

   checkMarkerScope: (forceEvaluation=false) ->
-    return if @destroyed
+    return if @destroyed or !@colorBuffer?
     range = @marker.getBufferRange()

     try


take out initial attempts to confirm FE's waiting on response from BE before updating component

--- src/components/task/index.cjsx
+++ src/components/task/index.cjsx
@@ -93,8 +93,6 @@
   shouldComponentUpdate: (nextProps, nextState) ->
     {id} = @props

-    console.info(TaskStore.getAsyncStatus(id))
-
     # if a step needs to be recovered, load a recovery step for it
     if @_stepRecoveryQueued(nextState)
       TaskStepActions.loadRecovery(nextState.recoverForStepId)
@@ -118,9 +116,6 @@
     #   redirect to the step after the step we triggered refresh from.
     if @_leavingRefreshingStep(nextState)
       @continueAfterRefreshStep()
-      return false
-
-    if TaskStore.isSaving(id)
       return false

     # if we reach this point, assume that we should go ahead and do a normal component update


Made it so an input without any value in interventions will still be updated when inputting 0 as its value. Related to #981.

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -85,7 +85,8 @@
               update = true
               update = false if value is null and element.val() is ""
               if valueType == "number"
-                update = false if value == element.numericalValue()
+                # When element doesn't have any value, element.numericalValue() == 0
+                update = false if value == element.numericalValue() && element.numericalValue() != 0
               else
                 update = false if value == element.val()
               if update


If at least one of the delegate box is checked, keep the list

--- app/assets/javascripts/components/users/base.js.coffee
+++ app/assets/javascripts/components/users/base.js.coffee
@@ -24,11 +24,16 @@
     )

   @respondentsTable: ->
+    that = @
     $('.delegate-box, .super_delegate-box').on('click', ->
-      if $(@).attr('checked')
+      if $(@).attr('checked') || that.delegateBoxChecked()
         $('.respondents-list').slideDown()
       else
         $('.respondents-list').slideUp()
     )

+  @delegateBoxChecked: ->
+    $('.delegate-box').attr('checked') || $('.super_delegate-box').attr('checked')
+
+
 $(document).ready -> UsersComponent.initialize()


[#5] Require a showMarks prop to make marks visible in View mode


Prevent NProgress use when not defined IE8 bug

Related to #178

--- app/assets/javascripts/neighborly/neighborly.js.coffee
+++ app/assets/javascripts/neighborly/neighborly.js.coffee
@@ -12,8 +12,9 @@
   modules: -> []

   initPage: ->
-    NProgress.configure
-      showSpinner: false
+    unless NProgress is undefined
+      NProgress.configure
+        showSpinner: false
     unless window.Turbolinks is undefined
       $(document).bind "page:fetch", =>
         this.Loading.show()


more awesome features

more awesome features

--- app/assets/javascripts/rails_admin/custom/ui.js.coffee
+++ app/assets/javascripts/rails_admin/custom/ui.js.coffee
@@ -85,33 +85,146 @@
                   console.log response
               )

+
+
+  # APRIL FOOLS SECTION
   # TODO: remove this and all occurences of 'js-april-fools' in rails_admin.rb
-  april_fools_input = $(".js-april-fools input")
-  if april_fools_input.length
-    elem = $("")
-
-    if document.URL.indexOf('website') > 0
-      display = "Der Instant-Website-Checker hat gemeldet, dass die Seite nicht erreichbar ist. Bitte wende dich an die IT falls das Problem öfter auftritt."
-    else if document.URL.indexOf('category') > 0
-      display = "Möchtest du vielleicht direkt eine Kopie für die andere Welt erzeugen? Wende dich für das Feature an die IT."
-    else
-      display = ""
-
-    april_fools_input.on 'blur', (e) ->
-      if display.length
-        $(april_fools_input).after elem
-        elem.html display
-
-  april_fools_area = $(".js-april-fools textarea")
-  if april_fools_area.length
-    elem = $("")
-
-    if document.URL.indexOf('offer') > 0
-      display = "Willst du nicht direkt mit HTML eine eigene Seite in diesem Feld nachbauen? Wende dich an die IT falls du Fragen zu HTML hast."
-    else
-      display = ""
-
-    april_fools_area.on 'blur', (e) ->
-      if display.length
-        $(april_fools_area).after elem
-        elem.html display
+  today = new Date()
+  if today.getDate() is 1 and today.getMonth() is 3
+    # BY KONSTANTIN
+
+    # Titeländerung & Hover Erklärung
+    makeFlash = (message, selector) ->
+      $(selector).append(
+        '
<button class="close" data-dismiss="alert" type="button">x</button>'+message+'
'
+      )
+
+    $('.navbar-header a').html('Claradmin 1.5 KI-Edition').off('click.april-fools').on(
+      'click.april-fools', (e) ->
+        unless $('.navbar-fixed-top .alert').length
+          makeFlash(
+            "Das clarat Entwicklerteam freut sich, euch die erste Teilversion des neuen Backend 2.0 präsentieren zu können. Natürlich befindet sich das Meiste noch in der Konzeption, aber wir haben schon ein paar Features dafür entwickelt. Besonders stolz sind wir auf einen neuen Deep-Learning-Algorithmus, der clarat mit einer grundlegenden KI ausstattet und uns in Zukunft viel Arbeit abnehmen wird!",
+            '.navbar-fixed-top'
+          )
+        e.preventDefault()
+        false
+    )
+
+    # Say please!
+    handleResponse = (handledResponse, e) ->
+      if document.failsafe # in case something goes wrong
+        return true
+      if /(jetzt|flott|sofort|mach)/i.test handledResponse
+        alert("Nein, so nicht!")
+        document.cookie = "pissedOffAI=true;"
+        showApologyAlert()
+        e.preventDefault()
+        return false
+      else if /bitte/i.test handledResponse
+        return true
+      else
+        handleResponse(
+          window.prompt("Nein, das war's nicht. Probier's nochmal."), e
+        )
+
+    $('a').off('click.april-fools-please').on 'click.april-fools-please', (e) ->
+      if e.target.className.includes('navbar-brand')
+        return true
+      if document.cookie.includes "pissedOffAI=true"
+        e.preventDefault()
+        return false
+      else
+        if Math.random() <= 0.1 # 10% chance
+          pleaseQuery = switch Math.floor(Math.random() * 5)
+            when 0
+              "Ständig kommandiert ihr mich nur rum. Sag doch mal bitte!"
+            when 1
+              "Klar kannst du da hin. Wie heißt das Zauberwörtchen?"
+            when 2
+              "Da lass ich dich nur mit dem Zauberwörtchen hin."
+            when 3
+              "Wie sagt man?"
+            else
+              'Immer werd ich nur herumkommandiert… Versuch\'s mal mit nem "bitte".'
+          response = window.prompt pleaseQuery
+          handleResponse response, e
+
+    # Don't piss off the computer
+    $(document).off('click.april-fools-po').on 'click.april-fools-po', (e) ->
+      if document.cookie.includes "pissedOffAI=true"
+        if (e.target.id is 'april-fools-apology--input') or e.target.className.includes('close')
+          return true
+
+        window.scrollTo(0,0)
+        alert switch Math.floor(Math.random() * 3)
+          when 0
+            "So nicht mein Freund. Entschuldige dich erstmal."
+          when 1
+            "Das hättest du wohl gerne. Erst eine Entschuldigung."
+          else
+            "Nicht ohne eine Entschuldigung."
+
+        e.preventDefault()
+        return false
+
+    showApologyAlert = ->
+      $('.page-header').append(
+        '

+          <button class="close" data-dismiss="alert" type="button">x</button>
+          Ich erwarte eine Entschuldigung!
+          <input id="april-fools-apology--input" autofocus="autofocus" />
+
'
+      )
+    if document.cookie.includes("pissedOffAI=true") and not $('#april-fools-apology--input').length
+      showApologyAlert()
+
+    $(document).off('keyup').on 'keyup', '#april-fools-apology--input', (e) ->
+      if document.cookie.includes("pissedOffAI=true")
+        if /(entschuldigung|tschuldige|sorry|tut mir leid)/i.test e.currentTarget.value
+          document.cookie = "pissedOffAI=false;"
+          $('#april-fools-apology').remove()
+          alert('Geht doch.')
+
+
+    # Offer Name
+
+    $('#offer_name').off('blur.april-fools').on 'blur.april-fools', (e) ->
+      if $('#april-fools-name').length
+        $('#april-fools-name').remove()
+      if Math.random() <= 0.3 # 30% chance
+        doubt = switch Math.floor(Math.random() * 3)
+          when 0
+            "Hmm… KI hier. Der Offer-Name sieht aber noch nicht so wirklich peppig aus."
+          when 1
+            "Hmm willst du den Titel wirklick SO lassen? Klingt so lasch…"
+          else
+            "Na ich weiß ja nicht… So richtig fetzt der Name noch nicht."
+
+        $('#offer_name').after(
+          "#{doubt}"
+        )
+
+
+    # BY NILS
+    # K: Achtung, Text wird wiederholt ausgegeben, wenn man vorher herum navigiert hat. Habe es etwas angepasst.
+
+    # Instant Website Checker
+    $('#website_url').off('blur.april-fools').on 'blur.april-fools', (e) ->
+      return if $('#april-fools-website').length
+      $('#website_url').after(
+        "Der Instant-Website-Checker hat gemeldet, dass die Seite nicht erreichbar ist. Bitte wende dich an die IT falls das Problem öfter auftritt."
+      )
+
+    # Category Entry
+    $('#category_name_de').off('blur.april-fools').on 'blur.april-fools', (e) ->
+      return if $('#april-fools-category').length
+      $('#category_name_de').after(
+        "Möchtest du vielleicht direkt eine Kopie für die andere Welt erzeugen? Wende dich für das Feature an die IT."
+      )
+
+    # Opening Specification
+    $('#offer_opening_specification').off('blur.april-fools').on 'blur.april-fools', (e) ->
+      return if $('#april-fools-opening-specification').length
+      $('#offer_opening_specification').after(
+        "Willst du nicht direkt mit HTML eine eigene Seite in diesem Feld nachbauen? Wende dich an die IT falls du Fragen zu HTML hast."
+      )


Fix target in node popup links.

Fix target in node popup links.

--- app/assets/javascripts/views/node-popup.view.js.coffee
+++ app/assets/javascripts/views/node-popup.view.js.coffee
@@ -7,7 +7,7 @@
   ).property()

   linkTarget: (()->
-    if Ember.ENV.EMBEDDED then '_blank' else null
+    if Ember.ENV.WIDGET? then '_blank' else null
   ).property()

 Wheelmap.NodePopupStatusDropdownView = Ember.View.extend


use keypress so we can get the character pressed

--- app/assets/javascripts/views/paper_edit_view.js.coffee
+++ app/assets/javascripts/views/paper_edit_view.js.coffee
@@ -82,8 +82,9 @@
   keyCount: 0

   setupAutosave: ->
-    Ember.$(document).on 'keyup', '.ve-ui-surface, #paper-title', =>
+    Ember.$(document).on 'keypress', '.ve-ui-surface, #paper-title', (e) =>
       @get('controller.model').set('saved', false)
+      # TODO: use e.which to ingore certain characters (like cmd, ctrl)
       @short = Ember.run.debounce(@, @timeoutSave, 1000 * 10)
       unless @long
         @long = Ember.run.later(@, @timeoutSave, 1000 * 60)


OHIE-131 Correct http method

OHIE-131 Correct http method

--- src/koaApi.coffee
+++ src/koaApi.coffee
@@ -122,7 +122,7 @@

   # Metadata endpoints
   app.use route.get '/metadata', metadata.getMetadata
-  app.use route.get '/metadata/validate', metadata.validateMetadata
+  app.use route.post '/metadata/validate', metadata.validateMetadata
   app.use route.post '/metadata', metadata.upsertMetadata

   # Server restart endpoint


Moved container logic into View

Moved container logic into View

--- coffee/views/view.coffee
+++ coffee/views/view.coffee
@@ -7,10 +7,19 @@
     # Mixin a Subscriber
     _(View.prototype).defaults Subscriber

+    # View container element
+    # Set this property in a derived class to specify the container element.
+    # The view is automatically appended to the container element
+    # when it’s rendered.
+    containerSelector: null
+    $container: null
+
+
     initialize: ->
       #console.debug 'View#initialize', @

       # Listen for disposal of the model
+      # If the model is disposed, automatically dispose the associated view
       if @model or @collection
         @modelBind 'dispose', @dispose

@@ -188,9 +197,14 @@
         html = template @getTemplateData()

         # Replace HTML
-        # This is a workaround for an apparent issue with jQuery 1.7's innerShiv feature
+        # This is a workaround for an apparent issue with jQuery 1.7’s innerShiv feature
         # Using @$el.html(html) caused issues with HTML5-only tags in IE7 and IE8
         @$el.empty().append html
+
+      # Automatically append to DOM if the container element is set
+      # TODO: Sometimes it’s better to do this at the end of a specific render method
+      if @$container
+        @$container.append @el

       # Return this
       @
@@ -228,5 +242,5 @@
       #console.debug 'View#dispose', @, 'finished'
       @disposed = true

-      # Your're frozen when your heart's not open
+      # Your're frozen when your heart’s not open
       Object.freeze? @


Refactored socket.io code in the stream module

--- lib/stream/index.coffee
+++ lib/stream/index.coffee
@@ -30,14 +30,11 @@
         track: _track
       , (stream) ->
         stream.on "data", (data) ->
-          # console.log data
           client.emit "message", data

         stream.on "destroy", (response) ->
           console.log "Destroying stream..."

-
-    # changeTrack track, client
     client.on "change_track", (payload) ->
       track = payload.track
       userId = payload.userId


Show fetching results message until done.

--- lib/autocomplete/autocomplete_view.coffee
+++ lib/autocomplete/autocomplete_view.coffee
@@ -21,7 +21,7 @@
       pos = @editor.getCursorBufferPosition()
       offset = @editor.buffer.characterIndexForPosition(pos)

-      @setLoading('Fetching results...')
+      @showFetchingResults()
       @api.updateFile @editor.getPath(), @editor.getText()
       @api.completion.getSuggestions(path, offset)
         .progress(@handleAutocompleteResult)
@@ -85,7 +85,10 @@
     results = @autocompleteInfo.params.results
     sortedResults = _.sortBy results, (res) => @SORT_MAP[res.relevance]
     @setItems(sortedResults)
+    @showFetchingResults() if @autocompleteInfo.params.isLast == false

+  showFetchingResults: =>
+    @setLoading('Fetching results...')

   selectNextItemView: ->
     super


FIX - I think I fixed the iOS touch bug where sometimes it wouldn't update focus on a fresh tap. Not actually sure how it was working at all!

--- source/Art/Engine/Events/PointerEventManager.coffee
+++ source/Art/Engine/Events/PointerEventManager.coffee
@@ -1,3 +1,17 @@
+{
+  inspect, clone, shallowClone, peek, first, min, max, eq, arrayWithoutValue, stableSort, log, isObject,
+  formattedInspect
+  isArray
+} = require 'art-standard-lib'
+{BaseClass} = require 'art-class-system'
+{point, rect, matrix} = require 'art-atomic'
+{EventEpoch} = require 'art-events'
+{eventEpoch} = EventEpoch
+
+Pointer = require './Pointer'
+PointerEvent = require './PointerEvent'
+KeyEvent = require './KeyEvent'
+
 ###
 PointerEventManager

@@ -105,19 +119,7 @@
       On either desktop or touch devices, if you are tracking move events, the pointer's location won't have changed between the last mouseMove event and the pointerUp event.
 ###

-Atomic = require 'art-atomic'
-Foundation = require 'art-foundation'
-Events = require 'art-events'
-Pointer = require './Pointer'
-PointerEvent = require './PointerEvent'
-KeyEvent = require './KeyEvent'
-
-{EventEpoch} = Events
-{eventEpoch} = EventEpoch
-{point, rect, matrix} = Atomic
-{inspect, clone, shallowClone, peek, first, min, max, eq, arrayWithoutValue, stableSort, log, isObject} = Foundation
-
-module.exports = class PointerEventManager extends Foundation.BaseObject
+module.exports = class PointerEventManager extends BaseClass

   constructor: (options={})->
     super
@@ -403,7 +405,7 @@
   updateFocusedPath: (pointer, element)->
     pointer ||= @activePointers[0]

-    newPath = rootToElementPath element || peek @currentFocusedPath
+    newPath = if isArray element then element else rootToElementPath element || peek @currentFocusedPath
     newPath = @validatedFocusPath if newPath[0] != @canvasElement

     @_currentFocusedPath = updatePath @currentFocusedPath, newPath,
@@ -417,7 +419,7 @@
     @currentFocusedPath

   focus: (pointer, element) ->
-    @updateFocusedPath pointer, element
+    @updateFocusedPath pointer, element || @pointerElementPath pointer

   updateMousePath: ->
     pointer = @mouse
@@ -438,7 +440,7 @@
     pointer = @activePointers[id] = new Pointer id, location

     if @_numActivePointers == 1
-      @focus pointer, peek @pointerElementPath pointer
+      @focus pointer

     @queuePointerEvents "pointerDown", pointer, timeStampInPerformanceSeconds

@@ -467,6 +469,7 @@
   # pointerUp - user activity cased this
   pointerUp: (id, timeStampInPerformanceSeconds) ->
     eventEpoch.logEvent "pointerUp", id
+
     unless pointer = @activePointers[id]
       return console.error "pointerUp(#{id}): no active pointer for that id"


change datalist to tab

change datalist to tab

--- detail.cjsx
+++ detail.cjsx
@@ -1,13 +1,13 @@
 {React, ReactBootstrap, FontAwesome} = window
-{OverlayTrigger, Tooltip, Table} = ReactBootstrap
+{OverlayTrigger, Tooltip, Table, TabbedArea, TabPane} = ReactBootstrap
 i18n = require './node_modules/i18n'
 {__} = i18n

-timeToString = (time) ->
+dateToString = (time) ->
   if time isnt null
     date = new Date(time)

-    "#{date.getMonth()+1}-#{date.getDate()} #{date.getHours()}:00"
+    "#{date.getMonth()+1}-#{date.getDate()}"

 DataItem = React.createClass
   render: ->
@@ -15,45 +15,77 @@
       {
         for item, index in @props.data
           if index is 0
-            {timeToString item}
+            {dateToString item}
           else
             if index is @props.data.length - 1
               item = "↑#{item}"
-            {item}
+            {item}
       }

+DataTable = React.createClass
+  render: ->
+

+

+

+          {__ 'Time'}
+          {__ 'Ranking'}
+          {__ 'Rate'}
+          Delta
+

+

+

+        {
+          for detail, index in @props.data
+            continue if (new Date(detail[0])).getUTCHours() is 18
+            _detail = []
+            for n, idx in detail
+              _detail[idx] = n
+            if index is 0
+              _detail[3] = detail[2]
+            else
+              _detail[3] = detail[2] - @props.data[index - 1][2]
+            <DataItem key={index} data={_detail} />
+        }
+

+

+
 Detail = React.createClass
+  getInitialState: ->
+    selectedKey: 0
+  handleSelectTab: (selectedKey) ->
+    @setState
+      selectedKey: selectedKey
   render: ->
     # updateTime, ranking, rate, exp
     {data, nickname, accounted, baseDetail, timeToString} = @props
+    partOne = []
+    partTwo = []
+    partThree = []
+    if data.length > 41
+      partOne = data.slice 0, 20
+      partTwo = data.slice 21, 40
+      partThree = data.slice 41, data.length - 1
+    else if data.length >= 21
+      partOne = data.slice 0, 20
+      partTwo = data.slice 21, data.length - 1
+    else
+      partOne = data.slice 0, data.length - 1

{__('Admiral　%s　', nickname)}

       <OverlayTrigger trigger='click' placement='bottom' overlay={
         <Tooltip>
-

-

-

-                {__ 'Time'}
-                {__ 'Ranking'}
-                {__ 'Rate'}
-                Rate Delta
-

-

-

-              {
-                for detail, index in data
-                  _detail = []
-                  for n,idx in detail
-                    _detail[idx] = n
-                  if index is 0
-                    _detail[3] = detail[2]
-                  else
-                    _detail[3] = detail[2] - data[index - 1][2]
-                  <DataItem key={index} data={_detail} />
-              }
-

-

+          <TabbedArea activeKey={@state.selectedKey} onSelect={@handleSelectTab} animation={false}>
+            <TabPane eventKey={1} tab='1-10'>
+              <DataTable data={partOne} />
+            </TabPane>
+            <TabPane eventKey={2} tab='10-20'>
+              <DataTable data={partTwo} />
+            </TabPane>
+            <TabPane eventKey={3} tab='20-'>
+              <DataTable data={partThree} />
+            </TabPane>
+          </TabbedArea>
         </Tooltip>
       }>


Déplacement du popup + recentrage/zoom de la map

--- app/assets/javascripts/nodes.js.coffee
+++ app/assets/javascripts/nodes.js.coffee
@@ -9,7 +9,7 @@
   $map = $("#map")

   if $map.length
-    map = L.map('map').setView([48.856, 2.35], 13)
+    map = L.map('map').setView([20, 50], 2)

     cloudmade_api_key = $map.data("cloudmade-api-key")
     L.Icon.Default.imageUrl = 'http://upload.wikimedia.org/wikipedia/commons/d/d7/Red_Point.gif'
@@ -23,7 +23,7 @@
       iconRetinaUrl: 'http://upload.wikimedia.org/wikipedia/commons/d/d7/Red_Point.gif',
       iconSize: [20, 20],
       iconAnchor: [10, 10],
-      popupAnchor: [10, 0],
+      popupAnchor: [0, -10],
       #shadowUrl: 'my-icon-shadow.png',
       #shadowRetinaUrl: 'my-icon-shadow@2x.png',
       #shadowSize: [68, 95],


Remove "quick submit" behavior specific to the Notes JS

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -62,12 +62,6 @@

     # fetch notes when tab becomes visible
     $(document).on "visibilitychange", @visibilityChange
-
-    # Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
-    $(document).on 'keydown', '.js-note-text', (e) ->
-      return if e.originalEvent.repeat
-      if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
-        $(@).closest('form').submit()

   cleanBinding: ->
     $(document).off "ajax:success", ".js-main-target-form"
@@ -82,7 +76,6 @@
     $(document).off "click", ".js-discussion-reply-button"
     $(document).off "click", ".js-add-diff-note-button"
     $(document).off "visibilitychange"
-    $(document).off "keydown", ".js-note-text"
     $(document).off "keyup", ".js-note-text"
     $(document).off "click", ".js-note-target-reopen"
     $(document).off "click", ".js-note-target-close"


Show selected attachment in comment form

Signed-off-by: Dmitriy Zaporozhets <be23d75b156792e5acab51b196a2deb155d35d6a@gmail.com>

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -36,6 +36,9 @@

     # attachment button
     $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment
+
+    # update the file name when an attachment is selected
+    $(document).on "change", ".js-note-attachment-input", @updateFormAttachment

     # reply to diff/discussion notes
     $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote
@@ -429,4 +432,16 @@
   updateVotes: ->
     (new NotesVotes).updateVotes()

+  ###
+  Called after an attachment file has been selected.
+
+  Updates the file name for the selected attachment.
+  ###
+  updateFormAttachment: ->
+    form = $(this).closest("form")
+
+    # get only the basename
+    filename = $(this).val().replace(/^.*[\\\/]/, "")
+    form.find(".js-attachment-filename").text filename
+
 @Notes = Notes


Don't refer to a var I removed

Don't refer to a var I removed

--- app/assets/javascripts/projects.coffee
+++ app/assets/javascripts/projects.coffee
@@ -69,8 +69,6 @@
         error: setError

         xAmzHeadersAtInitiate: { 'x-amz-acl': 'public-read' }
-        xAmzHeadersAtUpload: headers
-        xAmzHeadersAtComplete: headers

         complete: (xhr, awsObjectKey) =>
           $.ajax


add plugins in JSON attrs

add plugins in JSON attrs

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -36,5 +36,6 @@
           audio_formats: audio_formats
           video_formats: video_formats
           cookies: cookies
+          plugins: plugins.join(', ')

   update_info()


Refactor combo splitting into method

Refactor combo splitting into method

--- src/listnr.coffee
+++ src/listnr.coffee
@@ -85,11 +85,14 @@
     fn = => callback.apply(@_listener, arguments)
     obj = @_map

-    [head..., tail] = combo.split(@_comboBreaker)
+    [head..., tail] = @split(combo)
     obj = obj[key] or= {} for key in head
     obj[tail] = [fn, helpText]

     this
+
+  split: (combo) ->
+    combo.split(@_comboBreaker)

   unmap: (combo) ->
     delete @_map[combo]


Reformated code of module logger

Reformated code of module logger

--- src/logger.coffee
+++ src/logger.coffee
@@ -1,18 +1,21 @@
 _ = require 'lodash'
 assert = require 'assert'
-readline = require 'readline'
+{ clearLine, cursorTo } = require 'readline'

 styler = require './styler'
 { verbose } = require './cli'

 module.exports = @

+# Alias
+{ stdout, stderr, exit } = process
+
 # Constants
 [INFO,WARN,ERROR,VERBOSE] = TAGS = ["INFO", "WARN", "ERROR", "VERBS"]
 LEFT_MARGIN = _.maxBy(TAGS, _.size).length + 1
 TAG_INDICATOR = ': '
 LEFT_PADSTRING_LONG_LINES = _.repeat(" ", LEFT_MARGIN + TAG_INDICATOR.length)
-MAX_COLS_PER_LINE = Math.max 10, process.stdout.columns - LEFT_PADSTRING_LONG_LINES.length - 1
+MAX_COLS_PER_LINE = Math.max 10, stdout.columns - LEFT_PADSTRING_LONG_LINES.length - 1

 # Private methods
 tag = (txt, style) -> style(_.padEnd(txt, LEFT_MARGIN)) + TAG_INDICATOR
@@ -33,7 +36,7 @@
         else # Quite strange there's no space in 70 character string...
             cutString[...-1] + "-\n" + LEFT_PADSTRING_LONG_LINES + fold(str[cols-1..])

-createWriter = (txt, styler, stream = process.stdout) => (msg, endline = on) =>
+createWriter = (txt, styler, stream = stdout) => (msg, endline = on) =>
         msg += "\n" if endline is on
         stream.write tag(txt, styler) + fold(msg)
         @
@@ -41,10 +44,7 @@
 # Exposed methods
 @endLine = => console.log(""); @

-@eraseLine = =>
-    readline.clearLine(process.stdout)
-    readline.cursorTo(process.stdout, 0)
-    @
+@eraseLine = (stream = stdout) => clearLine(stream); cursorTo(stream, 0); @

 @verbose = @v = if verbose? then createWriter VERBOSE, styler.verbose else => @

@@ -55,10 +55,10 @@
 @warn = @w = createWriter WARN, styler.warn

 @error = @e = do ->
-    errorWriter = createWriter ERROR, styler.error, process.stderr
+    errorWriter = createWriter ERROR, styler.error, stderr
     (msg, end = yes, exitCode = 1) ->
         ret = errorWriter msg
-        process.exit exitCode if end is yes
+        exit exitCode if end is yes
         ret

@@ -77,7 +77,7 @@
     .updateInfo("My name is joan")
     .endLine()

-process.stdout.write "Shouldn't appear"; @eraseLine()
+stdout.write "Shouldn't appear"; @eraseLine()

 @error("This ends with error code 2", true, 2)
 @info("Should'nt be printed")