tabs to spaces

tabs to spaces

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -1,687 +1,687 @@
 (($, window) ->

-	log = (args...) ->
-		if console?.log and mb?.debug is true
-			args.unshift "[#{M.CLS}]"
-			console.log.apply console, args
-		return
-
-	#__msg = (args...) ->
-	#	ret = chrome.i18n.getMessage.apply chrome.i18n, args
-	#	log args, ret
-	#	ret
-	__msg = chrome.i18n.getMessage
-
-	class M
-		debug: 	false
-
-		@CLS: 			'mailfred'
-		#@CLS_NAV: 		M.CLS + '-nav'
-		@CLS_THREAD: 	M.CLS + '-thread'
-		@CLS_POPUP: 	M.CLS + '-popup'
-		@CLS_MENU: 		M.CLS + '-menu'
-		@CLS_PICKER: 	M.CLS + '-picker'
-		@CLS_LOADER: 	M.CLS + '-loader'
-
-		@ID_PREFIX: 	M.CLS + '-id-'
-
-		@TYPE_THREAD: 	'thread'
-		@TYPE_NAV: 		'nav'
-
-		@STORE:
-			# LASTUSED:		'lastUsed'
-			BOX_SETTING:	'settings'
-			DEBUG:			'debug'
-			EMAIL:			'email'
-			LAST_VERSION:	'lastVersion'
-
-		@GM_SEL:
-			ARCHIVE_BUTTON: 				'.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7:visible'
-			THREAD_BUTTON_BAR: 				'.iH > div'
-			INSERT_AFTER: 					'.G-Ni.J-J5-Ji:visible:nth-child(3)'
-			PREVIEW_PANE_ENABLED:			'.apF .apJ'
-			PREVIEW_PANE_THREAD_BUTTON_BAR:	"[gh='mtb'] > div > div"
-
-		# URL
-		url: null
-
-		# Current GMail address of the logged in user
-		currentGmail: null
-		settingEmail: null
-		settingProps: {}
-		selectedConversationId: null
-		# lastUsed: []
-
-		currentView: null
-
-		constructor: ->
-			window.addEventListener "message", @messageListener, false
-
-			@initSettings()
-
-			# Get the service URL
-			chrome.extension.sendMessage {action: 'url'}, (url) =>
-				@url = url
-				@checkVersion()
-				return
-
-		initSettings: ->
-			# Get the settings
-			chrome.storage.local.get null, (items) =>
-				_.each items, (value, key) =>
-					switch key
-						when M.STORE.DEBUG
-							@debug = value
-							log 'MailFred debugging is enabled' if @debug
-						when M.STORE.EMAIL
-							@settingEmail = value
-					return
-				return
-
-			# Get the selected options
-			chrome.storage.sync.get M.STORE.BOX_SETTING, (items) =>
-				@settingProps = items[M.STORE.BOX_SETTING]
-				return
-
-			# Get the last used presets
-			# chrome.storage.sync.get M.STORE.LASTUSED, (items) =>
-			#	@lastUsed = items[M.STORE.LASTUSED] ? []
-			#	return
-
-			# Listen to changes
-			chrome.storage.onChanged.addListener (changes, namespace) =>
-				switch namespace
-					when 'sync'
-						#if M.STORE.LASTUSED of changes
-						#	@lastUsed = changes[M.STORE.LASTUSED].newValue
-						if M.STORE.BOX_SETTING of changes
-							@settingProps = changes[M.STORE.BOX_SETTING].newValue
-					when 'local'
-						if M.STORE.DEBUG of changes
-							@debug = changes[M.STORE.DEBUG].newValue
-							log 'MailFred debugging is enabled' if @debug
-						if M.STORE.EMAIL of changes
-							@settingEmail = changes[M.STORE.EMAIL].newValue
-				return
-
-			return
-
-		@storeLastVersion: (version) ->
-			store = {}
-			store[M.STORE.LAST_VERSION] = version
-			chrome.storage.sync.set store, ->
-				log 'Set the last used version to', version
-				return
-			return
-
-		@getLastVersion: (resp) ->
-			chrome.storage.sync.get M.STORE.LAST_VERSION, (items) =>
-				resp items[M.STORE.LAST_VERSION]
-				return
-			return
-
-		@isAuthorisationErrorResponse: (resp) ->
-			resp?.toLowerCase().indexOf "authorization" isnt -1
-
-		checkAuthorised: (resp) ->
-			url = @getServiceURL()
-			log 'checking if the user authorised', url
-			$.ajax
-				url: 			url
-				dataType: 		'json'
-				data:			action: 'status'
-				success:		(data, textStatus, jqXHR) ->
-									log '...user is still authorised'
-									resp true
-									return
-				error:			(jqXHR, textStatus, errorThrown) ->
-									log '...user is not authorised'
-									resp false
-									return
-
-		firstInstall: (version) ->
-			log 'first install', version
-			@welcome()
-			return
-
-		upgradeInstall: (oldVersion, newVersion) ->
-			log 'upgrade from', oldVersion, newVersion
-			@checkAuthorised (authorised) =>
-				@gettingStarted action: 'setupNoSchedule' unless authorised
-				return
-			return
-
-		sameInstall: (version) ->
-			log 'no version change', version
-			return
-
-		inConversation: ->
-			@currentView is 'conversation'
-
-		activateArchiveButton: ->
-			return unless @inConversation()
-			button = ($ M.GM_SEL.ARCHIVE_BUTTON).get 0
-			if button
-				Eventr.simulate button, 'mousedown'
-				Eventr.simulate button, 'mouseup'
-			return
-
-		messageListener: (e) =>
-			if e.source is window
-				# We only accept messages from ourselves
-				#log "event", e
-
-				if e.data?.from is "GMAILR"
-					# log 'Got Gmailr event: ', e.data.event.type
-					evt = e.data.event
-					switch evt.type
-						when 'init'
-						# GMailr is ready
-							@currentGmail = evt.email
-							# GMailUI.Breadcrumbs.add (__msg 'extName'), => @gettingStarted()
-
-							# kick GMailr into debug mode?
-							if @debug
-								message =
-									from: 'MAILFRED'
-									type: 'debug.enable'
-								window.postMessage message, "*"
-
-						when 'viewThread'
-						# User moves to previous or next convo
-							@selectedConversationId = evt.args[0]
-							@inject()
-						when 'viewChanged'
-						# User switches view (conversation <-> threads)
-							@currentView = evt.args[0]
-							log "User switched to #{@currentView} view"
-							@inject()
-
-			return
-
-		checkVersion: ->
-			# Get the extension version
-			chrome.extension.sendMessage {action: "version"}, (version) =>
-				M.getLastVersion (lastVersion) =>
-					unless lastVersion
-						M.storeLastVersion version
-						@firstInstall version
-					else if lastVersion < version
-						M.storeLastVersion version
-						@upgradeInstall lastVersion, version
-					else
-						@sameInstall version
-					return
-				return
-			return
-
-		inject: ->
-			return unless @inConversation()
-			log 'Email address in settings', @settingEmail
-			log 'Current Gmail window', @currentGmail
-
-			@injectThread() if (not @settingEmail or not @currentGmail) or @currentGmail.trim() in @settingEmail.split /[, ]+/ig
-			return
-
-		getServiceURL: -> @url
-
-		#injectCompose: ->
-		#	navs = ($ ".dW.E[role=navigation] > .J-Jw").filter (index) ->
-		#		($ ".#{M.CLS_NAV}", @).length is 0
-		#
-		#	navs.append @composeButton M.TYPE_NAV if navs.length > 0
-		#	return
-
-		isPreviewPaneEnabled: ->
-			($ M.GM_SEL.PREVIEW_PANE_ENABLED).length > 0
-
-		injectThread: ->
-			log 'Injecting buttons in thread view'
-
-			if @isPreviewPaneEnabled()
-				# Preview pane is enabled
-				sel = M.GM_SEL.PREVIEW_PANE_THREAD_BUTTON_BAR
-			else
-				# Preview pane is not enabled
-				sel = M.GM_SEL.THREAD_BUTTON_BAR
-
-			threads = ($ sel).filter (index) ->
-				($ ".#{M.CLS_THREAD}", @).length is 0
-
-			if threads.length > 0
-				after = threads.find M.GM_SEL.INSERT_AFTER
-				bar = @composeButton()
-				bar.addClass 'T-I'
-				if after.length > 0 then after.after bar else threads.append bar
-			return
-
-		actionOps: [
-					'unread'
-					'star'
-					'inbox'
-					]
-
-		ucFirst: (str) ->
-			str[0].toUpperCase() + str.substring(1).toLowerCase()
-
-		getTexts: (key, time) ->
-			i18nKey = @ucFirst key
-			x = "menuTimePresetCloseFutureItem#{i18nKey}#{time}"
-			[
-				(__msg "#{x}Selected")
-				(__msg x)
-			]
-
-		composeButton: =>
-
-			props =
-				noanswer: 	false
-				unread:		true
-				star:		false
-				inbox:		true
-				archive:	true
-				#when:		_delta _1m
-
-			_.each props, (v, op) =>
-				hasSetting = @settingProps and typeof @settingProps[op] isnt 'undefined'
-				props[op] = !! @settingProps?[op] if hasSetting
-				return
-
-			schedule = (wen) =>
-				pickerMenu.close()
-				presetMenu.close()
-				button.close()
-
-				loading = ->
-					button.addClass M.CLS_LOADER
-					return
-				reset = ->
-					button.removeClass M.CLS_LOADER
-					return
-
-				props.when = wen
-				@onSchedule props, loading, reset
-
-				return
-
-			isValid = =>
-				valid = false
-				for op in @actionOps
-					valid |= props[op]
-				valid = !!valid
-				timeSection.toggle valid
-				constraintSection.toggle valid
-				errorSection.toggle !valid
-				return
-
-			propStoreFn = (checkbox, propName) =>
-				checkbox.addOnChange (e, checked) =>
-					props[propName] = checked
-					toStore = {}
-					toStore[M.STORE.BOX_SETTING] = props
-					@settingProps = props
-					chrome.storage.sync.set toStore, ->
-						log 'Storing properties finished'
-						return
-					isValid()
-				return
-
-			presets = {}
-			presets.minutes 	= [5] if @debug
-			presets.hours 		= [4]
-			presets.hours.unshift 2 if @debug
-			presets.tomorrow 	= [8,14]
-			presets.days 		= [2,7,14]
-			presets.months 		= [1] if @debug
-
-			# UI
-
-			bar = new GMailUI.ButtonBar
-			bar.addClass M.CLS
-			bar.addClass M.CLS_THREAD
-
-			popup = new GMailUI.Popup
-			popup.addClass M.CLS_POPUP
-
-			popup.append new GMailUI.PopupLabel __msg 'menuMailActions'
-
-			# Actions section
-
-			actionSection = popup.append new GMailUI.Section
-			actionSectionCheckboxes =
-				unread: actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionMarkUnread'), 	props.unread, 	'', (__msg 'mailActionMarkUnreadTitle'))
-				star:	actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionStar'), 			props.star, 	'', (__msg 'mailActionStarTitle'))
-				inbox:	actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionMoveToInbox'), 	props.inbox, 	'', (__msg 'mailActionMoveToInboxTitle'))
-
-			_.each actionSectionCheckboxes, propStoreFn
-
-			# Constraints section
-
-			constraintSection = popup.append new GMailUI.Section
-			constraintSection.append new GMailUI.Separator
-			constraintSectionCheckboxes =
-				noanswer:	constraintSection.append (new GMailUI.PopupCheckbox (__msg 'menuConstraintsNoAnswer'),		props.noanswer,	'', (__msg 'menuConstraintsNoAnswerTitle'))
-				archive:	constraintSection.append (new GMailUI.PopupCheckbox (__msg 'menuAdditionalActionsArchive'), props.archive,	'', (__msg 'menuAdditionalActionsArchiveTitle'))
-
-			_.each constraintSectionCheckboxes, propStoreFn
-
-
-			presetMenu = new GMailUI.PopupMenu popup
-			presetMenu.addClass M.CLS_MENU
-
-			# Date picker
-
-			pickerMenu = new GMailUI.PopupMenu popup
-			pickerMenu.addClass M.CLS_PICKER
-
-			pickerDiv = (pickerMenu.append new GMailUI.RawHTML '
').getElement()
-
-			initPicker = false
-			pickerMenu.onShow = =>
-				unless initPicker
-					$.datepicker.setDefaults $.datepicker.regional[ __msg 'calendarLanguage' ]
-					pickerDiv.datepicker
-									minDate: '+1d'
-									maxDate: '+1y'
-									dateFormat: __msg 'dateFormat'
-									showOtherMonths: true
-									selectOtherMonths: true
-									changeMonth: true
-									changeYear: true
-									onSelect: (dateText, inst) =>
-														if (date = pickerDiv.datepicker 'getDate')
-															#timeSectionElements.manual.setSelected true, true
-															log 'schedule', date
-															schedule @_specified date
-														return
-					initPicker = true
-				return
-
-			# Time section
-
-			timeSection = popup.append new GMailUI.Section
-			timeSection.append new GMailUI.Separator
-			timeSection.append new GMailUI.PopupLabel __msg 'menuTime'
-
-
-			#unless _.isEmpty @lastUsed
-			#	lastUsedSection = timeSection.append new GMailUI.Section
-
-			#	_.each @lastUsed, (tuple) =>
-			#		key = tuple.key
-			#		time = tuple.time
-			#		# Remove the last used presets from the list
-			#		presets[key] = _.without presets[key], time if presets[key]
-
-			#		[label, title] = @getTexts key, time
-			#		button = lastUsedSection.append new GMailUI.Button label, title
-			#		timeFn = @generateTimeFn key
-			#		button.on 'click', (e) =>
-			#			@storeLastUsed key, time
-			#			wen = timeFn time
-			#			log "schedule: #{time}, #{key}: #{wen}"
-			#			schedule wen
-			#			return
-			#		return
-			#	lastUsedSection.append new GMailUI.Separator
-
-
-			#presetItem = timeSection.append (new GMailUI.PopupMenuItem presetMenu, (__msg 'menuTimePresetCloseFuture'), 	'', '',	true)
-
-			# Presets
-
-			sep = null
-			_.each presets, (times, key) =>
-				unless _.isEmpty times
-					timeSection.append sep if sep
-					_.each times, (time) =>
-						timeFn = @generateTimeFn key
-						[label, title] = @getTexts key, time
-						item = timeSection.append new GMailUI.Button label, title
-						item.on 'click', =>
-								wen = timeFn time
-								log "schedule: #{time}, #{key}: #{wen}"
-								schedule wen
-							return
-						return
-					sep = new GMailUI.Separator
-				return
-
-			timeSection.append new GMailUI.Separator
-			timeSection.append 	(new GMailUI.PopupMenuItem pickerMenu, (__msg 'menuTimePresetSpecifiedDate'),	'',	'',	true)
-
-			button = bar.append new GMailUI.ButtonBarPopupButton popup, '', (__msg 'extName')
-
-			errorSection = popup.append new GMailUI.ErrorSection __msg 'errorNoActionSpecified' # __msg 'errorNoTimeSpecified'
-
-			isValid()
-
-			bar.getElement()
-
-		storeLastUsed: (key, time) =>
-			entry =
-				key: key
-				time: time
-			lastUsed = _.reject @lastUsed, (item) -> _.isEqual item, entry
-
-			lastUsed.unshift entry
-			store = {}
-			store[M.STORE.LASTUSED] = (_.first lastUsed, 3)
-
-			chrome.storage.sync.set store, ->
-				log 'Last used items saved', store
-				return
-			return
-
-		_delta: (offset) ->
-			"delta:#{offset}"
-
-		_specified: (d) ->
-			time = d.getTime() + d.getTimezoneOffset()*60*1000
-			"specified:#{time}"
-
-		generateTimeFn: (unit) ->
-			_1d = 24 * (_1h = 60 * (_1m = 60 * 1000))
-
-			switch unit
-				when 'minutes'
-					(time) => @_delta (_1m * time)
-				when 'hours'
-					(time) => @_delta (_1h * time)
-				when 'tomorrow'
-					(hour) =>
-						now = new Date
-						tomorrow = new Date
-						tomorrow.setDate (now.getDate() + 1)
-						tomorrow.setHours hour
-						tomorrow.setMinutes 0
-						tomorrow.setSeconds 0
-						tomorrow.setMilliseconds 0
-						@_specified tomorrow
-				when 'days'
-					(time) => @_delta (_1d * time)
-				when 'months'
-					(month) =>
-						now = new Date
-						other = new Date
-						other.setMonth (now.getMonth() + month)
-						@_specified other
-
-		getMessageId: ->
-			if @isPreviewPaneEnabled()
-				id = @selectedConversationId
-			else
-				id = /\/([0-9a-f]{16})/.exec window.location.hash
-				id = id?[1]
-
-			if id is null
-				throw __msg 'errorNotWithinAConversation'
-			id
-
-		onSchedule: (props, loadingIcon, resetIcon) =>
-			try
-				messageId = @getMessageId()
-			catch e
-				@onScheduleError null, null, e.toString()
-				return
-
-			archive = !!props.archive
-
-			loadingIcon?() unless archive
-
-			data =
-				msgId:	messageId
-				when:		props.when
-				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) ->
-				delete data[key] if val is false
-				return
-
-			log 'scheduling mail...', data
-
-			$.ajax
-				url: 			@getServiceURL()
-				type: 		'POST'
-				dataType: 		'json'
-				data:			data
-				success:		(resp, textStatus, jqXHR) =>
-									@onScheduleSuccess resp, data
-									return
-				error:			(jqXHR, textStatus, errorThrown) =>
-									# log arguments
-									@onScheduleError textStatus, data, errorThrown, jqXHR.responseText
-									return
-				complete:		(jqXHR, textStatus) ->
-									resetIcon?() unless data.archiveAfterScheduling
-									return
-
-			if archive
-				@activateArchiveButton()
-			else
-				_.delay (->
-						resetIcon?()
-						return
-						), 600
-			return
-
-
-		onScheduleSuccess: (data, params) =>
-			log 'Scheduling success', data
-			if data.success
-				chrome.extension.sendMessage
-					action: 	'notification'
-					icon: 		"images/tie48x48.png"
-					title: 		__msg 'notificationScheduleSuccessTitle'
-					message: 	__msg 'notificationScheduleSuccess'
-			else
-				@onScheduleError data.error, params, data.error
-			return
-
-		createDialog: (title, okButton, cancelButton) ->
-			dialog = new GMailUI.ModalDialog title
-
-			[okButtonLabel, okButtonTooltip] = okButton
-			[cancelButtonLabel, cancelButtonTooltip] = cancelButton
-
-			container = dialog.append new GMailUI.ModalDialog.Container
-
-			footer = dialog.append new GMailUI.ModalDialog.Footer
-			okButton = footer.append new GMailUI.ModalDialog.Button okButtonLabel, okButtonTooltip
-			cancelButton = footer.append new GMailUI.ModalDialog.Button cancelButtonLabel, cancelButtonTooltip, 'cancel'
-
-			[dialog, okButton, cancelButton, container, footer]
-
-		welcome: ->
-			extName = __msg 'extName'
-			[dialog, okButton, cancelButton, container, footer] = @createDialog (__msg 'welcomeDialogTitle', extName), [(__msg 'welcomeDialogButtonOk'), (__msg 'welcomeDialogButtonOkTooltip')], [(__msg 'welcomeDialogButtonCancel'), (__msg 'welcomeDialogButtonCancelTooltip', extName)]
-
-			container.append	"""
-

-
-									#{__msg 'welcomeDialogText', extName}
-

-								"""
-
-			okButton.on 'click', =>
-				[authDialog, authOkButton, authCancelButton, authContainer, authFooter] = @gettingStartedDialog()
-				container.replaceWith authContainer
-				okButton.replaceWith authOkButton
-				cancelButton.replaceWith authCancelButton
-				dialog.title authDialog.title()
-
-				authOkButton.on 'click', =>
-					@openAuthWindow action: 'setupNoSchedule'
-					dialog.close()
-					return
-
-				authCancelButton.on 'click', dialog.close
-
-			cancelButton.on 'click', dialog.close
-			dialog.open()
-
-		gettingStartedDialogContent: ->
-			extName = __msg 'extName'
-			img = chrome.extension.getURL "images/#{__msg 'authorizeDialogImageLanguage'}/clickToAuthorize.png"
-			"""
-

-				#{__msg 'authorizeDialogText', extName}
-

-

-
-

-			"""
-
-		gettingStartedDialog: ->
-			extName = __msg 'extName'
-			[dialog, okButton, cancelButton, container, footer] = @createDialog (__msg 'authorizeDialogTitle', extName), [(__msg 'authorizeDialogButtonOk'), (__msg 'authorizeDialogButtonOkTooltip')], [(__msg 'authorizeDialogButtonCancel'), (__msg 'authorizeDialogButtonCancelTooltip', extName)]
-			container.append @gettingStartedDialogContent()
-			[dialog, okButton, cancelButton, container, footer]
-
-		gettingStarted: (params) ->
-			[dialog, okButton, cancelButton, container, footer] = @gettingStartedDialog()
-
-			okButton.on 'click', =>
-				@openAuthWindow params
-				dialog.close()
-				return
-
-			cancelButton.on 'click', dialog.close
-
-			dialog.open()
-
-		openAuthWindow: (params) ->
-			url = @getServiceURL()
-			if params
-				query = $.param params
-				url += "?#{query}"
-			window.open url, M.CLS, 'width=880,height=500,location=0,menubar=0,scrollbars=0,status=0,toolbar=0,resizable=1'
-			return
-
-		onScheduleError: (status, params, error, responseText) =>
-			log 'There was an error', arguments
-			if (status is 'parsererror' and M.isAuthorisationErrorPage responseText) or (M.isAuthorisationErrorResponse status)
-				params ?= {}
-				params.action = 'setup'
-				delete params.callback if params.callback
-				@gettingStarted params
-			else
-				notification =
-					action: 	'notification'
-					icon: 		"images/tie48x48.png"
-					title:		__msg 'notificationScheduleErrorTitle'
-					message:	if (_.isObject error) and ('code' of error) then (__msg "notificationScheduleError#{error.code}") else (__msg 'notificationScheduleError', '' + new String error)
-
-				chrome.extension.sendMessage notification
-
-			return
-
-	mb = new M
-	return
+  log = (args...) ->
+    if console?.log and mb?.debug is true
+      args.unshift "[#{M.CLS}]"
+      console.log.apply console, args
+    return
+
+  #__msg = (args...) ->
+  #  ret = chrome.i18n.getMessage.apply chrome.i18n, args
+  #  log args, ret
+  #  ret
+  __msg = chrome.i18n.getMessage
+
+  class M
+    debug:   false
+
+    @CLS:       'mailfred'
+    #@CLS_NAV:     M.CLS + '-nav'
+    @CLS_THREAD:   M.CLS + '-thread'
+    @CLS_POPUP:   M.CLS + '-popup'
+    @CLS_MENU:     M.CLS + '-menu'
+    @CLS_PICKER:   M.CLS + '-picker'
+    @CLS_LOADER:   M.CLS + '-loader'
+
+    @ID_PREFIX:   M.CLS + '-id-'
+
+    @TYPE_THREAD:   'thread'
+    @TYPE_NAV:     'nav'
+
+    @STORE:
+      # LASTUSED:    'lastUsed'
+      BOX_SETTING:  'settings'
+      DEBUG:      'debug'
+      EMAIL:      'email'
+      LAST_VERSION:  'lastVersion'
+
+    @GM_SEL:
+      ARCHIVE_BUTTON:         '.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7:visible'
+      THREAD_BUTTON_BAR:         '.iH > div'
+      INSERT_AFTER:           '.G-Ni.J-J5-Ji:visible:nth-child(3)'
+      PREVIEW_PANE_ENABLED:      '.apF .apJ'
+      PREVIEW_PANE_THREAD_BUTTON_BAR:  "[gh='mtb'] > div > div"
+
+    # URL
+    url: null
+
+    # Current GMail address of the logged in user
+    currentGmail: null
+    settingEmail: null
+    settingProps: {}
+    selectedConversationId: null
+    # lastUsed: []
+
+    currentView: null
+
+    constructor: ->
+      window.addEventListener "message", @messageListener, false
+
+      @initSettings()
+
+      # Get the service URL
+      chrome.extension.sendMessage {action: 'url'}, (url) =>
+        @url = url
+        @checkVersion()
+        return
+
+    initSettings: ->
+      # Get the settings
+      chrome.storage.local.get null, (items) =>
+        _.each items, (value, key) =>
+          switch key
+            when M.STORE.DEBUG
+              @debug = value
+              log 'MailFred debugging is enabled' if @debug
+            when M.STORE.EMAIL
+              @settingEmail = value
+          return
+        return
+
+      # Get the selected options
+      chrome.storage.sync.get M.STORE.BOX_SETTING, (items) =>
+        @settingProps = items[M.STORE.BOX_SETTING]
+        return
+
+      # Get the last used presets
+      # chrome.storage.sync.get M.STORE.LASTUSED, (items) =>
+      #  @lastUsed = items[M.STORE.LASTUSED] ? []
+      #  return
+
+      # Listen to changes
+      chrome.storage.onChanged.addListener (changes, namespace) =>
+        switch namespace
+          when 'sync'
+            #if M.STORE.LASTUSED of changes
+            #  @lastUsed = changes[M.STORE.LASTUSED].newValue
+            if M.STORE.BOX_SETTING of changes
+              @settingProps = changes[M.STORE.BOX_SETTING].newValue
+          when 'local'
+            if M.STORE.DEBUG of changes
+              @debug = changes[M.STORE.DEBUG].newValue
+              log 'MailFred debugging is enabled' if @debug
+            if M.STORE.EMAIL of changes
+              @settingEmail = changes[M.STORE.EMAIL].newValue
+        return
+
+      return
+
+    @storeLastVersion: (version) ->
+      store = {}
+      store[M.STORE.LAST_VERSION] = version
+      chrome.storage.sync.set store, ->
+        log 'Set the last used version to', version
+        return
+      return
+
+    @getLastVersion: (resp) ->
+      chrome.storage.sync.get M.STORE.LAST_VERSION, (items) =>
+        resp items[M.STORE.LAST_VERSION]
+        return
+      return
+
+    @isAuthorisationErrorResponse: (resp) ->
+      resp?.toLowerCase().indexOf "authorization" isnt -1
+
+    checkAuthorised: (resp) ->
+      url = @getServiceURL()
+      log 'checking if the user authorised', url
+      $.ajax
+        url:       url
+        dataType:     'json'
+        data:      action: 'status'
+        success: (data, textStatus, jqXHR) ->
+          log '...user is still authorised'
+          resp true
+          return
+        error:      (jqXHR, textStatus, errorThrown) ->
+          log '...user is not authorised'
+          resp false
+          return
+
+    firstInstall: (version) ->
+      log 'first install', version
+      @welcome()
+      return
+
+    upgradeInstall: (oldVersion, newVersion) ->
+      log 'upgrade from', oldVersion, newVersion
+      @checkAuthorised (authorised) =>
+        @gettingStarted action: 'setupNoSchedule' unless authorised
+        return
+      return
+
+    sameInstall: (version) ->
+      log 'no version change', version
+      return
+
+    inConversation: ->
+      @currentView is 'conversation'
+
+    activateArchiveButton: ->
+      return unless @inConversation()
+      button = ($ M.GM_SEL.ARCHIVE_BUTTON).get 0
+      if button
+        Eventr.simulate button, 'mousedown'
+        Eventr.simulate button, 'mouseup'
+      return
+
+    messageListener: (e) =>
+      if e.source is window
+        # We only accept messages from ourselves
+        #log "event", e
+
+        if e.data?.from is "GMAILR"
+          # log 'Got Gmailr event: ', e.data.event.type
+          evt = e.data.event
+          switch evt.type
+            when 'init'
+            # GMailr is ready
+              @currentGmail = evt.email
+              # GMailUI.Breadcrumbs.add (__msg 'extName'), => @gettingStarted()
+
+              # kick GMailr into debug mode?
+              if @debug
+                message =
+                  from: 'MAILFRED'
+                  type: 'debug.enable'
+                window.postMessage message, "*"
+
+            when 'viewThread'
+            # User moves to previous or next convo
+              @selectedConversationId = evt.args[0]
+              @inject()
+            when 'viewChanged'
+            # User switches view (conversation <-> threads)
+              @currentView = evt.args[0]
+              log "User switched to #{@currentView} view"
+              @inject()
+
+      return
+
+    checkVersion: ->
+      # Get the extension version
+      chrome.extension.sendMessage {action: "version"}, (version) =>
+        M.getLastVersion (lastVersion) =>
+          unless lastVersion
+            M.storeLastVersion version
+            @firstInstall version
+          else if lastVersion < version
+            M.storeLastVersion version
+            @upgradeInstall lastVersion, version
+          else
+            @sameInstall version
+          return
+        return
+      return
+
+    inject: ->
+      return unless @inConversation()
+      log 'Email address in settings', @settingEmail
+      log 'Current Gmail window', @currentGmail
+
+      @injectThread() if (not @settingEmail or not @currentGmail) or @currentGmail.trim() in @settingEmail.split /[, ]+/ig
+      return
+
+    getServiceURL: -> @url
+
+    #injectCompose: ->
+    #  navs = ($ ".dW.E[role=navigation] > .J-Jw").filter (index) ->
+    #    ($ ".#{M.CLS_NAV}", @).length is 0
+    #
+    #  navs.append @composeButton M.TYPE_NAV if navs.length > 0
+    #  return
+
+    isPreviewPaneEnabled: ->
+      ($ M.GM_SEL.PREVIEW_PANE_ENABLED).length > 0
+
+    injectThread: ->
+      log 'Injecting buttons in thread view'
+
+      if @isPreviewPaneEnabled()
+        # Preview pane is enabled
+        sel = M.GM_SEL.PREVIEW_PANE_THREAD_BUTTON_BAR
+      else
+        # Preview pane is not enabled
+        sel = M.GM_SEL.THREAD_BUTTON_BAR
+
+      threads = ($ sel).filter (index) ->
+        ($ ".#{M.CLS_THREAD}", @).length is 0
+
+      if threads.length > 0
+        after = threads.find M.GM_SEL.INSERT_AFTER
+        bar = @composeButton()
+        bar.addClass 'T-I'
+        if after.length > 0 then after.after bar else threads.append bar
+      return
+
+    actionOps: [
+          'unread'
+          'star'
+          'inbox'
+          ]
+
+    ucFirst: (str) ->
+      str[0].toUpperCase() + str.substring(1).toLowerCase()
+
+    getTexts: (key, time) ->
+      i18nKey = @ucFirst key
+      x = "menuTimePresetCloseFutureItem#{i18nKey}#{time}"
+      [
+        (__msg "#{x}Selected")
+        (__msg x)
+      ]
+
+    composeButton: =>
+
+      props =
+        noanswer:   false
+        unread:    true
+        star:    false
+        inbox:    true
+        archive:  true
+        #when:    _delta _1m
+
+      _.each props, (v, op) =>
+        hasSetting = @settingProps and typeof @settingProps[op] isnt 'undefined'
+        props[op] = !! @settingProps?[op] if hasSetting
+        return
+
+      schedule = (wen) =>
+        pickerMenu.close()
+        presetMenu.close()
+        button.close()
+
+        loading = ->
+          button.addClass M.CLS_LOADER
+          return
+        reset = ->
+          button.removeClass M.CLS_LOADER
+          return
+
+        props.when = wen
+        @onSchedule props, loading, reset
+
+        return
+
+      isValid = =>
+        valid = false
+        for op in @actionOps
+          valid |= props[op]
+        valid = !!valid
+        timeSection.toggle valid
+        constraintSection.toggle valid
+        errorSection.toggle !valid
+        return
+
+      propStoreFn = (checkbox, propName) =>
+        checkbox.addOnChange (e, checked) =>
+          props[propName] = checked
+          toStore = {}
+          toStore[M.STORE.BOX_SETTING] = props
+          @settingProps = props
+          chrome.storage.sync.set toStore, ->
+            log 'Storing properties finished'
+            return
+          isValid()
+        return
+
+      presets = {}
+      presets.minutes   = [5] if @debug
+      presets.hours     = [4]
+      presets.hours.unshift 2 if @debug
+      presets.tomorrow   = [8,14]
+      presets.days     = [2,7,14]
+      presets.months     = [1] if @debug
+
+      # UI
+
+      bar = new GMailUI.ButtonBar
+      bar.addClass M.CLS
+      bar.addClass M.CLS_THREAD
+
+      popup = new GMailUI.Popup
+      popup.addClass M.CLS_POPUP
+
+      popup.append new GMailUI.PopupLabel __msg 'menuMailActions'
+
+      # Actions section
+
+      actionSection = popup.append new GMailUI.Section
+      actionSectionCheckboxes =
+        unread: actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionMarkUnread'),   props.unread,   '', (__msg 'mailActionMarkUnreadTitle'))
+        star:  actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionStar'),       props.star,   '', (__msg 'mailActionStarTitle'))
+        inbox:  actionSection.append (new GMailUI.PopupCheckbox (__msg 'mailActionMoveToInbox'),   props.inbox,   '', (__msg 'mailActionMoveToInboxTitle'))
+
+      _.each actionSectionCheckboxes, propStoreFn
+
+      # Constraints section
+
+      constraintSection = popup.append new GMailUI.Section
+      constraintSection.append new GMailUI.Separator
+      constraintSectionCheckboxes =
+        noanswer:  constraintSection.append (new GMailUI.PopupCheckbox (__msg 'menuConstraintsNoAnswer'),    props.noanswer,  '', (__msg 'menuConstraintsNoAnswerTitle'))
+        archive:  constraintSection.append (new GMailUI.PopupCheckbox (__msg 'menuAdditionalActionsArchive'), props.archive,  '', (__msg 'menuAdditionalActionsArchiveTitle'))
+
+      _.each constraintSectionCheckboxes, propStoreFn
+
+
+      presetMenu = new GMailUI.PopupMenu popup
+      presetMenu.addClass M.CLS_MENU
+
+      # Date picker
+
+      pickerMenu = new GMailUI.PopupMenu popup
+      pickerMenu.addClass M.CLS_PICKER
+
+      pickerDiv = (pickerMenu.append new GMailUI.RawHTML '
').getElement()
+
+      initPicker = false
+      pickerMenu.onShow = =>
+        unless initPicker
+          $.datepicker.setDefaults $.datepicker.regional[ __msg 'calendarLanguage' ]
+          pickerDiv.datepicker
+                  minDate: '+1d'
+                  maxDate: '+1y'
+                  dateFormat: __msg 'dateFormat'
+                  showOtherMonths: true
+                  selectOtherMonths: true
+                  changeMonth: true
+                  changeYear: true
+                  onSelect: (dateText, inst) =>
+                    if (date = pickerDiv.datepicker 'getDate')
+                      #timeSectionElements.manual.setSelected true, true
+                      log 'schedule', date
+                      schedule @_specified date
+                    return
+          initPicker = true
+        return
+
+      # Time section
+
+      timeSection = popup.append new GMailUI.Section
+      timeSection.append new GMailUI.Separator
+      timeSection.append new GMailUI.PopupLabel __msg 'menuTime'
+
+
+      #unless _.isEmpty @lastUsed
+      #  lastUsedSection = timeSection.append new GMailUI.Section
+
+      #  _.each @lastUsed, (tuple) =>
+      #    key = tuple.key
+      #    time = tuple.time
+      #    # Remove the last used presets from the list
+      #    presets[key] = _.without presets[key], time if presets[key]
+
+      #    [label, title] = @getTexts key, time
+      #    button = lastUsedSection.append new GMailUI.Button label, title
+      #    timeFn = @generateTimeFn key
+      #    button.on 'click', (e) =>
+      #      @storeLastUsed key, time
+      #      wen = timeFn time
+      #      log "schedule: #{time}, #{key}: #{wen}"
+      #      schedule wen
+      #      return
+      #    return
+      #  lastUsedSection.append new GMailUI.Separator
+
+
+      #presetItem = timeSection.append (new GMailUI.PopupMenuItem presetMenu, (__msg 'menuTimePresetCloseFuture'),   '', '',  true)
+
+      # Presets
+
+      sep = null
+      _.each presets, (times, key) =>
+        unless _.isEmpty times
+          timeSection.append sep if sep
+          _.each times, (time) =>
+            timeFn = @generateTimeFn key
+            [label, title] = @getTexts key, time
+            item = timeSection.append new GMailUI.Button label, title
+            item.on 'click', ->
+              wen = timeFn time
+              log "schedule: #{time}, #{key}: #{wen}"
+              schedule wen
+              return
+            return
+          sep = new GMailUI.Separator
+        return
+
+      timeSection.append new GMailUI.Separator
+      timeSection.append   (new GMailUI.PopupMenuItem pickerMenu, (__msg 'menuTimePresetSpecifiedDate'),  '',  '',  true)
+
+      button = bar.append new GMailUI.ButtonBarPopupButton popup, '', (__msg 'extName')
+
+      errorSection = popup.append new GMailUI.ErrorSection __msg 'errorNoActionSpecified' # __msg 'errorNoTimeSpecified'
+
+      isValid()
+
+      bar.getElement()
+
+    storeLastUsed: (key, time) =>
+      entry =
+        key: key
+        time: time
+      lastUsed = _.reject @lastUsed, (item) -> _.isEqual item, entry
+
+      lastUsed.unshift entry
+      store = {}
+      store[M.STORE.LASTUSED] = (_.first lastUsed, 3)
+
+      chrome.storage.sync.set store, ->
+        log 'Last used items saved', store
+        return
+      return
+
+    _delta: (offset) ->
+      "delta:#{offset}"
+
+    _specified: (d) ->
+      time = d.getTime() + d.getTimezoneOffset()*60*1000
+      "specified:#{time}"
+
+    generateTimeFn: (unit) ->
+      _1d = 24 * (_1h = 60 * (_1m = 60 * 1000))
+
+      switch unit
+        when 'minutes'
+          (time) => @_delta (_1m * time)
+        when 'hours'
+          (time) => @_delta (_1h * time)
+        when 'tomorrow'
+          (hour) =>
+            now = new Date
+            tomorrow = new Date
+            tomorrow.setDate (now.getDate() + 1)
+            tomorrow.setHours hour
+            tomorrow.setMinutes 0
+            tomorrow.setSeconds 0
+            tomorrow.setMilliseconds 0
+            @_specified tomorrow
+        when 'days'
+          (time) => @_delta (_1d * time)
+        when 'months'
+          (month) =>
+            now = new Date
+            other = new Date
+            other.setMonth (now.getMonth() + month)
+            @_specified other
+
+    getMessageId: ->
+      if @isPreviewPaneEnabled()
+        id = @selectedConversationId
+      else
+        id = /\/([0-9a-f]{16})/.exec window.location.hash
+        id = id?[1]
+
+      if id is null
+        throw __msg 'errorNotWithinAConversation'
+      id
+
+    onSchedule: (props, loadingIcon, resetIcon) =>
+      try
+        messageId = @getMessageId()
+      catch e
+        @onScheduleError null, null, e.toString()
+        return
+
+      archive = !!props.archive
+
+      loadingIcon?() unless archive
+
+      data =
+        msgId:  messageId
+        when:    props.when
+        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) ->
+        delete data[key] if val is false
+        return
+
+      log 'scheduling mail...', data
+
+      $.ajax
+        url:       @getServiceURL()
+        type:     'POST'
+        dataType:     'json'
+        data:      data
+        success:    (resp, textStatus, jqXHR) =>
+          @onScheduleSuccess resp, data
+          return
+        error:      (jqXHR, textStatus, errorThrown) =>
+          # log arguments
+          @onScheduleError textStatus, data, errorThrown, jqXHR.responseText
+          return
+        complete:    (jqXHR, textStatus) ->
+          resetIcon?() unless data.archiveAfterScheduling
+          return
+
+      if archive
+        @activateArchiveButton()
+      else
+        _.delay (->
+          resetIcon?()
+          return
+          ), 600
+      return
+
+
+    onScheduleSuccess: (data, params) =>
+      log 'Scheduling success', data
+      if data.success
+        chrome.extension.sendMessage
+          action:   'notification'
+          icon:     "images/tie48x48.png"
+          title:     __msg 'notificationScheduleSuccessTitle'
+          message:   __msg 'notificationScheduleSuccess'
+      else
+        @onScheduleError data.error, params, data.error
+      return
+
+    createDialog: (title, okButton, cancelButton) ->
+      dialog = new GMailUI.ModalDialog title
+
+      [okButtonLabel, okButtonTooltip] = okButton
+      [cancelButtonLabel, cancelButtonTooltip] = cancelButton
+
+      container = dialog.append new GMailUI.ModalDialog.Container
+
+      footer = dialog.append new GMailUI.ModalDialog.Footer
+      okButton = footer.append new GMailUI.ModalDialog.Button okButtonLabel, okButtonTooltip
+      cancelButton = footer.append new GMailUI.ModalDialog.Button cancelButtonLabel, cancelButtonTooltip, 'cancel'
+
+      [dialog, okButton, cancelButton, container, footer]
+
+    welcome: ->
+      extName = __msg 'extName'
+      [dialog, okButton, cancelButton, container, footer] = @createDialog (__msg 'welcomeDialogTitle', extName), [(__msg 'welcomeDialogButtonOk'), (__msg 'welcomeDialogButtonOkTooltip')], [(__msg 'welcomeDialogButtonCancel'), (__msg 'welcomeDialogButtonCancelTooltip', extName)]
+
+      container.append  """
+

+
+                  #{__msg 'welcomeDialogText', extName}
+

+                """
+
+      okButton.on 'click', =>
+        [authDialog, authOkButton, authCancelButton, authContainer, authFooter] = @gettingStartedDialog()
+        container.replaceWith authContainer
+        okButton.replaceWith authOkButton
+        cancelButton.replaceWith authCancelButton
+        dialog.title authDialog.title()
+
+        authOkButton.on 'click', =>
+          @openAuthWindow action: 'setupNoSchedule'
+          dialog.close()
+          return
+
+        authCancelButton.on 'click', dialog.close
+
+      cancelButton.on 'click', dialog.close
+      dialog.open()
+
+    gettingStartedDialogContent: ->
+      extName = __msg 'extName'
+      img = chrome.extension.getURL "images/#{__msg 'authorizeDialogImageLanguage'}/clickToAuthorize.png"
+      """
+

+        #{__msg 'authorizeDialogText', extName}
+

+

+
+

+      """
+
+    gettingStartedDialog: ->
+      extName = __msg 'extName'
+      [dialog, okButton, cancelButton, container, footer] = @createDialog (__msg 'authorizeDialogTitle', extName), [(__msg 'authorizeDialogButtonOk'), (__msg 'authorizeDialogButtonOkTooltip')], [(__msg 'authorizeDialogButtonCancel'), (__msg 'authorizeDialogButtonCancelTooltip', extName)]
+      container.append @gettingStartedDialogContent()
+      [dialog, okButton, cancelButton, container, footer]
+
+    gettingStarted: (params) ->
+      [dialog, okButton, cancelButton, container, footer] = @gettingStartedDialog()
+
+      okButton.on 'click', =>
+        @openAuthWindow params
+        dialog.close()
+        return
+
+      cancelButton.on 'click', dialog.close
+
+      dialog.open()
+
+    openAuthWindow: (params) ->
+      url = @getServiceURL()
+      if params
+        query = $.param params
+        url += "?#{query}"
+      window.open url, M.CLS, 'width=880,height=500,location=0,menubar=0,scrollbars=0,status=0,toolbar=0,resizable=1'
+      return
+
+    onScheduleError: (status, params, error, responseText) =>
+      log 'There was an error', arguments
+      if (status is 'parsererror' and M.isAuthorisationErrorPage responseText) or (M.isAuthorisationErrorResponse status)
+        params ?= {}
+        params.action = 'setup'
+        delete params.callback if params.callback
+        @gettingStarted params
+      else
+        notification =
+          action:   'notification'
+          icon:     "images/tie48x48.png"
+          title:    __msg 'notificationScheduleErrorTitle'
+          message:  if (_.isObject error) and ('code' of error) then (__msg "notificationScheduleError#{error.code}") else (__msg 'notificationScheduleError', '' + new String error)
+
+        chrome.extension.sendMessage notification
+
+      return
+
+  mb = new M
+  return

 ) jQuery, window if top.document is document


Debugging code removed

Debugging code removed

--- coffee/codesearch.coffee
+++ coffee/codesearch.coffee
@@ -18,15 +18,13 @@
                 $scope.model.results = data
                 return
         else
-            console.log "Clearing"
             $scope.model.results = []
         return

     return

-
-modalController = app.controller "ModalCtrl", ($scope, modalService) ->
+modalController = controller.controller "ModalCtrl", ($scope, modalService) ->
     $scope.modal = modalService
     return


Fix SVG renderer typos

Fix SVG renderer typos

--- src/core/svgRenderer.coffee
+++ src/core/svgRenderer.coffee
@@ -87,9 +87,9 @@
       <line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}'
         #{dashString}
         stroke-linecap='#{shape.capStyle}'
-        stroke='#{shape.color}'stroke-width='#{shape.strokeWidth}' />
+        stroke='#{shape.color} 'stroke-width='#{shape.strokeWidth}' />
       #{capString}
-    <g>
+    </g>
   "


Load $locationProvider

Load $locationProvider

--- docs/app/src/application.coffee
+++ docs/app/src/application.coffee
@@ -7,7 +7,8 @@
 ])

-.config ($stateProvider, $urlRouterProvider) ->
+.config ($stateProvider, $urlRouterProvider, $locationProvider) ->
+
   # Now set up the states
   $stateProvider.state 'home',
     url: '/home'


queue open action to until SideNav is initiated

--- packages/rocketchat-ui/lib/sideNav.coffee
+++ packages/rocketchat-ui/lib/sideNav.coffee
@@ -1,9 +1,10 @@
 @SideNav = (->
-
+	initiated = false
 	sideNav = {}
 	flexNav = {}
 	arrow = {}
 	animating = false
+	openQueue = []

 	toggleArrow = (status = null) ->
 		if status is -1 or (status isnt 1 and arrow.hasClass "top")
@@ -61,6 +62,9 @@
 		, 500

 	openFlex = (callback = null) ->
+		if not initiated
+			return openQueue.push { config: getFlex(), callback: callback }
+
 		return if animating == true
 		toggleArrow 1
 		toggleFlex 1, callback
@@ -90,6 +94,14 @@
 		arrow = sideNav.children ".arrow"
 		setFlex ""
 		arrowBindHover()
+		initiated = true
+
+		if openQueue.length > 0
+			openQueue.forEach (item) ->
+				setFlex item.config.template, item.config.data
+				openFlex item.callback
+
+			openQueue = []

 	getSideNav = ->
 		return sideNav


suppressing error

suppressing error

--- src/components/scores/index.cjsx
+++ src/components/scores/index.cjsx
@@ -210,6 +210,7 @@

     rowGetter = (rowIndex) =>
         @renderStudentRow(data.rows[rowIndex])
+
       if data.rows.length > 0

           Student Scores
@@ -224,7 +225,6 @@
               onColumnResizeEndCallback={(colWidth, columnKey) => @setState({colResizeWidth: colWidth, colResizeKey: columnKey})}
               rowHeight={46}
               rowGetter={rowGetter}
-
               rowsCount={data.rows.length}
               width={tableWidth}
               height={tableHeight}
@@ -232,16 +232,16 @@
               groupHeaderHeight={50}>

               {@renderNameHeader()}
-              {_.map(data.headings, @renderHeadingCell)}
-
-

-
+             {_.map(data.headings, @renderHeadingCell)}
+

-

+

       else

         No Assignments Yet
+

+

 ScoresShell = React.createClass
   contextTypes:


add react native import

add react native import

--- snippets/imports.cson
+++ snippets/imports.cson
@@ -43,6 +43,9 @@
   'Import Simple Schema':
     'prefix': 'impsschema'
     'body': 'import {SimpleSchema} from \'meteor/aldeed:simple-schema\''
+  'Import New Simple Schema':
+    'prefix': 'impnewsimpleschema'
+    'body': 'import SimpleSchema from \'simpl-schema\''
   'Import Material Colors':
     'prefix': 'immc'
     'body': 'import * as Colors from \'material-ui/styles/colors\''
@@ -61,6 +64,9 @@
   'Import React':
     'prefix': 'impr'
     'body': 'import React from \'react\''
+  'Import React Native':
+    'prefix': 'imprn'
+    'body': 'import {$1} from \'react-native\''
   'Import Meteor':
     'prefix': 'impme'
     'body': 'import {Meteor} from \'meteor/meteor\''


Clean console message with old version of M&J

--- src/coffee/controllers.coffee
+++ src/coffee/controllers.coffee
@@ -18,12 +18,6 @@
 "\t\t\t   \\:\\  \\     &    /:/  / \n"+
 "\t\t\t    \\:\\__\\        /:/  / \n"+
 "\t\t\t     \\/__/        \\/__/  \n"
-#"\t\t\t ███╗   ███╗       ██╗            ██╗\n"+
-#"\t\t\t ████╗ ████║       ██║            ██║\n"+
-#"\t\t\t ██╔████╔██║    ████████╗         ██║\n"+
-#"\t\t\t ██║╚██╔╝██║    ██╔═██╔═╝    ██   ██║\n"+
-#"\t\t\t ██║ ╚═╝ ██║    ██████║      ╚█████╔╝\n"+
-#"\t\t\t ╚═╝     ╚═╝    ╚═════╝       ╚════╝ \n"

 MARKER_ICONS =


Fixed table view not rendering empty rows correctly. Updated table view comments correctly.

--- src/directives/table_view.coffee
+++ src/directives/table_view.coffee
@@ -1,26 +1,29 @@
-###
- Table view
-
- A directive for generating a lazy rendered table
-
- Dependencies
- - jQuery UI Sortable
- - jQuery UI Resizable
-
- Attributes:
-  - mac-table-data:              Array of objects with table data
-  - mac-table-columns:           Array of columns to display
-  - mac-table-has-header:        A boolean value to determine if header should be shown
-  - mac-table-has-footer:        A boolean value to determine if footer should be shown
-  - mac-table-width:             The width of the whole table
-  - mac-table-column-width:      The minimum width of a column
-  - mac-table-row-height:        The height of each row in the table
-  - mac-table-num-display-rows:  The total number of rows to display
-  - mac-table-cell-padding:      Should match the css padding value on each cell
-  - mac-table-sortable:          Allow user to change the order of columns
-  - mac-table-resizable:         Allow each column to be resized by the user
-  - mac-table-lock-first-column: Lock first column to a static position
-###
+##
+## @name
+## Table view
+##
+## @description
+## A directive for generating a lazy rendered table
+##
+## @dependencies
+## - jQuery UI Sortable
+## - jQuery UI Resizable
+##
+## @attributes
+## - mac-table-data:              Array of objects with table data
+## - mac-table-columns:           Array of columns to display
+## - mac-table-has-header:        A boolean value to determine if header should be shown (default true)
+## - mac-table-has-footer:        A boolean value to determine if footer should be shown (default true)
+## - mac-table-width:             The width of the whole table                           (default 800)
+## - mac-table-column-width:      The minimum width of a column                          (default 140)
+## - mac-table-row-height:        The height of each row in the table                    (default 20)
+## - mac-table-num-display-rows:  The total number of rows to display                    (default 10)
+## - mac-table-cell-padding:      Should match the css padding value on each cell        (default 8)
+## - mac-table-border-width:      Should match the css border width value on each cell   (default 1)
+## - mac-table-sortable:          Allow user to change the order of columns              (default false)
+## - mac-table-resizable:         Allow each column to be resized by the user            (default false)
+## - mac-table-lock-first-column: Lock first column to a static position                 (default false)
+##

 angular.module("Mac").directive "macTable", [
   "$rootScope"
@@ -47,8 +50,8 @@
         numDisplayRows:  10
         cellPadding:     8
         borderWidth:     1
-        sortable:        true
-        resizable:       true
+        sortable:        false
+        resizable:       false
         lockFirstColumn: false

       transcludedBlock = $(".table-transclude", element)
@@ -91,8 +94,11 @@
           # Set column property again
           templateCell.prop "column", column

+          # Calculate the relative width of each cell
           calculatedWidth = (opts.width / numColumns) - opts.cellPadding * 2 - opts.borderWidth
-          width           = Math.max calculatedWidth, opts.columnWidth
+
+          # Use minimum width if the relative width is too small
+          width = Math.max calculatedWidth, opts.columnWidth

           templateCell.css
             padding: opts.cellPadding
@@ -132,6 +138,7 @@
           # Set the header to be the width of all columns
           headerRow.width rowWidth

+          # Create a separate cell if the first cell is locked
           if opts.lockFirstColumn
             fcTemplateCell = createHeaderCellTemplate $scope.columns[0]
             fcTemplateCell.addClass "locked-cell"
@@ -195,9 +202,10 @@
             # Calculate the width of each row
             rowWidth += templateCell.outerWidth() + opts.borderWidth

-            emptyTemplateRow.append createCellTemplate()
+            emptyTemplateRow.append createCellTemplate().addClass "table-cell"

           tableRow.append(tableCell).width rowWidth
+          emptyTemplateRow.width rowWidth
           bodyBlock.append tableRow

           # Compile the body block to bind all values correctly


fixed indententation in dawit

fixed indententation in dawit

--- scripts/dawitistic.coffee
+++ scripts/dawitistic.coffee
@@ -19,9 +19,9 @@

 module.exports = (robot) ->
   robot.hear /dawit?/i, (msg) ->
-      roll = Math.floor(Math.random() * 5) + 1
-          if roll is 1
-            dawitMe msg
+    roll = Math.floor(Math.random() * 5) + 1
+      if roll is 1
+        dawitMe msg

 dawitMe = (msg) ->
   msg.http('http://dawitistics.com/dawitistic.txt')


Fail in ?setPermission if permission is already set

--- handlers/opserv.coffee
+++ handlers/opserv.coffee
@@ -90,8 +90,13 @@

 					db.getUserByUsername username.trim(), (err, user) ->
 						if user?
-							db.givePermissionToUserID user.userID, permission.join('').trim(), (rows) ->
-								api.replyTo message, __("Gave %1$s to “%2$s”", permission, username), no, callback
+							db.hasPermissionByUserID user.userID, permission.join('').trim(), (alreadyHasPermission) ->
+								if alreadyHasPermission
+									api.replyTo message, __("“%2$s” already has got the permission %1$s", permission, username), no, callback
+								else
+									db.givePermissionToUserID user.userID, permission.join('').trim(), (rows) ->
+										api.replyTo message, __("Gave %1$s to “%2$s”", permission, username), no, callback
+
 						else
 							api.replyTo message, __("Could not find user “%s”", username), no, callback
 				else


Remove outdated up/down visual block mode keymappings for Atom.

Up and down arrow keys now work without this mapping.

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -84,11 +84,6 @@
   # visual-mode. Assigning 'backspace' seems to provide the expected
   # behavior.
   'backspace': 'vim-mode-plus:delete-left'
-
-'atom-text-editor.vim-mode-plus.visual-mode.blockwise':
-  # Add up and down key mappings to suppliment j and k in block mode.
-  'down': 'vim-mode-plus:blockwise-move-down'
-  'up':   'vim-mode-plus:blockwise-move-up'

 ###############################################################################
 # vim-mode-visual-block keymappings


introducing new raw, command escaping

introducing new raw, command escaping

--- src/MKTS.coffee
+++ src/MKTS.coffee
@@ -519,98 +519,6 @@
     return null

 #-----------------------------------------------------------------------------------------------------------
-@$_preprocess_commands = ( S ) ->
-  ### TAINT `<xxx>` translates as `(xxx`, which is generally correct, but it should translate
-  to `(xxx)` when `xxx` is a known HTML5 'lone' tag. ###
-  ### TAINT no need for `:` any more; replaced by `{definitions}` ###
-  left_meta_fence     = '<'
-  right_meta_fence    = '>'
-  repetitions         = 2
-  fence_pattern       = ///
-    #{left_meta_fence}{#{repetitions}}
-    (
-      (?:
-        \\#{right_meta_fence}       |
-        [^ #{right_meta_fence} ]    |
-        #{right_meta_fence}{ #{repetitions - 1} } (?! #{right_meta_fence} )
-        )*
-      )
-    #{right_meta_fence}{#{repetitions}}
-    ///
-  prefix_pattern      = ///^ ( [ !: ] ) ( .* ) ///
-  collector           = []
-  track               = @TRACKER.new_tracker '{code}', '(code)', '(latex)', '(latex)'
-  #.........................................................................................................
-  return $ ( event, send ) =>
-    within_literal = track.within '{code}', '(code)', '(latex)', '(latex)'
-    track event
-    [ type, name, text, meta, ] = event
-    if ( not within_literal ) and @select event, '.', 'text'
-      is_command = yes
-      for part in text.split fence_pattern
-        is_command  = not is_command
-        left_fence  = null
-        right_fence = null
-        if is_command
-          last_idx    = part.length - 1
-          left_fence  = part[        0 ] if part[        0 ] in @FENCES.xleft
-          right_fence = part[ last_idx ] if part[ last_idx ] in @FENCES.xright
-          if left_fence? and right_fence?
-            command_name = part[ 1 ... last_idx ]
-            if prefix_pattern.test command_name
-              warn "prefix not supported in #{rpr part}"
-              send [ '?', part, null, ( @copy meta ), ]
-            else
-              send [ left_fence,  command_name, null, ( @copy meta ), ]
-              send [ right_fence, command_name, null, ( @copy meta ), ]
-          else if left_fence?
-            command_name  = part[ 1 ... ]
-            if ( match = command_name.match prefix_pattern )?
-              [ _, prefix, suffix, ] = match
-              switch prefix
-                when ':'
-                  send [ left_fence, prefix, suffix, ( @copy meta ), ]
-                else
-                  warn "prefix #{rpr prefix} not supported in #{rpr part}"
-                  send [ '?', part, null, ( @copy meta ), ]
-            else
-              send [ left_fence, command_name, null, ( @copy meta ), ]
-          else if right_fence?
-            ### TAINT code duplication ###
-            command_name = part[ ... last_idx ]
-            if ( match = command_name.match prefix_pattern )?
-              [ _, prefix, suffix, ] = match
-              # debug '©9nGvB', ( rpr command_name ), ( rpr prefix ), ( rpr suffix )
-              switch prefix
-                when ':'
-                  send [ right_fence, prefix, suffix, ( @copy meta ), ]
-                else
-                  warn "prefix #{rpr prefix} not supported in #{rpr part}"
-                  send [ '?', part, null, ( @copy meta ), ]
-            else
-              send [ right_fence, command_name, null, ( @copy meta ), ]
-          else
-            match = part.match prefix_pattern
-            unless match?
-              warn "not a legal command: #{rpr part}"
-              send [ '?', part, null, ( @copy meta ), ]
-            else
-              [ _, prefix, suffix, ] = match
-              switch prefix
-                when '!'
-                  send [ '!', suffix, null, ( @copy meta ), ]
-                else
-                  warn "prefix #{rpr prefix} not supported in #{rpr part}"
-                  send [ '?', part, null, ( @copy meta ), ]
-        else
-          send [ type, name, part, ( @copy meta ), ]
-    #.......................................................................................................
-    else
-      send event
-    #.......................................................................................................
-    return null
-
-#-----------------------------------------------------------------------------------------------------------
 @$_process_end_command = ( S ) ->
   S.has_ended   = no
   remark        = @_get_remark()
@@ -1077,21 +985,5 @@
   #.........................................................................................................
   return R

-### TAINT currently not used, but 'potentially useful'
-#-----------------------------------------------------------------------------------------------------------
-@_meta  = Symbol 'meta'
-
-#-----------------------------------------------------------------------------------------------------------
-@set_meta = ( x, name, value = true ) ->
-  target          = x[ @_meta ]?= {}
-  target[ name ]  = value
-  return x
-
-#-----------------------------------------------------------------------------------------------------------
-@get_meta = ( x, name = null ) ->
-  R = x[ @_meta ]
-  R = R[ name ] if name
-  return R
-###
-
-
+
+


introducing new raw, command escaping

introducing new raw, command escaping


cleanup

cleanup

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -334,13 +334,7 @@
     bus.end()
     input.push("b")
     expect(events).toEqual([new Bacon.Next("a"), new Bacon.End()])
-  it "Bus delivers pushed events", ->
-    bus = new Bacon.Bus()
-    values = []
-    bus.onValue((v) => values.push(v))
-    bus.push(1)
-    expect(values).toEqual([1])
-  it "delivers pushed events (test2)", ->
+  it "delivers pushed events", ->
     expectStreamEvents(
       ->
         s = new Bacon.Bus()


Event types matched for autocompletion can be a one-way binding.

(The autocomplete dropdown won't be changing that list.)

--- app/assets/javascripts/controllers/event_search_criteria_controller.coffee
+++ app/assets/javascripts/controllers/event_search_criteria_controller.coffee
@@ -10,7 +10,7 @@

   # Now we can bind.
   eventTypeCriterionBinding: 'controllers.eventTypes.criterion'
-  eventTypeCandidatesBinding: 'controllers.eventTypes.candidates'
+  eventTypeCandidatesBinding: Ember.Binding.oneWay 'controllers.eventTypes.candidates'

   search: ->
     @get('content').search()


fix epics table tests

fix epics table tests

--- app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee
+++ app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee
@@ -36,11 +36,30 @@
         }
         provide.value "tgEpicsService", mocks.tgEpicsService

+    _mockTgProjectService = () ->
+        mocks.tgProjectService = {
+            project: Immutable.fromJS({
+                'id': 3
+            })
+        }
+        provide.value "tgProjectService", mocks.tgProjectService
+
+    _mockTgStorageService = () ->
+        mocks.tgStorage = {
+            get: sinon.stub(),
+            set: sinon.spy()
+        }
+
+        mocks.tgStorage.get.returns({col1: true})
+        provide.value "$tgStorage", mocks.tgStorage
+
     _mocks = () ->
         module ($provide) ->
             provide = $provide
             _mockTgConfirm()
             _mockTgEpicsService()
+            _mockTgStorageService()
+            _mockTgProjectService()
             return null

     beforeEach ->
@@ -63,3 +82,8 @@
         epicTableCtrl.nextPage()

         expect(mocks.tgEpicsService.nextPage).to.have.been.calledOnce
+
+    it "storage view options", () ->
+        epicTableCtrl = controller "EpicsTableCtrl"
+
+        expect(epicTableCtrl.column).to.be.eql({col1: true})


Add another mixed test with CSS first

Add another mixed test with CSS first

--- spec/preparser.coffee
+++ spec/preparser.coffee
@@ -110,3 +110,42 @@
       chai.expect(statements[0]).to.be.an 'array'
       chai.expect(statements[0].length).to.equal 2
       chai.expect(statements[0][0]).to.equal 'css'
+
+  describe 'with mixed CSS, CCSS, VFL, and GTL. CSS first', ->
+    source = """
+    h1 {
+      color: red;
+    }
+    /* Here we define some constraints */
+    #box1.width >= #box2.width;
+    @-gss-horizontal |-[#box1]-[#button1]-| in(#dialog);
+    /* And then we lay it all out */
+    @-gss-layout "frontpageLayout" {
+      grid: "aaab"
+            "aaab"
+            "cccc";
+      place-a: "#box1" "#box1";
+    }
+    """
+    statements = null
+    it 'should produce a statement array', ->
+      statements = parser.parse source
+      chai.expect(statements).to.be.an 'array'
+    it 'it should include four statements', ->
+      chai.expect(statements.length).to.equal 4
+    it 'the first one should be CSS', ->
+      chai.expect(statements[0]).to.be.an 'array'
+      chai.expect(statements[0].length).to.equal 2
+      chai.expect(statements[0][0]).to.equal 'css'
+    it 'the second one should be CCSS', ->
+      chai.expect(statements[0]).to.be.an 'array'
+      chai.expect(statements[0].length).to.equal 2
+      chai.expect(statements[0][0]).to.equal 'ccss'
+    it 'the third one should be VFL', ->
+      chai.expect(statements[0]).to.be.an 'array'
+      chai.expect(statements[0].length).to.equal 2
+      chai.expect(statements[0][0]).to.equal 'vfl'
+    it 'the fourth one should be GTL', ->
+      chai.expect(statements[0]).to.be.an 'array'
+      chai.expect(statements[0].length).to.equal 2
+      chai.expect(statements[0][0]).to.equal 'gtl'


tests for virtal elements, no parser changes required!

--- spec/preparser.coffee
+++ spec/preparser.coffee
@@ -17,7 +17,16 @@
       expect(statements).to.be.an 'array'
     it '/ should parse correctly', ->
       expect(statements).to.eql target
-
+
+canParse = (name,source) ->
+  statements = null
+  describe name, ->
+    it '/ should parse', ->
+      #parser.constraintCount = 0
+      #parser.blockCount = 0
+      statements = parser.parse source
+      expect(statements).to.be.an 'array'
+

 describe 'GSS preparser ~', ->

@@ -281,7 +290,7 @@
               selectors: ['h1']
               rules: [
                 {type:'directive',name:"vertical",terms:".word"}
-                {type:'style', key:'color', val: 'red'}
+                {type:'style', key:'color', val:'red'}
                 {type:'constraint',cssText:'::[height] == ::parent[height];'}
                 {
                   type: "ruleset"
@@ -298,7 +307,84 @@
         {type:'directive',name:"horizontal",terms:".thought in(.brain)"}
       ]

-    test '@if @else w/ nesting',
+
+    test '@if @else w/ nesting & virtuals',
+      """
+
+      @horizontal |-["main"]-["side"]-| in("window") gap("col"[size]);
+      60 =< "col"[size] <= "window" / 12;
+
+      "main" {
+        @if "this"[width] >= 960 {
+          @vertical .post gap(40);
+        }
+        @else {
+          "post" {
+            height: <= "window"[height] / 2 !strong;
+            font-size: 14px;
+          }
+        }
+      }
+
+      """,
+      [
+         {
+            "type": "directive",
+            "name": "horizontal",
+            "terms": '|-["main"]-["side"]-| in("window") gap("col"[size])'
+         },
+         {
+            "type": "constraint",
+            "cssText": '60 =< "col"[size] <= "window" / 12;'
+         },
+         {
+            "type": "ruleset",
+            "selectors": [
+               '"main"'
+            ],
+            "rules": [
+               {
+                  "type": "directive",
+                  "name": "if",
+                  "terms": '"this"[width] >= 960',
+                  "rules": [
+                     {
+                        "type": "directive",
+                        "name": "vertical",
+                        "terms": ".post gap(40)"
+                     }
+                  ]
+               },
+               {
+                  "type": "directive",
+                  "name": "else",
+                  "terms": "",
+                  "rules": [
+                     {
+                        "type": "ruleset",
+                        "selectors": [
+                           '"post"'
+                        ],
+                        "rules": [
+                           {
+                              "type": "constraint",
+                              'cssText': '::[height] <= "window"[height] / 2 !strong;'
+                           },
+                           {
+                              "type": "style",
+                              "key": "font-size",
+                              "val": "14px"
+                           }
+                        ]
+                     }
+                  ]
+               }
+            ]
+         }
+      ]
+
+
+    test '@if @else w/ nesting & ',
       """

       @horizontal |-[#main]-[#side]-| in(::window) gap([col-size]);
@@ -432,5 +518,33 @@
         {type:'constraint',cssText:"#box1.width >= #box2.width;"}
         {type:'directive', name:'horizontal', terms:"|-[#box1]-[#button1]-| in(#dialog)"}
       ]
+
+
+
+  describe "Can parse...", ->
+
+    canParse "mixed",
+      """
+      @horizontal [#b1][#b2];
+
+      #box[right] == #box2[left];
+
+      #main {
+
+        line-height: >= [col-size];
+
+        @if [target] >= 960 {
+          width: == [big];
+        }
+        @else [target] >= 500 {
+          width: == [med];
+        }
+        @else {
+          right: == ::window[left];
+        }
+
+      }
+
+      """


disable tests

disable tests

--- spec/todo-spec.coffee
+++ spec/todo-spec.coffee
@@ -1,7 +1,8 @@
 Todo = require '../lib/todo'
 helper = require './helper'
+service = require '../lib/service';

-describe "Todo", ->
+xdescribe "Todo", ->
   [workspaceElement, activationPromise, todoElement] = []

   beforeEach ->
@@ -41,7 +42,7 @@
         atom.commands.dispatch workspaceElement, 'todo:toggle'
         expect(todoElement).not.toBeVisible()

-fdescribe 'when searching for todo statements', ->
+xdescribe 'when searching for todo statements', ->
   beforeEach ->
     helper.createFile 'foo.txt', [
       {
@@ -67,5 +68,10 @@
     helper.removeFiles()

   it 'should omit node_modules', ->
-    resultsCount
-    expect(resultsCount).toBe(2)
+    items = []
+
+    waitsForPromise ->
+      service.findTodoItems().then((todoItems) -> items = todoItems)
+
+    runs ->
+      expect(resultsCount).toBe(2)


logToMixpanel added

logToMixpanel added

--- client/app/MainApp/mixpanel.coffee
+++ client/app/MainApp/mixpanel.coffee
@@ -65,5 +65,10 @@
       "$loginDate"  : Date.now()

+KD.logToMixpanel = (args, percent=100)->
+    if KD.utils.runXpercent percent
+      mixpanel.track args
+      log "Log #{percent}% of time: #{args}"
+
 if mixpanel? && KD.config.logToExternal then do ->
   KD.mixpanel = new KDMixpanel


Remove unused plugin

Remove unused plugin

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,7 +22,6 @@

   # Grunt plugins used for testing
   @loadNpmTasks 'grunt-mocha-phantomjs'
-  @loadNpmTasks 'grunt-coffeelint'

   # Our local tasks
   @registerTask 'build', 'Build NoFlo for the chosen target platform', (target = 'all') =>


Transpile only just before browser tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -132,11 +132,11 @@

   @registerTask 'test', 'Build NoFlo and run automated tests', (target = 'all') =>
     @task.run 'coffeelint'
-    @task.run 'coffee'
     if target is 'all' or target is 'nodejs'
       @task.run 'cafemocha'
     if target is 'all' or target is 'browser'
       @task.run 'connect'
+      @task.run 'coffee'
       @task.run 'noflo_browser'
       @task.run 'mocha_phantomjs'


Remove Grunt symlink target to extensions directory

This is not applicable since extensions should be developed externally
from Cilantro core.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -180,10 +180,6 @@
             templates:
                 relativeSrc: '../../<%= srcDir %>/templates'
                 dest: '<%= localDir %>/js/templates'
-
-            extensions:
-                relativeSrc: '../../<%= srcDir %>/extensions'
-                dest: '<%= localDir %>/js/extensions'

         requirejs:


Add source maps to Gruntfile

Add source maps to Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,7 +3,7 @@
     pkg: grunt.file.readJSON "package.json"
     clean:
       main:
-        ["dist/{/,routes/,bin/}*.js"]
+        ["dist/{/,routes/,bin/}*{.js,.js.map}"]
     coffee:
       main:
         files: [
@@ -12,17 +12,20 @@
           dest: "dist/",
           ext: ".js"
         ]
+        options:
+          sourceMap: true
+          sourceMapDir: "dist/sourcemap"
     copy:
       main:
         files: [
           expand: true,
-          src: ["public/**", "views/**", "node/**", "START.cmd", "INSTALL.cmd"],
+          src: ["public/**", "views/**", "node/**", "START.cmd", "INSTALL.cmd", "package.json"],
           dest: "dist/"
         ]
       fast:
         files: [
           expand: true,
-          src: ["public/**", "views/**", "START.cmd", "INSTALL.cmd"],
+          src: ["public/**", "views/**", "START.cmd", "INSTALL.cmd", "package.json"],
           dest: "dist/"
         ]


Fix on Gruntfile

Fix on Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,27 +14,18 @@
         'src/quo.coffee'
         'src/quo.*.coffee'
       ]
-      widgets: [
-        'src/gestures/quo.gestures.coffee'
-        'src/gestures/quo.gestures.*.coffee'
-      ]

     coffee:
-      app:      files: 'package/<%= meta.file %>.debug.js'        : ['<%= resources.app %>']
-      # widgets:  files: 'package/<%= meta.file %>.widget.debug.js' : ['<%= resources.widgets %>']
+      app: files: 'package/<%= meta.file %>.debug.js': ['<%= resources.app %>']

     uglify:
       options: compress: false, banner: "<%= meta.banner %>"
-      app:      files: 'package/<%= meta.file %>.js'        : 'package/<%= meta.file %>.debug.js'
-      # widgets:  files: 'package/<%= meta.file %>.widget.js' : 'package/<%= meta.file %>.widget.debug.js'
+      app: files: 'package/<%= meta.file %>.js': 'package/<%= meta.file %>.debug.js'

     watch:
       app:
         files: ['<%= resources.app %>']
         tasks: ["coffee:app"]
-      # widgets:
-      #   files: ['<%= resources.widgets %>']
-      #   tasks: ["coffee:widgets"]

   grunt.loadNpmTasks "grunt-contrib-coffee"
   grunt.loadNpmTasks "grunt-contrib-uglify"


add more browsers

add more browsers

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -9,13 +9,13 @@
   pkg = grunt.file.readJSON "package.json"

   browsers = []
-
+  browsers.push browser for browser in ({browserName: "iphone", version: "#{vers}"} for vers in [4, "5.0", "5.1", "6.0", "6.1", 7])
+  browsers.push browser for browser in ({browserName: "android", version: "#{vers}"} for vers in ["4.0"])
+  browsers.push browser for browser in ({browserName: "safari", version: "#{vers}"} for vers in [5..7])
+  browsers.push browser for browser in ({platform: "XP", browserName: "opera", version: "#{vers}"} for vers in [11..12])
   browsers.push browser for browser in ({platform: "XP", browserName: "googlechrome", version: "#{vers}"} for vers in [26..31])
-
   browsers.push browser for browser in ({browserName: "firefox", version: "#{vers}"} for vers in [3..26])
-
   browsers.push browser for browser in ({browserName: "internet explorer", version: "#{vers}"} for vers in [6..11])
-

   grunt.initConfig


Update build setup

Update build setup

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,7 +19,7 @@
       install:
         command: './node_modules/.bin/component install -d'
       build:
-        command: './node_modules/.bin/component build -u component-json,component-coffee -o browser -n noflo-runtime-iframe -c -d'
+        command: './node_modules/.bin/component build -u component-json,component-coffee -o browser -n noflo-runtime-iframe -d'

     # Automated recompilation and testing when developing
     watch:


Fix brunch build; urequire doesn't need to run for it.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -97,6 +97,15 @@
           src: '**/*.js'
         ]

+        options:
+          processContent: (content, path) ->
+            name = ///temp/(.*)\.js///.exec(path)[1]
+            """
+            require.define({'#{name}': function(exports, require, module) {
+            #{content}
+            }});
+            """
+
       amd:
         files: [
           expand: true
@@ -355,7 +364,6 @@

   grunt.registerTask 'build:brunch', [
     'coffee:compile'
-    'urequire'
     'copy:commonjs'
     'concat:brunch'
     'uglify:brunch'


build once on watch

build once on watch

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -46,6 +46,11 @@

 	# Tasks
 	# =====
+	grunt.registerTask 'default', ->
+		grunt.task.run 'concat'
+		grunt.task.run 'uglify'
+		grunt.task.run 'watch'
+
 	grunt.registerTask 'build', ->
 		# Build for release
 		grunt.task.run 'concat'


Don't restart when these files change

Don't restart when these files change

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -30,7 +30,17 @@
       dev:
         script: "app.coffee"
         options:
-          ignore: ["./public/*"]
+          ignore: [ # Do not restart when these files change
+            ".gitignore"
+            "./.git"
+            "./public/*"
+            "./src/*"
+            "*.sublime-project"
+            "*.sublime-workspace"
+            "./LICENSE"
+            "./package.json"
+            "./README.md"
+          ]
           env:
             DEBUG: "karma:*"
             PORT: "3436"


Finder: pass all information to filetree node when uploadProgress happens

--- client/finder/AppController.coffee
+++ client/finder/AppController.coffee
@@ -65,9 +65,9 @@
       .on 'drop', ->
         uploaderPlaceholder.hide()

-      .on 'uploadProgress', ({ file, percent }) ->
+      .on 'uploadProgress', ({ file, progress }) ->
         filePath = "[#{file.machine.uid}]#{file.path}"
-        treeController.nodes[filePath]?.showProgressView percent
+        treeController.nodes[filePath]?.showProgressView progress

       .on 'uploadComplete', ({ parentPath }) ->
         controller.expandFolders parentPath


feat(grunt): minify and uglify on build

feat(grunt): minify and uglify on build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -122,10 +122,10 @@
     'karma:ci:start'
   ]

-  # Compile, don't build, as we don't want the files to be minified
+  # Build -- minify and uglify
   grunt.registerTask 'build', [
     'clean'
-    'exec:compile'
+    'exec:build'
     'copy:404'
     'docker'
   ]


fix(grunt): avoid cleaning empty files

fix(grunt): avoid cleaning empty files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -488,6 +488,9 @@
         src: "<%= amsf.base %>/themes/<%= amsf.theme.current %>"

     cleanempty:
+      options:
+        files: false
+
       dist:
         src: ["<%= config.dist %>/**/*"]


Tests: Added routes.loadIDENotFound.

Tests: Added routes.loadIDENotFound.

--- client/ide/test/routes.test.coffee
+++ client/ide/test/routes.test.coffee
@@ -215,3 +215,21 @@

       expect(storage.getValue).toHaveBeenCalled()
       expect(workspace).toEqual undefined
+
+
+  describe '.loadIDENotFound', ->
+
+    it 'should tell ide instance to createMachineStateModal with NotFound state', ->
+
+      fakeApp        = {}
+      { appManager } = kd.singletons
+
+      expect.spyOn(appManager, 'open').andCall (appName, options, callback) -> callback fakeApp
+      expect.spyOn appManager, 'tell'
+
+      routes.loadIDENotFound()
+
+      expect(fakeApp.amIHost).toBe yes
+      expect(appManager.open).toHaveBeenCalled()
+      expect(appManager.tell).toHaveBeenCalledWith 'IDE', 'createMachineStateModal', state: 'NotFound'
+


Restore fast: always

Restore fast: always

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -798,7 +798,7 @@
         noImplicitAny: true
         sourceMap: false
         module: 'commonjs'
-        fast: 'watch'
+        fast: 'always'
         rootDir: '.'
         allowJs: true


fixed merge issues

fixed merge issues

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -117,15 +117,10 @@
         #if Player on star
              @game.add.tween(star).to({alpha:1},200,Phaser.Easing.Quintic.Out,true)
      	 else
-<<<<<<< HEAD
-       #if Player not on star
-     	       @game.add.tween(star).to({alpha:0.1},15000,Phaser.Easing.Quintic.Out,true)
-               #@game.Tween.removeAllTweens();
-=======
+
        #if Player not on star
                @game.add.tween(star).to({alpha:0.1},15000,Phaser.Easing.Quintic.Out,true)
                #@game.Tween.removeAllTweens()
->>>>>>> 693bfe230094cc82da6676c691012f62eadbed6c
      	 if won
      	    if star.alpha < 0.35
      	       won = false


change acceptance test to use big naught list of strings


Namespace entity

Namespace entity

--- source/atoms.coffee
+++ source/atoms.coffee
@@ -1,12 +1,12 @@
 # Globals
 Atoms = @Atoms =
-  version   : "0.2.13"
+  version   : "0.2.29"
   Core      : {}
   Class     : {}

   Atom      : {}
   Molecule  : {}
   Organism  : {}
-  Template  : {}
+  Entity    : {}
   # DOM Handler Facade
   $: (args...) -> if $$? then $$ args... else $ args...


half-baked checkin to refactor bundles

half-baked checkin to refactor bundles

in this version:
 - bundles are being written to a separate file
 - 'templateCommands' is in the process of being changed to
   'configCommands'

--- csi.coffee
+++ csi.coffee
@@ -212,7 +212,7 @@
   for test in tests
     console.log("http://#{host}:#{port}/#{test.replace(/\.js$/, "")}")

-templateCommands =
+configCommands =
   requirejs: (config) ->
     join config.baseUrl || "", config.paths.csi, "require.js"
   extra: () ->
@@ -235,11 +235,12 @@
       optimizations
     , {}
   baseurl: (config) -> argv.baseurl
-  all: (config) ->
-    _.reduce(templateCommands, ((templateObj, cmd, name) ->
-      return templateObj if name is "all"
-      templateObj[name] = templateCommands[name](config)
-      return templateObj
+  all: (config, which) ->
+    _.reduce(configCommands, ((configObj, cmd, name) ->
+      return configObj if name is "all"
+      if which == null or which[name]
+        configObj[name] = configCommands[name](config)
+      configObj
     ), {})

 buildProfile = () ->
@@ -302,13 +303,13 @@
       log "available tests:"
       listTests tests, argv.host, argv.port

-  template:
+  config:
     description: """
     output html snippets for things like the require.js path, can be any of:
-    #{(" - " + cmd + "\n" for cmd of templateCommands).join("")}
+    #{(" - " + cmd + "\n" for cmd of configCommands).join("")}
     """.replace(/\n$/, "")
     action: (cmd, args...) ->
-      console.log templateCommands[cmd](getConfig())
+      console.log configCommands[cmd](getConfig())

   build:
     description: """
@@ -424,11 +425,20 @@
       # this must be after the optimization step, since it checks for optimized
       # files and includes them in the config
       if argv.templatepath
-        templateObj = templateCommands.all config
         provide argv.templatepath
+
+        templateObj = configCommands.all config,
+          require: true
+          extra: true
+          config: true
+          baseurl: true
         contextjsonname = join(argv.templatepath, argv.contextjsonname)
-        log "writing context json to #{contextjsonname}"
+        log "writing template context to #{contextjsonname}"
         write contextjsonname, JSON.stringify(templateObj)
+
+        stringBundleName = join(argv.templatepath, "strings.yaml")
+        log "writing string bundle to #{stringBundleName}"
+        write stringBundleName, JSON.stringify(allStringBundles(), null, "    ")

   completion:
     description: """


Client: Activity: Test: Added getMockMessages method

--- client/mocks/mockingjay.coffee
+++ client/mocks/mockingjay.coffee
@@ -1,20 +1,20 @@
-kd               = require 'kd'
-expect              = require 'expect'
-remote              = require('app/remote').getInstance()
-FSFile              = require 'app/util/fs/fsfile'
-Machine             = require 'app/providers/machine'
-ideRoutes           = require 'ide/routes.coffee'
-dataProvider        = require 'app/userenvironmentdataprovider'
-mockjaccount        = require './mock.jaccount'
-mockjgroup          = require './mock.jgroup'
-mockjmachine        = require './mock.jmachine'
-mockjworkspace      = require './mock.jworkspace'
-mockReactComponent  = require './mock.reactComponent'
-mockMessage         = require 'app/util/generateDummyMessage'
-mockChannel         = require 'app/util/generateDummyChannel'
-mockThread          = require 'app/util/generateDummyThread'
-mockParticipants    = require 'app/util/generateDummyParticipants'
-
+kd                 = require 'kd'
+expect             = require 'expect'
+remote             = require('app/remote').getInstance()
+FSFile             = require 'app/util/fs/fsfile'
+Machine            = require 'app/providers/machine'
+ideRoutes          = require 'ide/routes.coffee'
+dataProvider       = require 'app/userenvironmentdataprovider'
+mockjaccount       = require './mock.jaccount'
+mockjgroup         = require './mock.jgroup'
+mockjmachine       = require './mock.jmachine'
+mockjworkspace     = require './mock.jworkspace'
+mockReactComponent = require './mock.reactComponent'
+mockMessage        = require 'app/util/generateDummyMessage'
+mockChannel        = require 'app/util/generateDummyChannel'
+mockThread         = require 'app/util/generateDummyThread'
+mockParticipants   = require 'app/util/generateDummyParticipants'
+mockMessages       = require 'app/util/generateDummyMessages'

 mockMachine = new Machine { machine: mockjmachine }
 mockGroup   = remote.revive mockjgroup
@@ -293,3 +293,7 @@
   getMockParticipants: (args...) -> return mockParticipants(args...)

   getMockReactComponent: -> return new mockReactComponent
+
+  getMockMessages: (args...) -> return mockMessages(args...)
+
+


Even more working picker

Even more working picker

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -213,39 +213,11 @@
   }
   getInitialState: -> @getState()

-  openPicker: -> @setState {isPickerVisible: true}
+  togglePicker: -> @setState {isPickerVisible: not @state.isPickerVisible}
   closePicker: -> @setState {isPickerVisible: false}
   setColor: (c) ->
     @props.lc.setColor(@props.colorName, c)
     @setState @getState()
-    @closePicker()
-
-  renderPicker: ->
-    return null unless @state.isPickerVisible
-    {div} = React.DOM
-
-    rows = [("hsl(0, 0%, #{i}%)" for i in [0..100] by 10)]
-    for hue in [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]
-      rows.push("hsl(#{hue}, 100%, #{i}%)" for i in [10..90] by 8)
-
-    (div {className: 'color-picker-popup'},
-      rows.map((row, ix) =>
-        (div {className: 'color-row', style: {width: 20 * row.length}},
-          row.map((cellColor, ix2) =>
-            className = React.addons.classSet
-              'color-cell': true
-              'selected': @state.color == cellColor
-            (div \
-              {
-                className,
-                onClick: => @setColor(cellColor)
-                style: {backgroundColor: cellColor}
-              }
-            )
-          )
-        )
-      )
-    )

   render: ->
     {div, label} = React.DOM
@@ -260,7 +232,7 @@
           className: React.addons.classSet
             'color-well-container': true
             'selected': @state.isPickerVisible
-          onClick: @openPicker
+          onClick: @togglePicker
           style: {
             backgroundColor: 'white'
             position: 'relative'
@@ -278,4 +250,32 @@
         ),
         @renderPicker()
       )
-    )+    )
+
+  renderPicker: ->
+    {div} = React.DOM
+    return null unless @state.isPickerVisible
+
+    rows = [("hsl(0, 0%, #{i}%)" for i in [0..100] by 10)]
+    for hue in [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]
+      rows.push("hsl(#{hue}, 100%, #{i}%)" for i in [10..90] by 8)
+
+    (div {className: 'color-picker-popup'},
+      rows.map((row, ix) =>
+        (div {className: 'color-row', key: ix, style: {width: 20 * row.length}},
+          row.map((cellColor, ix2) =>
+            className = React.addons.classSet
+              'color-cell': true
+              'selected': @state.color == cellColor
+            (div \
+              {
+                className,
+                onClick: => @setColor(cellColor)
+                style: {backgroundColor: cellColor}
+                key: ix2
+              }
+            )
+          )
+        )
+      )
+    )


Add support for new package name format and some cleanup

--- src/Meteor.coffee
+++ src/Meteor.coffee
@@ -32,9 +32,7 @@
       "mongo-url"   : process.env.MONGO_URL || null
       "settings"    : null
       "production"  : false
-      "production"  : false
-      "production"  : false
-      "once"        : false
+      "release"     : null
     }

@@ -49,8 +47,6 @@
   # See baseOpts why it is a function an not an object.
   testPackagesOpts: ->
     {
-      "app": "."
-      "app-packages": true #TODO Add Support for testing all packages within an app that are not symlinks
       "timeout": 120000 # 2 minutes
       "meteor-ready-text": "=> App running at:"
       "meteor-error-text": "Waiting for file change."
@@ -91,20 +87,19 @@
     else
       @opts = require("rc")("spacejam",@opts,->)

+    if not fs.existsSync(process.cwd() + '/.meteor/packages')
+      #log.error "Error: spacejam needs to be run from within a meteor app folder. Exiting."
+      throw new Error("spacejam needs to be run from within a meteor app folder.")
+
     expect(+@opts["port"],"--port is not a number. See 'spacejam help' for more info.").to.be.ok

     @opts["root-url"] ?= Meteor.getDefaultRootUrl(@opts["port"])

     packages = @opts._[1..] # Get packages from command line

-    _testPackages = null
     if packages.length > 0
-      _testPackages = @_globPackages(@opts["app"],packages)
-
-      if !_testPackages.length > 0
-        log.warn "Package not found"
-        @emit "exit",1
-        return
+      _testPackages = @_globPackages(packages)
+      expect(_testPackages).to.have.length.above 0

     args = [
       "--port"
@@ -114,11 +109,9 @@
       "test-packages"
     ]
     args.push(_testPackages) if _testPackages
-    args.push("--production") if @opts["production"]
-    args.push("--once") if @opts["once"]
-    args.push(["--settings",@opts["settings"]]) if @opts["settings"]
-    args.push(["--release",@opts["release"]]) if @opts["release"]
-    args.push(["--deploy",@opts["deploy"]]) if @opts["deploy"]
+    args.push("--production") if @opts.production
+    args.push(["--settings", @opts.settings]) if @opts.settings
+    args.push(["--release", @opts.release]) if @opts.release

     # Remove undefined values from args
     args = _.without(args,undefined)
@@ -131,7 +124,7 @@
     env.MONGO_URL = @opts["mongo-url"] if @opts["mongo-url"]

     options = {
-      cwd: @opts["app"],
+      cwd: process.cwd(),
       env: env,
       detached:false
     }
@@ -170,35 +163,37 @@

   # TODO: Test
-  _globPackages: (app,packages)-> # Use glob to get packages that match the packages arg
+  _globPackages: (packages)-> # Use glob to get packages that match the packages arg
     log.debug "Meteor._globPackages()",arguments
-    expect(app,"@app should be a string").to.be.a "string"
     expect(packages,"@packages should be and array").to.be.an "array"

+    pkgsFolder = process.cwd() + '/packages'
+
+    globOpts = {
+      cwd: pkgsFolder
+    }
+
     matchedPackages = []

-    appPath = path.resolve(app)
-    meteorPath = "#{appPath}/.meteor"
-
-    if fs.existsSync(meteorPath)
-      cwd = "#{appPath}/packages"
-    else
-      cwd = appPath
-
-    globOpts = {
-      cwd: cwd
-    }
-    packages.forEach (globPkg)=>
-      globedPackages = glob.sync(globPkg, globOpts)
-      if globedPackages.length > 0
-        globedPackages.forEach (pkg)->
-          matchedPackages.push(pkg)
-####
-#      else
-#        log.warn "No packages matching #{packages} have been found."
-#        if app is "."
-#          log.warn "Make sure you are running spacejam from a meteor app folder. If not, use --app to specify app folder."
-####
+    packages.forEach (pkgArg)=>
+      if pkgArg.indexOf(':') > 0
+        # It's a package name in the new format, we'll add it as is
+        # TODO: Support globs for this too, by looking up package names inside package.js
+        matchedPackages.push(pkgArg)
+      else if pkgArg.indexOf('/') >= 0
+        # It's a path to a package, we'll add it as is too
+        # TODO: Support globs for this too
+        matchedPackages.push(pkgArg)
+      else
+        # It's a package name, let's find matching package names, if it includes wildcards
+        globedPackages = glob.sync(pkgArg, globOpts)
+        if globedPackages.length > 0
+          globedPackages.forEach (pkg)->
+            matchedPackages.push(pkg)
+        else
+          log.warn "Warning: No packages matching #{pkgArg} have been found. Will add it to the meteor command line anyway, in case it's in PACKAGE_DIRS."
+          # TODO: Support globs in PACKAGE_DIRS too.
+          matchedPackages.push(pkgArg)

     return matchedPackages


Implement getDefaultRootUrl()

Implement getDefaultRootUrl()

--- src/Meteor.coffee
+++ src/Meteor.coffee
@@ -3,6 +3,7 @@
 ChildProcess = require './ChildProcess'
 EventEmitter = require('events').EventEmitter
 glob = require("glob")
+

 class Meteor extends EventEmitter
@@ -14,31 +15,39 @@
     stderr:""
   }

+
+
   # It is a function not an object because of design for testability, so we can modify process.env before each tests.
   defaultOpts: ->
     {
-    "port"        : process.env.PORT || 4096
-    "root-url"    : process.env.ROOT_URL || null
-    "mongo-url"   : process.env.MONGO_URL || null
-    "settings"    : null
-    "production"  : false
-    "once"        : false
+      "port"        : process.env.PORT || 4096
+      "root-url"    : process.env.ROOT_URL || Meteor.getDefaultRootUrl()
+      "mongo-url"   : process.env.MONGO_URL || null
+      "settings"    : null
+      "production"  : false
+      "once"        : false
     }

-  runOpts:{
+
+
+  runOpts: ->
+  {

   }
+
+

   # See defaultOpts why it is a function an not an object.
   testPackagesOpts: ->
     {
-    "app": null
-    "driver-package": "test-in-console"
-    "app-packages": true #TODO Add Support for testing all packages within an app that are not symlinks
-    "timeout": 120000 # 2 minutes
-    "meteor-ready-text": "=> App running at:"
-    "meteor-error-text": "Waiting for file change."
+      "app": null
+      "driver-package": "test-in-console"
+      "app-packages": true #TODO Add Support for testing all packages within an app that are not symlinks
+      "timeout": 120000 # 2 minutes
+      "meteor-ready-text": "=> App running at:"
+      "meteor-error-text": "Waiting for file change."
     }
+

   @exec: ->
@@ -51,13 +60,17 @@
     log.debug "Meteor.run()",arguments
     log.info("Spawning meteor")

+
+
   # @opts
   # @parseCommandLine
   testPackages: (opts,parseCommandLine=true)=>
     log.debug "Meteor.testPackages()"
     log.info("Spawning meteor")
+    expect(opts,"@opts should be an object.").to.be.an "object"
+    expect(parseCommandLine,"@parseCommandLine should be a boolean.").to.be.a "boolean"

-    expect(@childProcess,"ChildProcess is already running").to.be.null
+    expect(@childProcess,"Meteor's child process is already running").to.be.null
     opts = _.extend(@defaultOpts(),opts)
     opts = _.extend(@testPackagesOpts(),opts)

@@ -121,9 +134,23 @@
       @buffer.stderr += data
       @hasErrorText data

+
+
+  @getDefaultRootUrl: ->
+    log.debug "Meteor.getDefaultRootUrl()"
+    port = process.env.SPACEJAM_PORT || # TODO: Remove if unnecessary
+      process.env.PORT ||
+      4096
+    rootUrl = "http://localhost:#{port}/"
+    return rootUrl
+
+
+
+  # TODO: Test
   _globPackages: (app,packages)-> # Use glob to get packages that match the packages arg
     log.debug "Meteor._globPackages()",arguments
-    expect(packages,"Invalid @packages").to.be.an "array"
+    expect(app,"@app should be a string").to.be.a "string"
+    expect(packages,"@packages should be and array").to.be.an "array"

     matchedPackages = []

@@ -149,13 +176,15 @@
       @emit "error"

+
   hasReadyText: ( buffer )=>
     if buffer.lastIndexOf( @testPackagesOpts()["meteor-ready-text"] ) isnt -1
       @emit "ready"

+
+  # TODO: Test
   kill:->
     @childProcess?.child?.kill()

 module.exports = Meteor
-


vm.import() accepts a SR id.

vm.import() accepts a SR id.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -813,7 +813,7 @@

 #---------------------------------------------------------------------

-handleVmImport = $coroutine (req, res, { xapi }) ->
+handleVmImport = $coroutine (req, res, { xapi, srId }) ->
   # Timeout seems to be broken in Node 4.
   # See https://github.com/nodejs/node/issues/3319
   req.setTimeout(43200000) # 12 hours
@@ -825,7 +825,7 @@
     return

   try
-    vm = yield xapi.importVm(req, contentLength)
+    vm = yield xapi.importVm(req, contentLength, { srId })
     res.end(format.response(0, vm.$id))
   catch e
     res.writeHead(500)
@@ -834,19 +834,33 @@
   return

 # TODO: "sr_id" can be passed in URL to target a specific SR
-import_ = $coroutine ({host}) ->
-  xapi = @getXAPI(host)
+import_ = $coroutine ({host, sr}) ->
+  if not sr
+    if not host
+      throw new InvalidParameters('you must provide either host or SR')
+
+    xapi = @getXAPI(host)
+    sr = xapi.pool.$default_SR
+    if not sr
+      throw new InvalidParameters('there is not default SR in this pool')
+  else
+    xapi = @getXAPI(sr)

   return {
-    $sendTo: yield @registerHttpRequest(handleVmImport, { xapi })
+    $sendTo: yield @registerHttpRequest(handleVmImport, {
+      srId: sr._xapiId,
+      xapi
+    })
   }

 import_.params = {
-  host: { type: 'string' }
+  host: { type: 'string', optional: true },
+  sr: { type: 'string', optional: true }
 }

 import_.resolve = {
-  host: ['host', 'host', 'administrate']
+  host: ['host', 'host', 'administrate'],
+  sr: ['sr', 'SR', 'administrate']
 }
 exports.import = import_


clean up jamesfile

clean up jamesfile

--- Jamesfile.coffee
+++ Jamesfile.coffee
@@ -1,5 +1,3 @@
-fs = require 'fs'
-
 james = require 'james'
 jade = require 'james-jade-static'
 stylus = require 'james-stylus'


Updating

Updating

--- packages.cson
+++ packages.cson
@@ -1,5 +1,6 @@
 packages: [
   "dark-bint-syntax"
+  "fusion-ui"
   "file-icons"
   "hyperclick"
   "hyperlink-hyperclick"


fix comments

fix comments

--- server/lib/request.coffee
+++ server/lib/request.coffee
@@ -143,7 +143,7 @@
                     }
                 }
                 """
-        # Usefull to manage device access
+        # Usefull to manage application access
         application:
             all:
                 map: """


баг с плашкой codeforces

баг с плашкой codeforces

--- server/models/user.coffee
+++ server/models/user.coffee
@@ -110,7 +110,10 @@
     logger.info "setting cf login ", @_id, cfLogin
     await @update({$set: {"cf.login": cfLogin}})
     @cf.login = cfLogin
-    @updateCfRating()
+    if(cfLogin != undefined)
+        @updateCfRating()
+    else
+        await @update({$set: {"cf.rating": undefined}})

 usersSchema.methods.setAchieves = (achieves) ->
     logger.info "setting achieves login ", @_id, achieves


Clarify verification logic

Clarify verification logic

--- server/publication.coffee
+++ server/publication.coffee
@@ -90,18 +90,19 @@
     existingPublication = Publications.findOne
       sha256: sha256

+    # Filter importing.by to contain only this person
     if existingPublication?.importing?.by
       existingPublication.importing.by = _.filter existingPublication.importing.by, (importingBy) ->
         return importingBy.person._id is Meteor.personId()

     if existingPublication?.importing?.by?[0]?
-      # This user already has an import, so ask for confirmation or upload
-      return [existingPublication._id, existingPublication.cached]
+      # This person already has an import, so ask for confirmation or upload
+      return [existingPublication._id, if existingPublication.cached then existingPublication._uploadOffsets Meteor.personId() else null]

     if existingPublication?.metadata
       # We already have the PDF, so ask for verification
-      id = existingPublication._id
-      verify = true
+      return [existingPublication._id, existingPublication._uploadOffsets Meteor.personId()]
+
     else if existingPublication?
       # We have the publication but no metadata, so get filename
       Publications.update
@@ -114,10 +115,9 @@
             filename: filename
             temporary: Random.id()
             uploadProgress: 0
-      id = existingPublication._id
-
       # If we have the file, ask for verification. Otherwise, ask for upload
-      verify = existingPublication.cached
+      return [existingPublication._id, if existingPublication.cached then existingPublication._uploadOffsets Meteor.personId() else null]
+
     else
       # We don't have anything, so create a new publication and ask for upload
       id = Publications.insert
@@ -136,12 +136,7 @@
         cached: false
         metadata: false
         processed: false
-      verify = false
-
-    verify = if verify then publication._uploadOffsets Meteor.personId() else null
-
-    return [id, verify]
-
+      return [id, null]

   uploadPublication: (file) ->
     throw new Meteor.Error 401, 'User is not signed in.' unless Meteor.personId()


Add about/* to responsive pages.

Add about/* to responsive pages.

--- lib/middleware/redirect_mobile.coffee
+++ lib/middleware/redirect_mobile.coffee
@@ -38,6 +38,7 @@
 router.get '/professional-buyer*', isResponsive
 router.get '/2016-year-in-art*', isResponsive
 router.get '/article/*', isResponsive
+router.get '/about/*', isResponsive
 router.get TEAM_BLOGS, isResponsive
 router.use redirect
 module.exports = router


Add list of 'batteries included' for demo page

--- lib/react_components/hello_world.cjsx
+++ lib/react_components/hello_world.cjsx
@@ -8,9 +8,11 @@

You're looking at the Coffeescript React Quickstart project by Kyle Mathews.

It has a number of nice goodies included like:

-
Full Coffeescript support provided by coffee-react-transform

-
Live reloading for both CSS and Javascript! This really speeds up your development. Live reload of Javascript enabled by the react-hot-loader project.

-
Gulpfile with both development and production tasks to simplify building your project.

+
Live reloading for both CSS and Javascript! This really speeds up your development. Live reloading is powered by the Webpack module bundler and the react-hot-loader projects.

+
Full Coffeescript for React support provided by coffee-react-transform

+
Amazing URL-driven-development (UDD) with the react-router project

+
Uses Gulp for building CSS and Javascript. Run `cult watch` for rebuilding css/js on the fly while developing and `cult build` to create minified versions to deploy to production.

Starter Sass project with sensible defaults. Check them out on the <Link to="styleguide">styleguide page</Link>

+
Uses the best of breed grid system Susy.