Remove the server list functionality from the index.coffee and comment some features

--- www/coffee/index.coffee
+++ www/coffee/index.coffee
@@ -1,68 +1,4 @@
-currentView = 'start'
 token = {}
-
-showView = (view) ->
-	currentView = view
-	$('.view').addClass 'hidden'
-
-	$('#' + view + 'View').removeClass 'hidden'
-
-
-addSwipeEventToOpenServerList = ($el) ->
-	started = undefined
-
-	$el.on 'touchstart', (e) ->
-		if e.originalEvent.touches.length is 2
-			started =
-				date: Date.now()
-				pageX: e.originalEvent.touches[0].pageX
-				pageY: e.originalEvent.touches[0].pageY
-
-	$el.on 'touchmove', (e) ->
-		if started?
-			if Date.now() - started.date < 2000
-				if Math.abs(e.originalEvent.touches[0].pageX - started.pageX) < 50
-					if Math.abs(e.originalEvent.touches[0].pageY - started.pageY) > 100
-						toggleServerList()
-						started = undefined
-
-	$el.on 'touchend', (e) ->
-		started = undefined
-
-
-window.toggleServerList = (open) ->
-	if open is true
-		$(document.body).addClass 'server-list-open'
-	else if open is false
-		$(document.body).removeClass 'server-list-open'
-	else
-		$(document.body).toggleClass 'server-list-open'
-
-
-window.refreshServerList = ->
-	ul = document.querySelector("#serverList ul")
-
-	while ul.children[0]
-		ul.children[0].remove()
-
-	for server in Servers.getServers()
-		li = """
-

-
#{server.name}

-
X

-

-		"""
-
-		ul.appendChild $(li)[0]
-
-
-	li = document.createElement('LI')
-
-	li.className = 'addServer'
-	li.innerText = '+'
-
-	ul.appendChild li
-

 registerServer = ->
 	serverAddress = $('#serverAddress').val().trim().toLowerCase()
@@ -100,104 +36,70 @@
 					$('.loading-text').text "Loading #{name}..."
 					Servers.save()
 					Servers.startServer serverAddress, ->
-						showView 'server'
+						#
 				else
 					$('.loading-text').html "Downloading files...
( #{status.count} / #{status.total} )"
 	, 250

-onIframeLoad = ->
-	$(document.body).removeClass 'loading'
-	iframeDocument = $($('iframe').contents()[0])
-	iframe = $('iframe')[0]
-
-	iframe.contentWindow.facebookConnectPlugin = facebookConnectPlugin
-	iframe.contentWindow.PushNotification = PushNotification
-	iframe.contentWindow.device = device
-	iframe.contentWindow.open = window.open
-	iframe.contentWindow.pushToken = token
-
-	$(iframeDocument).on 'click', 'a[href^="http"]', (e) ->
-		url = $(this).attr('href')
-		window.open(url, '_system')
-		e.preventDefault()
-
-	iframe.contentWindow.addEventListener 'onNewVersion', (e) ->
-		if Servers.getActiveServer().info.version is e.detail
-			return
-
-		if not confirm('There is a new version available, do you want to update now?')
-			return
-
-		$(document.body).addClass 'loading'
-		$('.loading-text').text 'Updating files...'
-		serverAddress = Servers.getActiveServer().url
-		name = Servers.getActiveServer().name
-		Servers.updateServer serverAddress, (status) ->
-			if status.done is true
-				$('.loading-text').text "Loading #{name}..."
-				Servers.save()
-				Servers.startServer serverAddress, ->
-					showView 'server'
-			else
-				$('.loading-text').html "Updating files...
( #{status.count} / #{status.total} )"
-
-	started = undefined
-
-	addSwipeEventToOpenServerList iframeDocument
-
-	url = Servers.getActiveServer().url
-
-	# Save all localStorage records from inframe in the main
-	# localStorage as an objetc of key:value under the key (url)
-	iframe.contentWindow.localStorage.setItem = (key, value) ->
-		data = JSON.parse localStorage.getItem(url) or '{}'
-		data[key] = value
-		localStorage.setItem url, JSON.stringify(data)
-
-		if key is 'android_senderID' and value?
-			localStorage.setItem key, value
-			configurePush()
-
-	# Respond inframe localStorage from the main localStorage
-	# getting from the server object
-	iframe.contentWindow.localStorage.getItem = (key) ->
-		data = JSON.parse localStorage.getItem(url) or '{}'
-		return data[key]
-
-
-onServerClick = (e) ->
-	toggleServerList(false)
-	target = $(e.currentTarget)
-	$(document.body).addClass 'loading'
-	$('.loading-text').text "Loading #{target.data('name')}..."
-	setTimeout ->
-		showView 'server'
-		Servers.startServer target.data('url'), ->
-			Servers.setActiveServer target.data('url')
-	, 200
-
-
-onServerDeleteClick = (e) ->
-	target = $(e.currentTarget)
-	if confirm("Delete server #{target.data('name')}")
-		activeServer = Servers.getActiveServer()
-		Servers.deleteServer target.data('url')
-		if activeServer.url is target.data('url')
-			toggleServerList(false)
-			setTimeout ->
-				refreshServerList()
-				showView 'start'
-			, 200
-		else
-			refreshServerList()
-
-
-onAddServerClick = ->
-	toggleServerList(false)
-	setTimeout ->
-		showView 'start'
-	, 200
+# onIframeLoad = ->
+# 	$(document.body).removeClass 'loading'
+# 	iframeDocument = $($('iframe').contents()[0])
+# 	iframe = $('iframe')[0]
+
+# 	iframe.contentWindow.facebookConnectPlugin = facebookConnectPlugin
+# 	iframe.contentWindow.PushNotification = PushNotification
+# 	iframe.contentWindow.device = device
+# 	iframe.contentWindow.open = window.open
+# 	iframe.contentWindow.pushToken = token
+
+# 	$(iframeDocument).on 'click', 'a[href^="http"]', (e) ->
+# 		url = $(this).attr('href')
+# 		window.open(url, '_system')
+# 		e.preventDefault()
+
+# 	iframe.contentWindow.addEventListener 'onNewVersion', (e) ->
+# 		if Servers.getActiveServer().info.version is e.detail
+# 			return
+
+# 		if not confirm('There is a new version available, do you want to update now?')
+# 			return
+
+# 		$(document.body).addClass 'loading'
+# 		$('.loading-text').text 'Updating files...'
+# 		serverAddress = Servers.getActiveServer().url
+# 		name = Servers.getActiveServer().name
+# 		Servers.updateServer serverAddress, (status) ->
+# 			if status.done is true
+# 				$('.loading-text').text "Loading #{name}..."
+# 				Servers.save()
+# 				Servers.startServer serverAddress, ->
+# 					showView 'server'
+# 			else
+# 				$('.loading-text').html "Updating files...
( #{status.count} / #{status.total} )"
+
+# 	started = undefined
+
+# 	addSwipeEventToOpenServerList iframeDocument
+
+# 	url = Servers.getActiveServer().url
+
+# 	# Save all localStorage records from inframe in the main
+# 	# localStorage as an objetc of key:value under the key (url)
+# 	iframe.contentWindow.localStorage.setItem = (key, value) ->
+# 		data = JSON.parse localStorage.getItem(url) or '{}'
+# 		data[key] = value
+# 		localStorage.setItem url, JSON.stringify(data)
+
+# 		if key is 'android_senderID' and value?
+# 			localStorage.setItem key, value
+# 			configurePush()
+
+# 	# Respond inframe localStorage from the main localStorage
+# 	# getting from the server object
+# 	iframe.contentWindow.localStorage.getItem = (key) ->
+# 		data = JSON.parse localStorage.getItem(url) or '{}'
+# 		return data[key]

 serverAddressInput = ->
@@ -218,94 +120,92 @@
 	$('#alert-messages').append "
#{alertObj.message}
"

-window.configurePush = ->
-	# if window.push?
-	# 	return console.log 'already registered'
-
-	if device.platform.toLowerCase() is 'android' and not localStorage.getItem('android_senderID')?
-		return console.log 'no SenderID'
-
-	config =
-		ios:
-			alert: "true"
-			badge: "true"
-			sound: "true"
-
-	if device.platform.toLowerCase() is 'android'
-		config.android =
-			senderID: localStorage.getItem('android_senderID')
-			sound: true
-			vibrate: true
-
-	window.push = PushNotification.init config
-
-	push.on 'registration', (data) ->
-		if device.platform.toLowerCase() is 'android'
-			token.gcm = data.registrationId
-		else
-			token.apn = data.registrationId
-
-	push.on 'notification', (data) ->
-		if data.additionalData.foreground is true
-			return
-
-		if typeof data.additionalData.ejson is 'string'
-			data.additionalData.ejson = JSON.parse data.additionalData.ejson
-
-		host = data.additionalData.ejson.host
-		if not host?
-			return
-
-		host = host.replace /\/$/, ''
-		if Servers.serverExists(host) isnt true
-			return
-
-		iframe = $('iframe')[0]
-		if Servers.getActiveServer().url is host and currentView isnt 'start'
-			iframe.contentWindow.dispatchEvent new CustomEvent 'push-notification', {detail: data}
-		else
-			Servers.startServer host, (err, url) ->
-				if err?
-					# TODO err
-					return console.log err
-
-				showView 'server'
-
-				onLoad = ->
-					iframe.removeEventListener 'load', onLoad
-					iframe.contentWindow.dispatchEvent new CustomEvent 'push-notification', {detail: data}
-
-				iframe.addEventListener 'load', onLoad
-
-	push.on 'error', (data) ->
-		console.log 'err', data
-
-
-window.addEventListener 'native.keyboardshow', (e) ->
-	if device?.platform.toLowerCase() isnt 'android'
-		$('.keyboard').css 'bottom', e.keyboardHeight
-
-
-window.addEventListener 'native.keyboardhide', ->
-	if device?.platform.toLowerCase() isnt 'android'
-		$('.keyboard').css 'bottom', 0
-
-
-document.addEventListener 'pause', (e) ->
-	$('iframe')[0].contentDocument.dispatchEvent(e)
-
-
-document.addEventListener 'resume', (e) ->
-	$('iframe')[0].contentDocument.dispatchEvent(e)
+# window.configurePush = ->
+# 	# if window.push?
+# 	# 	return console.log 'already registered'
+
+# 	if device.platform.toLowerCase() is 'android' and not localStorage.getItem('android_senderID')?
+# 		return console.log 'no SenderID'
+
+# 	config =
+# 		ios:
+# 			alert: "true"
+# 			badge: "true"
+# 			sound: "true"
+
+# 	if device.platform.toLowerCase() is 'android'
+# 		config.android =
+# 			senderID: localStorage.getItem('android_senderID')
+# 			sound: true
+# 			vibrate: true
+
+# 	window.push = PushNotification.init config
+
+# 	push.on 'registration', (data) ->
+# 		if device.platform.toLowerCase() is 'android'
+# 			token.gcm = data.registrationId
+# 		else
+# 			token.apn = data.registrationId
+
+# 	push.on 'notification', (data) ->
+# 		if data.additionalData.foreground is true
+# 			return
+
+# 		if typeof data.additionalData.ejson is 'string'
+# 			data.additionalData.ejson = JSON.parse data.additionalData.ejson
+
+# 		host = data.additionalData.ejson.host
+# 		if not host?
+# 			return
+
+# 		host = host.replace /\/$/, ''
+# 		if Servers.serverExists(host) isnt true
+# 			return
+
+# 		iframe = $('iframe')[0]
+# 		if Servers.getActiveServer().url is host and currentView isnt 'start'
+# 			iframe.contentWindow.dispatchEvent new CustomEvent 'push-notification', {detail: data}
+# 		else
+# 			Servers.startServer host, (err, url) ->
+# 				if err?
+# 					# TODO err
+# 					return console.log err
+
+# 				showView 'server'
+
+# 				onLoad = ->
+# 					iframe.removeEventListener 'load', onLoad
+# 					iframe.contentWindow.dispatchEvent new CustomEvent 'push-notification', {detail: data}
+
+# 				iframe.addEventListener 'load', onLoad
+
+# 	push.on 'error', (data) ->
+# 		console.log 'err', data
+
+
+# window.addEventListener 'native.keyboardshow', (e) ->
+# 	if device?.platform.toLowerCase() isnt 'android'
+# 		$('.keyboard').css 'bottom', e.keyboardHeight
+
+
+# window.addEventListener 'native.keyboardhide', ->
+# 	if device?.platform.toLowerCase() isnt 'android'
+# 		$('.keyboard').css 'bottom', 0
+
+
+# document.addEventListener 'pause', (e) ->
+# 	$('iframe')[0].contentDocument.dispatchEvent(e)
+
+
+# document.addEventListener 'resume', (e) ->
+# 	$('iframe')[0].contentDocument.dispatchEvent(e)

 document.addEventListener "deviceready", ->
-	configurePush()
+	# configurePush()

 	cordova.plugins?.Keyboard?.hideKeyboardAccessoryBar? true
 	cordova.plugins?.Keyboard?.disableScroll? true
-
-	refreshServerList()

 	$('form').on 'submit', (e) ->
 		e.preventDefault()
@@ -314,23 +214,16 @@
 			registerServer()
 		, 100

-	$(document).on 'click', '.server .name', onServerClick
-	$(document).on 'click', '.server .delete-btn', onServerDeleteClick
-	$(document).on 'click', '.addServer', onAddServerClick
-
-	$(document).on 'click', "#serverList", (e) ->
-		if $(e.target).is('#serverList')
-			toggleServerList(false)
-
-	$(document).on 'click', ".overlay", (e) ->
-		if $(e.target).is('.overlay')
-			toggleServerList(false)
-
-	$('iframe').on 'load', onIframeLoad
+	# $('iframe').on 'load', onIframeLoad
 	$('#serverAddress').on 'input', serverAddressInput

-	addSwipeEventToOpenServerList $('#startView')
-
+	Servers.onLoad ->
+		refreshServerList()
+		window.loadLast()
+		navigator.splashscreen.hide()
+
+
+window.loadLast = ->
 	activeServer = Servers.getActiveServer()
 	if activeServer?
 		$(document.body).addClass 'loading'
@@ -339,7 +232,3 @@
 			if err?
 				# TODO err
 				return console.log err
-
-			showView 'server'
-
-	navigator.splashscreen.hide()


Fix handling the case when a AMD module is registered for a control

--- src/coffee/cilantro/ui/field/controls.coffee
+++ src/coffee/cilantro/ui/field/controls.coffee
@@ -51,14 +51,19 @@

             controlId = model.get('name')

-            if not (controlView = controls.get(controlId))
-                require [controlId], (controlView) =>
+            # Get the registered control
+            controlView = controls.get(controlId)
+
+            if _.isFunction(controlView)
+                @createView(controlView, options)
+            else
+                # If control view is not defined, fallback to using the
+                # control id as the module path
+                require [controlView or controlId], (controlView) =>
                     @createView(controlView, options)
                 , (err) =>
                     @showErrorView()
                     logger.debug(err)
-            else
-                @createView(controlView, options)

         createView: (viewClass, options) =>
             try


Use 'polyfill' for _.find

Use 'polyfill' for _.find

--- src/coffee/portal-shipping-calculator.coffee
+++ src/coffee/portal-shipping-calculator.coffee
@@ -1,8 +1,16 @@
 # DEPENDENCIES:
 # jQuery
-# Underscore

 $ = window.jQuery
+
+
+# if we don't have underscore, make our own _.find
+unless _.find
+	_.extend
+		find: (arr, iterator) ->
+			for item in arr
+				if !!iterator(item)
+					return item

 # CLASSES
 class ShippingCalculator extends ProductComponent


Use new tooltips API in project find

Use new tooltips API in project find

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -57,19 +57,32 @@
     @updateOptionsLabel()

   destroy: ->
-    @subscriptions.dispose()
+    @subscriptions?.dispose()
+    @tooltipSubscriptions?.dispose()

   setPanel: (@panel) ->
     @subscriptions.add @panel.onDidChangeVisible (visible) =>
       if visible then @didShow() else @didHide()

   didShow: ->
-    atom.workspaceView.addClass('find-visible')
-    unless @tooltipsInitialized
-      @regexOptionButton.setTooltip("Use Regex", command: 'project-find:toggle-regex-option', commandElement: @findEditor)
-      @caseOptionButton.setTooltip("Match Case", command: 'project-find:toggle-case-option', commandElement: @findEditor)
-      @replaceAllButton.setTooltip("Replace All", command: 'project-find:replace-all', commandElement: @replaceEditor)
-      @tooltipsInitialized = true
+    atom.views.getView(atom.workspace).classList.add('find-visible')
+    return if @tooltipSubscriptions?
+
+    @tooltipSubscriptions = subs = new CompositeDisposable
+    subs.add atom.tooltips.add @regexOptionButton,
+      title: "Use Regex"
+      keyBindingCommand: 'project-find:toggle-regex-option',
+      keyBindingTarget: @findEditor[0]
+
+    subs.add atom.tooltips.add @caseOptionButton,
+      title: "Match Case",
+      keyBindingCommand: 'project-find:toggle-case-option',
+      keyBindingTarget: @findEditor[0]
+
+    subs.add atom.tooltips.add @replaceAllButton,
+      title: "Replace All",
+      keyBindingCommand: 'project-find:replace-all',
+      keyBindingTarget: @replaceEditor[0]

   didHide: ->
     @hideAllTooltips()
@@ -77,9 +90,8 @@
     atom.workspaceView.removeClass('find-visible')

   hideAllTooltips: ->
-    @regexOptionButton.hideTooltip()
-    @caseOptionButton.hideTooltip()
-    @replaceAllButton.hideTooltip()
+    @tooltipSubscriptions.dispose()
+    @tooltipSubscriptions = null

   handleEvents: ->
     @on 'core:confirm', => @confirm()


Use bare collection for bibliography

Use bare collection for bibliography

--- models/mixins/relations/artist.coffee
+++ models/mixins/relations/artist.coffee
@@ -34,7 +34,7 @@
     merchandisable = new Books
     merchandisable.url = "#{APP_URL}/artist/data/#{@id}/publications?merchandisable[]=true"

-    bibliography = new Books
+    bibliography = new Backbone.Collection
     bibliography.url = "#{APP_URL}/artist/data/#{@id}/publications"

     collections = new Backbone.Collection


activity: ignore submitted post of others in most liked filter [fixes #78733488]

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -214,7 +214,8 @@

   addMessage: (message) ->

-    return  if message.account._id is KD.whoami()._id
+    return  if KD.isMyPost message
+    return  if @currentFilter is 'MOST_LIKED' and not KD.isMyPost message

     {lastToFirst} = @getOptions()
     index = if lastToFirst then @listController.getItemCount() else 0
@@ -351,4 +352,3 @@
     @listController.removeAllItems()
     @listController.showLazyLoader()
     @populate()
-


Require missed Promise module

Require missed Promise module

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -14,6 +14,8 @@
 getters                 = require 'activity/flux/getters'
 showError               = require 'app/util/showError'
 showNotification        = require 'app/util/showNotification'
+Promise                 = require 'bluebird'
+

 dispatch = (args...) -> kd.singletons.reactor.dispatch args...


Client: Activity: Updated loadMessages method to set limit to 50 and dispatch set/unset all messages loaded events

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -17,10 +17,11 @@
 ###
 loadMessages = (channelId, options = {}) ->

-  options.limit ?= 25
+  options.limit ?= 50
   { socialapi } = kd.singletons
   { LOAD_MESSAGES_BEGIN, LOAD_MESSAGES_FAIL,
-    LOAD_MESSAGES_SUCCESS, LOAD_MESSAGE_SUCCESS } = actionTypes
+    LOAD_MESSAGES_SUCCESS, LOAD_MESSAGE_SUCCESS
+    SET_ALL_MESSAGES_LOADED, UNSET_ALL_MESSAGES_LOADED } = actionTypes

   dispatch LOAD_MESSAGES_BEGIN, { channelId, options }

@@ -34,6 +35,11 @@

       # clean load more markers of given messages first.
       # kd.utils.defer -> cleanLoaderMarkers channelId, messages
+
+      if messages.length < options.limit
+        dispatch SET_ALL_MESSAGES_LOADED, { channelId }
+      else
+        dispatch UNSET_ALL_MESSAGES_LOADED, { channelId }

       kd.singletons.reactor.batch ->
         messages.forEach (message) ->


StatusBar: destroy statusbar if not clicked on selectbox

--- client/ide/views/statusbar/statusbarmenu.coffee
+++ client/ide/views/statusbar/statusbarmenu.coffee
@@ -13,7 +13,7 @@
     super options, menuItems

     @on 'ContextMenuItemReceivedClick', (view, event) =>
-      if event.target.classList.contains 'name'
+      unless event.target.parentNode.classList.contains 'kdselectbox'
         @destroy()

   getMenuItems: ->


instruction book refactor

instruction book refactor

--- client/landing/app/MainApp/book/bookview.coffee
+++ client/landing/app/MainApp/book/bookview.coffee
@@ -639,11 +639,7 @@
     return callback @unreadPages if @unreadPages

     @getStorage().fetchValue "lastReadVersion", (lastReadVersion=0)=>
-
-      version = @getVersion()
-
-      if version > lastReadVersion
-
+      if @getVersion() > lastReadVersion
         unreadPages = @getNewerPages lastReadVersion

         @newPagePointer ?= 0


Tests: Environments: Paidacount: Added pause for addVM test.

--- client/test/lib/environments/paidaccount.coffee
+++ client/test/lib/environments/paidaccount.coffee
@@ -91,7 +91,10 @@
             helpers.selectPlan(browser)
             helpers.fillPaymentForm(browser)

-            browser.url helpers.getUrl() + '/IDE'
+            browser
+              .url helpers.getUrl() + '/IDE'
+              .pause  10000 # wait for sidebar redraw
+
             environmentHelpers.clickAddVMButton(browser)
             environmentHelpers.clickCreateVMButton(browser)
           else


dont forget the name

dont forget the name

--- vue/src/shared/models/user_model.coffee
+++ vue/src/shared/models/user_model.coffee
@@ -90,6 +90,6 @@
     @avatarUrl[size]

   nameWithTitle: (group) ->
-    return unless group
+    return @name unless group
     titles = @titles || {}
     compact([@name, (titles[group.id] || titles[group.parentId])]).join(' · ')


home: minor regex fix

home: minor regex fix

--- client/landing/app/Applications/Home.kdapplication/Views/homeslideshow.coffee
+++ client/landing/app/Applications/Home.kdapplication/Views/homeslideshow.coffee
@@ -70,7 +70,7 @@

   pistachio:->

-    host = unless /koding.com/.test location.hostname then "" else "https://api.koding.com"
+    host = unless /koding\.com/.test location.hostname then "" else "https://api.koding.com"
     """
     <ul class="clearfix">
       <li>


Add `settings` Korean translation

Add `settings` Korean translation

--- def/ko/settings.cson
+++ def/ko/settings.cson
@@ -3,386 +3,367 @@
   menu: [
     {
       _label: "Core"
-      value: "Core"
+      value: "코어"
     }
     {
       _label: "Editor"
-      value: "Editor"
+      value: "에디터"
     }
     {
       _label: "Keybindings"
-      value: "Keybindings"
+      value: "키 바인딩"
     }
     {
       _label: "Packages"
-      value: "Packages"
+      value: "패키지"
     }
     {
       _label: "Themes"
-      value: "Themes"
+      value: "테마"
     }
     {
       _label: "Updates"
-      value: "Updates"
+      value: "업데이트"
     }
     {
       _label: "Install"
-      value: "Install"
+      value: "설치"
     }
   ]
-  "btn-open-config-folder": "Open Config Folder"
+  "btn-open-config-folder": "구성 폴더 열기"
   sectionHeadings: [
-    {_label: "Core Settings", value: "Core Settings"}
-    {_label: "Editor Settings", value: "Editor Settings"}
-    {_label: "Keybindings", value: "Keybindings"}
-    {_label: "Installed Packages", value: "Installed Packages"}
-    {_label: "Choose a Theme", value: "Choose a Theme"}
-    {_label: "Installed Themes", value: "Installed Themes"}
-    {_label: "Install Packages", value: "Install Packages"}
-    {_label: "Featured Packages", value: "Featured Packages"}
-    {_label: "Install Themes", value: "Install Themes"}
-    {_label: "Featured Themes", value: "Featured Themes"}
+    {_label: "Core Settings", value: "코어 설정"}
+    {_label: "Editor Settings", value: "에디터 설정"}
+    {_label: "Keybindings", value: "키 바인딩"}
+    {_label: "Installed Packages", value: "설치된 패키지"}
+    {_label: "Choose a Theme", value: "테마 선택"}
+    {_label: "Installed Themes", value: "설치된 테마"}
+    {_label: "Install Packages", value: "패키지 설치"}
+    {_label: "Featured Packages", value: "추천 패키지"}
+    {_label: "Install Themes", value: "테마 설치"}
+    {_label: "Featured Themes", value: "추천 테마"}
   ]
-  "heading-available-updates": {_label: "Available Updates", value: "Available Updates"}
+  "heading-available-updates": {_label: "Available Updates", value: "사용 가능한 업데이트"}
   subSectionHeadings: [
-    {_label: "Invisible", value: "Invisible"}
-    {_label: "Community Packages", value: "Community Packages"}
-    {_label: "Core Packages", value: "Core Packages"}
-    {_label: "Development Packages", value: "Development Packages"}
-    {_label: "Git Packages", value: "Git Packages"}
-    {_label: "Community Themes", value: "Community Themes"}
-    {_label: "Core Themes", value: "Core Themes"}
-    {_label: "Development Themes", value: "Development Themes"}
-    {_label: "Git Themes", value: "Git Themes"}
+    {_label: "Invisible", value: "비가시성 문자"}
+    {_label: "Community Packages", value: "커뮤니티 패키지"}
+    {_label: "Core Packages", value: "코어 패키지"}
+    {_label: "Development Packages", value: "개발 패키지"}
+    {_label: "Git Packages", value: "Git 패키지"}
+    {_label: "Community Themes", value: "커뮤니티 테마"}
+    {_label: "Core Themes", value: "코어 테마"}
+    {_label: "Development Themes", value: "개발 테마"}
+    {_label: "Git Themes", value: "Git 테마"}
   ]
   settings: {
     notes: [
       {
         _id: 'core-settings-note'
-        html: 'These are Atom\'s core settings which affect behavior
-               unrelated to text editing. Individual packages may have
-               their own additional settings found within their package
-               card in the Packages list.'
+        html: '이 설정은 텍스트 편집과 관계 없는 Atom Editor의 동작에 관련된 코어 설정입니다.
+               몇몇 패키지는 해당 패키지만의 추가적인 설정을 가지고 있을 수 있으며
+               Packages list의
+               패키지 카드에서 찾을 수 있습니다.'
       }
       {
         _id: 'editor-settings-note'
-        html: 'These settings are related to text editing. Some of these
-               can be overriden on a per-language basis. Check language
-               settings by clicking its package card in the
-               Packages list.'
+        html: '이 설정은 문서 편집과 관련된 에디터 설정입니다.
+               어떤 설정은 각 언어 설정에 따라 덮어씌어질 수 있습니다.
+               Packages list에서
+               패키지 카드를 선택하여 언어 설정을 확인하세요.'
       }
     ]
     controls: [
       {
         _id: 'core.allowPendingPaneItems'
-        title: "Allow Pending PaneItems"
-        desc: "Allow items to be previewed without adding them to a pane
-               permanently, such as when single clicking files in the tree view."
+        title: "패널 아이템 미리보기 허용"
+        desc: "트리뷰에서 파일을 한 번 클릭 했을 때, 아이템을 탭에 추가하지 않고 미리보기 형태로 이전 패널과 겹쳐 보여줍니다."
       }
       {
         _id: 'core.audioBeep'
-        title: "Audio Beep"
-        desc: "Trigger the system's beep sound when certain actions cannot be
-               executed or there are no results."
+        title: "비프음"
+        desc: "어떤 특정한 동작을 실행시키지 못했거나 결과가 없을 때 시스템 비프 사운드를 재생합니다."
       }
       {
         _id: 'core.automaticallyUpdate'
-        title: "Automatically Update"
-        desc: "Automatically update Atom when a new release is available."
+        title: "자동 업데이트"
+        desc: "새로운 릴리즈를 사용할 수 있을 때 Atom을 자동으로 업데이트 합니다."
       }
       {
         _id: 'core.autoHideMenuBar'
-        title: "Auto Hide Menu Bar"
-        desc: "Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux."
+        title: "메뉴 막대 자동 숨기기"
+        desc: "자동으로 창 상단의 메뉴 막대를 숨기고, Alt키를 눌렀을 때만 토글합니다. 이 기능은 Windows와 Linux에서만 지원합니다."
       }
       {
         _id: 'core.closeEmptyWindows'
-        title: "Close Empty Windows"
-        desc: "When a window with no open tabs or panes is given the 'Close Tab' command, close that window."
+        title: "빈 창 닫기"
+        desc: "윈도우가 탭이나 패널을 하나도 열고 있지 않을 때, '탭 닫기 (Close Tab)' 커맨드가 입력되면 윈도우를 닫습니다."
       }
       {
         _id: 'core.destroyEmptyPanes'
-        title: "Destroy Empty Panes"
-        desc: "When the last tab of a pane is closed, remove that pane as well."
+        title: "빈 패널 닫기"
+        desc: "패널의 마지막 탭이 닫혔을 때, 그 패널도 같이 닫습니다."
       }
       {
         _id: 'core.excludeVcsIgnoredPaths'
-        title: "Exclude VCS Ignored Paths"
-        desc: "Files and directories ignored by the current project's VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders."
+        title: "VCS에서 무시된 경로 제외하기"
+        desc: "현재 프로젝트의 VCS 시스템에서 무시된 파일과 디렉토리가 몇몇 fuzzy finder와 find and replace 같은 패키지에서 무시됩니다. 예를 들어 Git을 사용하는 프로젝트에서 이러한 경로가 .gitignore 파일에 기재되어 있는 경우가 있습니다. 각각의 패키지들은 VCS에서 무시된 파일과 경로에 대한 추가적인 설정을 제공할 수도 있습니다."
       }
       {
         _id: 'core.fileEncoding'
-        title: "File Encoding"
-        desc: "Default character set encoding to use when reading and writing files."
+        title: "파일 인코딩"
+        desc: "파일을 읽거나 작성할 때 사용할 기본 문자 세트를 지정합니다."
       }
       {
         _id: 'core.followSymlinks'
-        title: "Follow Symlinks"
-        desc: "Follow symbolic links when searching files and when opening files with the fuzzy finder."
+        title: "심링크 따라가기"
+        desc: "파일을 찾거나 fuzzy finder로 열 때 심링크를 따라갑니다."
       }
       {
         _id: 'core.ignoredNames'
-        title: "Ignored Names"
-        desc: "List of glob patterns. Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names."
+        title: "무시될 파일과 경로"
+        desc: "Glob 패턴 문자열로 이루어진 리스트입니다. 이 패턴에 일치하는 파일과 디렉토리는 몇몇 fuzzy finder와 find and replace 같은 패키지에서 무시됩니다. 각각의 패키지들은 무시될 파일과 경로에 대한 설정을 제공할 수도 있습니다."
       }
       {
         _id: 'core.openEmptyEditorOnStart'
-        title: "Open Empty Editor On Start"
-        desc: "Automatically open an empty editor on startup."
+        title: "시작시 빈 에디터로 열기"
+        desc: "자동으로 아무것도 열리지 않은 빈 에디터로 시작합니다."
       }
       {
         _id: 'core.packagesWithKeymapsDisabled'
-        title: "Packages With Keymaps Disabled"
+        title: "단축키 설정을 비활성화할 패키지 리스트"
       }
       {
         _id: 'core.projectHome'
-        title: "Project Home"
-        desc: "The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default."
+        title: "프로젝트 홈"
+        desc: "프로젝트가 위치해야 할 경로입니다. 패키지 생성기로 만드는 패키지들은 모두 기본적으로 이 경로를 기준으로 생성됩니다."
       }
       {
         _id: 'core.reopenProjectMenuCount'
-        title: "Reopen Project Menu Count"
-        desc: "How many recent projects to show in the Reopen Project menu."
+        title: "프로젝트 다시 열기 메뉴의 프로젝트 개수"
+        desc: "프로제긑 다시 열기 메뉴 (Reopen Project menu)에서 최근 열린 프로젝트를 몇개까지 표시할지 지정합니다."
       }
       {
         _id: 'core.restorePreviousWindowsOnStart'
-        title: "Restore Previous Windows On Start"
+        title: "시작시 이전 창 복구"
       }
       {
         _id: 'core.telemetryConsent'
-        title: "Send Telemetry to the Atom Team"
-        desc: "Allow usage statistics and exception reports to be sent to the Atom team to help improve the product."
+        title: "Atom 팀으로 사용 정보 보내기"
+        desc: "Atom을 더 좋게 만들기 위해 사용 정보와 예외 리포트를 Atom 팀으로 보낼 수 있도록 허용합니다."
         select:
-          limited: "Allow limited anonymous usage stats, exception and crash reporting"
-          no: "Do not send any telemetry data"
-          undecided: "Undecided (Atom will ask again next time it is launched)"
+          limited: "제한된 익명의 사용 정보, 예외 및 크래시 정보를 보내는 것을 허용합니다"
+          no: "어떠한 정보도 보내지 않습니다"
+          undecided: "나중에 물어보기 (나중에 Atom이 실행될 때 다시 물어봅니다)"
       }
       {
         _id: 'core.useCustomTitleBar'
-        title: "Use Custom Title Bar"
+        title: "커스텀 제목 막대 사용"
       }
       {
         _id: 'core.warnOnLargeFileLimit'
-        title: "Warn On Large File Limit"
-        desc: "Warn before opening files larger than this number of megabytes."
+        title: "커다란 크기의 파일을 열 때 경고"
+        desc: "다음 메가바이트 단위로 지정된 파일 크기보다 더 큰 파일을 열 때 경고를 표시합니다."
       }
       {
         _id: 'editor.atomicSoftTabs'
-        title: "Atomic Soft Tabs"
-        desc: "Skip over tab-length runs of leading whitespace when moving the cursor."
+        title: "간소화한 Soft 탭"
+        desc: "커서를 움직일 때, 탭 길이에 따라 선행하는 여백을 일반 탭처럼 건너뜁니다."
       }
       {
         _id: 'editor.autoIndent'
-        title: "Auto Indent"
-        desc: "Automatically indent the cursor when inserting a newline."
+        title: "자동 들여쓰기"
+        desc: "개행할 때 자동으로 커서를 들여쓰기합니다."
       }
       {
         _id: 'editor.autoIndentOnPaste'
-        title: "Auto Indent On Paste"
-        desc: "Automatically indent pasted text based on the indentation of the previous line."
+        title: "붙여넣기시 자동 들여쓰기"
+        desc: "붙여넣기된 텍스트를 이전 행의 들여쓰기에 맞춰 자동으로 들여쓰기를 수행합니다."
       }
       {
         _id: 'editor.backUpBeforeSaving'
-        title: "Back Up Before Saving"
-        desc: ""
+        title: "저장하기 전에 백업"
+        desc: "만약 저장 과정에 I/O 에러가 있더라도 일시적인 백업 복사본을 만듦으로써, 작성한 파일 내용을 잃어버리지 않게 합니다."
       }
       {
         _id: 'editor.confirmCheckoutHeadRevision'
-        title: "Confirm Checkout HEAD Revision"
-        desc: "Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit."
+        title: "HEAD 리비전 체크아웃시 확인"
+        desc: "HEAD 리비전을 체크아웃하고 마지막 커밋으로부터 현재 파일의 변경 사항을 폐기할 때 확인 대화상자를 표시합니다."
       }
       {
         _id: 'editor.fontFamily'
-        title: "Font Family"
-        desc: "The name of the font family used for editor text."
+        title: "폰트 패밀리"
+        desc: "에디터 텍스트에 사용할 폰트 패밀리를 지정합니다."
       }
       {
         _id: 'editor.fontSize'
-        title: "Font Size"
-        desc: "Height in pixels of editor text."
+        title: "폰트 크기"
+        desc: "픽셀 단위의 에디터 텍스트 크기입니다."
       }
       {
         _id: 'editor.invisibles.cr'
-        title: "Invisibles Cr"
-        desc: "Character used to render carriage return characters
-               (for Microsoft-style line endings) when the Show
-               Invisibles setting is enabled."
+        title: "비가시성 Cr"
+        desc: "비가시성 문자 보이기 설정이 활성화되어있을 때, 캐리지 리턴 문자 (Microsoft 스타일 개행)를 표현할 문자를 지정합니다."
       }
       {
         _id: 'editor.invisibles.eol'
-        title: "Invisibles Eol"
-        desc: "Character used to render newline characters (\n) when the
-               Show Invisibles setting is enabled."
+        title: "비가시성 Eol"
+        desc: "비가시성 문자 보이기 설정이 활성화되어있을 때, 개행 문자 (\n)를 표현할 문자를 지정합니다."
       }
       {
         _id: 'editor.invisibles.space'
-        title: "Invisibles Space"
-        desc: "Character used to render leading and trailing space characters
-               when the Show Invisibles setting is enabled."
+        title: "비가시성 Space"
+        desc: "비가시성 문자 보이기 설정이 활성화되어있을 때, 선행, 후행 스페이스를 표현할 문자를 지정합니다."
       }
       {
         _id: 'editor.invisibles.tab'
-        title: "Invisibles Tab"
-        desc: "Character used to render hard tab characters (\t) when the
-               Show Invisibles setting is enabled."
+        title: "비가시성 Tab"
+        desc: "비가시성 문자 보이기 설정이 활성화되어있을 때, 하드 탭 (\t)을 표현할 문자를 지정합니다."
       }
       {
         _id: 'editor.lineHeight'
-        title: "Line Height"
-        desc: "Height of editor lines, as a multiplier of font size."
+        title: "행 높이"
+        desc: "에디터 행의 높이이며 폰트 크기에 추가로 늘어납니다."
       }
       {
         _id: 'editor.nonWordCharacters'
-        title: "Non Word Characters"
-        desc: "A string of non-word characters to define word boundaries."
+        title: "비단어 문자"
+        desc: "단어의 경계를 구분할 비단어 문자의 문자열"
       }
       {
         _id: 'editor.preferredLineLength'
-        title: "Preferred Line Length"
-        desc: "Identifies the length of a line which is used when wrapping text
-               with the Soft Wrap At Preferred Line Length setting
-               enabled, in number of characters."
+        title: "선호하는 행 길이"
+        desc: "선호하는 행 길이에 소프트 랩핑 적용 설정이 활성화되어 있을 때,
+               텍스트를 랩핑하기 위해 사용할 행 문자의 개수를 지정합니다."
       }
       {
         _id: 'editor.scrollPastEnd'
-        title: "Scroll Past End"
-        desc: "Allow the editor to be scrolled past the end of the last line."
+        title: "마지막 행에서 스크롤"
+        desc: "에디터가 마지막 행의 끝에서 밑으로 더 스크롤 할 수 있도록 허용합니다."
       }
       {
         _id: 'editor.scrollSensitivity'
-        title: "Scroll Sensitivity"
-        desc: "Determines how fast the editor scrolls when using a mouse or
-               trackpad."
+        title: "스크롤 감도"
+        desc: "마우스나 트랙패드를 사용할 떄 얼마나 빠르게 에디터를 스크롤할지 지정합니다."
       }
       {
         _id: 'editor.showIndentGuide'
-        title: "Show Indent Guide"
-        desc: "Show indentation indicators in the editor."
+        title: "들여쓰기 가이드 보이기"
+        desc: "에디터에서 들여쓰기 가이드를 보이게할지 지정합니다."
       }
       {
         _id: 'editor.showInvisibles'
-        title: "Show Invisibles"
-        desc: "Render placeholders for invisible characters, such as tabs,
-               spaces and newlines."
+        title: "비가시성 문자 보이기"
+        desc: "탭, 스페이스, 개행 문자와 같은 비가시성 문자열의 대체 문자를 표시합니다."
       }
       {
         _id: 'editor.showLineNumbers'
-        title: "Show Line Numbers"
-        desc: "Show line numbers in the editor's gutter."
+        title: "행 번호 보이기"
+        desc: "에디터의 왼쪽 공간에 행 번호를 표시합니다."
       }
       {
         _id: 'editor.softTabs'
-        title: "Soft Tabs"
-        desc: "If the Tab Type config setting is set to \"auto\"
-               and autodetection of tab type from buffer content fails, then
-               this config setting determines whether a soft tab or a hard tab
-               will be inserted when the Tab key is pressed."
+        title: "소프트 탭"
+        desc: "탭 종류 설정이 \"auto\"로 되어있고,
+               버퍼 콘텐츠에서 탭 종류의 자동 인식에 실패했을 때,
+               탭 키가 입력되면 소프트 탭을 사용할지 하드 탭을 사용할지를 지정합니다."
       }
       {
         _id: 'editor.softWrap'
-        title: "Soft Wrap"
-        desc: "Wraps lines that exceed the width of the window. When
-               Soft Wrap At Preferred Line Length is set, it will
-               wrap to the number of characters defined by the Preferred
-               Line Length setting."
+        title: "소프트 랩핑"
+        desc: "창의 크기를 초과하는 행을 랩핑합니다.
+              선호하는 행 길이에 소프트 랩핑 적용 설정이 활성화되어있는 경우,
+              선호하는 행 길이 설정에 맞춰 랩핑됩니다."
       }
       {
         _id: 'editor.softWrapAtPreferredLineLength'
-        title: "Soft Wrap At Preferred Line Length"
-        desc: "Instead of wrapping lines to the window's width, wrap lines to
-               the number of characters defined by the Preferred Line
-               Length setting. This will only take effect when the soft
-               wrap config setting is enabled globally or for the current
-               language. Note: If you want to hide the wrap
-               guide (the vertical line) you can disable the
-               wrap-guide package."
+        title: "선호하는 행 길이에 소프트 랩핑 적용"
+        desc: "각 행을 창의 넓이에 랩핑하는 대신, 선호하는 행 길이 설정에서
+               지정한 문자 개수에 맞춰 랩핑합니다. 이 설정은 전역 소프트 랩핑 설정 또는
+               현재 언어의 설정에서 활성화된 경우에만 작동합니다.
+               참고: 랩핑 가이드 (에디터에 표시되는 세로줄)를 숨기고 싶다면,
+               wrap-guide 패키지를 비활성화하면 됩니다."
       }
       {
         _id: 'editor.softWrapHangingIndent'
-        title: "Soft Wrap Hanging Indent"
-        desc: "When soft wrap is enabled, defines length of additional
-               indentation applied to wrapped lines, in number of characters."
+        title: "소프트 랩핑 추가 들여쓰기"
+        desc: "소프트 랩핑이 활성화되어있을 때, 랩핑된 행에 지정한 수 만큼의 들여쓰기를 추가합니다."
       }
       {
         _id: 'editor.tabLength'
-        title: "Tab Length"
-        desc: "Number of spaces used to represent a tab."
+        title: "탭 길이"
+        desc: "탭을 표현하는데 사용할 스페이스의 개수입니다."
       }
       {
         _id: 'editor.tabType'
-        title: "Tab Type"
-        desc: "Determine character inserted when Tab key is pressed. Possible
-               values: \"auto\", \"soft\" and \"hard\". When set to \"soft\" or
-               \"hard\", soft tabs (spaces) or hard tabs (tab characters) are
-               used. When set to \"auto\", the editor auto-detects the tab type
-               based on the contents of the buffer (it uses the first leading
-               whitespace on a non-comment line), or uses the value of the Soft
-               Tabs config setting if auto-detection fails."
+        title: "탭 종류"
+        desc: "탭 키가 입력됐을 때 어떤 문자가 삽입될지 지정합니다.
+               사용할 수 있는 값은: \"auto\", \"soft\" 그리고 \"hard\"가 있습니다.
+               \"soft\" 또는 \"hard\"로 설정하면 각각 소프트 탭 (스페이스)과 하드 탭 (탭 문자)이 사용됩니다.
+               \"auto\"로 설정하면, 에디터가 버터의 콘텐츠에 맞춰 자동으로 탭 종류를 지정하고
+               (주석이 아닌 첫 선행 공백 문자열을 통해 확인합니다), 만약 자동 지정에 실패하면 소프트 탭 설정에서 지정한 값을 사용합니다."
       }
       {
         _id: 'editor.undoGroupingInterval'
-        title: "Undo Grouping Interval"
-        desc: "Time interval in milliseconds within which text editing
-               operations will be grouped together in the undo history."
+        title: "실행취소 그룹화 간격"
+        desc: "실행취소 히스토리에 기록될 텍스트 편집 작업의 밀리초 단위의 간격입니다."
       }
       {
         _id: 'editor.zoomFontWhenCtrlScrolling'
-        title: "Zoom Font When Ctrl Scrolling"
-        desc: "Change the editor font size when pressing the Ctrl key and
-               scrolling the mouse up/down."
+        title: "Ctrl 키를 누른 상태에서 스크롤시 폰트 크기 조절"
+        desc: "Ctrl 키를 누른 상태로 위/아래로 스크롤시 폰트 크기를 조절할지 지정합니다."
       }
     ]
   }
   keymaps: {
     notes: {
-      text1: "You can override these keybindings by copying "
-      text2: "and pasting them into "
-      text3: "your keymap file"
-      text4: ""   # optional
+      text1: ""
+      text2: " 아이콘을 클릭하여 키 맵핑을 복사한 후 "
+      text3: "키 맵핑 파일"
+      text4: " 에 붙여넣으면 단축키를 덮어쓸 수 있습니다"   # optional
       _template: "${text1} <icon /> ${text2} ${text3} ${text4}"
     }
-    "search-bar-placeholder": "Search keybindings"
-    "table-header-keystroke": "Keystroke"
-    "table-header-command": "Command"
-    "table-header-source": "Source"
-    "table-header-selector": "Selector"
+    "search-bar-placeholder": "키 바인딩 검색"
+    "table-header-keystroke": "키스트로크"
+    "table-header-command": "커맨드"
+    "table-header-source": "소스"
+    "table-header-selector": "선택자"
   }
   packages: {
   }
   themes: {
     notes: {
-      text1: "You can also style Atom by editing "
-      text2: "your stylesheet"
-      text3: ""   # optional
+      text1: "Atom의 "
+      text2: "스타일 시트"
+      text3: "를 수정함으로써 외관을 편집할 수 있습니다"   # optional
       _template: "${text1} ${text2} ${text3}"
     }
-    title1: "UI Theme"
-    description1: "This styles the tabs, status bar, tree view, and dropdowns"
-    title2: "Syntax Theme"
-    description2: "This styles the text inside the editor"
+    title1: "UI 테마"
+    description1: "이 테마는 탭, 상태 막대, 트리뷰, 그리고 드롭다운 메뉴의 스타일을 지정합니다"
+    title2: "문법 테마"
+    description2: "이 테마는 에디터 안의 텍스트 스타일을 지정합니다"
   }
   updates: {
-    "check-updates": "Check for Updates"
-    "update-all": "Update All"
-    "checking-updates": "Checking for updates..."
-    "all-updated-message": "All of your installed packages are up to date!"
+    "check-updates": "업데이트 확인"
+    "update-all": "모두 업데이트"
+    "checking-updates": "업데이트를 확인하는 중입니다..."
+    "all-updated-message": "설치된 모든 패키지가 최신 상태입니다!"
   }
   install: {
     notes: {
-      text1: "Packages are published to "
-      text2: " and are installed to "
-      text3: ""   # optional
+      text1: "패키지들은 모두 "
+      text2: " 에서 배포되며,"
+      text3: " 폴더에 설치됩니다"   # optional
       _template: "${text1}atom.io${text2} /path/to/pkg ${text3}"
     }
-    "searchbar-placeholder": "Search packages"
-    "btn-packages": "Packages"
-    "btn-themes": "Themes"
+    "searchbar-placeholder": "패키지 검색"
+    "btn-packages": "패키지"
+    "btn-themes": "테마"
   }
   "control-btns":{
-    install: "Install"
-    update: "Update to"
-    setting: "Settings"
-    uninstall: "Uninstall"
-    disable: "Disable"
-    enable: "Enable"
-  }
+    install: "설치"
+    update: "다음으로 업데이트"
+    setting: "설정"
+    uninstall: "제거"
+    disable: "비활성화"
+    enable: "활성화"
+  }


support shorthand for node background

support shorthand for node background

--- app/_elements/aurora-story-controller/aurora-story-controller.coffee
+++ app/_elements/aurora-story-controller/aurora-story-controller.coffee
@@ -33,18 +33,24 @@
     @node = node

   _renderNode: (node) ->
+    # video node
     if node.type is 'video'
       @app.video.allowSkipping = node.allowSkipping
       @app.video.video = node.video
       return

     # background
-    if node.background?
-      @app.background.options = node.background
+    if (background = node.background)?
+      switch typeof background
+        # shortcut
+        when 'string'
+          @app.background.options = { name: background }
+        when 'object'
+          @app.background.options = background

     # tachies
-    if node.tachies?
-      @app.tachies.tachies = node.tachies
+    if (tachies = node.tachies)?
+      @app.tachies.tachies = tachies

     # conversation-box
     @app.conversationBox.node = node


auth: temporary hack to hold the chat exchange open

--- workers/auth/lib/auth/authworker.coffee
+++ workers/auth/lib/auth/authworker.coffee
@@ -400,6 +400,10 @@
       {connection} = bongo.mq
       @monitorPresence connection

+      # FIXME: this is a hack to hold the chat exchange open for the meantime
+      connection.exchange 'chat', NOTIFICATION_EXCHANGE_OPTIONS, (chatExchange) ->
+        # *chirp chirp chirp chirp*
+
       connection.exchange 'authAll', AUTH_EXCHANGE_OPTIONS, (authAllExchange) =>
         connection.queue '', {exclusive:yes}, (authAllQueue) =>
           authAllQueue.bind authAllExchange, ''


link embed box links now target _blank

link embed box links now target _blank

--- client/app/Applications/Activity.kdapplication/views/embedbox.coffee
+++ client/app/Applications/Activity.kdapplication/views/embedbox.coffee
@@ -97,10 +97,10 @@
         html = """

-
+

-
+

#{data.title or "untitled"}

Provided by #{data.provider_name or "the internet"}#{if data.provider_url then " at "+prettyLink(data.provider_url)+"" else ""}

#{data.description or ""}


Revert "Ensure "focus" event is triggered when autofocusing"

This is not the right behavior and triggered "focus" when the browser naturally wouldn't (window not focused, element hidden).

This reverts commit b4745a816d1f508e6055e439fa9fcd96ae476645.

--- src/trix/elements/trix_editor_element.coffee
+++ src/trix/elements/trix_editor_element.coffee
@@ -9,6 +9,11 @@
   id = 0

   # Contenteditable support helpers
+
+  autofocus = (element) ->
+    unless document.querySelector(":focus")
+      if element.hasAttribute("autofocus") and document.querySelector("[autofocus]") is element
+        element.focus()

   makeEditable = (element) ->
     return if element.hasAttribute("contenteditable")
@@ -137,22 +142,12 @@
       @editorController ?= new Trix.EditorController(editorElement: this, html: @defaultValue = @value)
       @editorController.registerSelectionManager()
       @registerResetListener()
-      @autofocus()
+      autofocus(this)
       requestAnimationFrame => @notify("initialize")

   detachedCallback: ->
     @editorController?.unregisterSelectionManager()
     @unregisterResetListener()
-
-  # Autofocus support
-
-  autofocus: ->
-    unless document.querySelector(":focus")
-      if @hasAttribute("autofocus") and document.querySelector("[autofocus]") is this
-        focusTriggered = false
-        handleEventOnce("focus", onElement: this, withCallback: -> focusTriggered = true)
-        @focus()
-        triggerEvent("focus", onElement: this) unless focusTriggered

   # Form reset support


sdp: Edit comments

sdp: Edit comments

--- sdp.coffee
+++ sdp.coffee
@@ -73,8 +73,7 @@
     #
     # rtpmap:96 mpeg4-generic/<audio clock rate>/<audio channels>
     #
-    # TODO: How to determine profile-level-id?
-    #   1: Main Audio Profile Level 1
+    # TODO: Use appropriate profile-level-id
     sdpBody = """
     v=0
     o=#{opts.username} #{opts.sessionID} #{opts.sessionVersion} IN #{opts.addressType} #{opts.unicastAddress}
@@ -96,6 +95,7 @@
         frameLength: 1024  # TODO: How to detect 960?
       configspec = configspec.toString 'hex'

+      # profile-level-id=1: Main Profile Level 1
       sdpBody += """
       m=audio 0 RTP/AVP #{opts.audioPayloadType}
       a=rtpmap:#{opts.audioPayloadType} #{opts.audioEncodingName}/#{opts.audioClockRate}/#{opts.audioChannels}


bug fixes

bug fixes

--- drm-validation.coffee
+++ drm-validation.coffee
@@ -12,110 +12,132 @@
             self = @
             body = $ 'body'

+            patterns =
+                # an integer can be negative or positive and can include one comma separator followed by exactly 3 numbers
+                integer: new RegExp "^\\-?\\d*"
+                number: new RegExp "^\\-?\\d*\\.?\\d*"
+                url: new RegExp '^https?:\\/\\/[\\da-z\\.\\-]+[\\.a-z]{2,6}[\\/\\w/.\\-]*\\/?$','gi'
+                email: new RegExp '^[a-z][a-z\\-\\_\\.\\d]*@[a-z\\-\\_\\.\\d]*\\.[a-z]{2,6}$','gi'
+                # validates United States phone number patterns
+                phone: new RegExp '^\\(?\\d{3}[\\)\\-\\.]?\\d{3}[\\-\\.]?\\d{4}(?:[xX]\\d+)?$','gi'
+                # allows alpha . - and ensures that the user enters both a first and last name
+                fullName: new RegExp '^[a-z]+ [a-z\\.\\- ]+$','gi'
+                alpha: new RegExp '^[a-z ]*','gi'
+                alphaNum: new RegExp '^[a-z\\d ]*$','gi'
+                noSpaces: new RegExp '^\\S*$','gi'
+                alphaNumDash: new RegExp '^[a-z\\d- ]*$','gi'
+                # allows alphanumeric characters and underscores; no spaces; recommended for usernames
+                alphaNumUnderscore: new RegExp '^[a-z\\d_]*$','gi'
+                noTags: new RegExp '<[a-z]+.*>.*<\/[a-z]+>','i'
+                date: new RegExp ''
+                creditCard: new RegExp ''
+                cvv: new RegExp ''
+
             validateField = (value, validate) ->
-                if validate.message isnt null
+                if validate.message?
                     self.issueNotice.call @, validate.status, validate.message, validate.issuer, self.speed
                 else
                     self.removeNotice.call @, validate.issuer, self.speed
                 self.removeValidationClass.call @, validate.status
                 self.applyValidationClass.call @, validate.status

-            body.on 'click', ':disabled', (e) ->
-                e.preventDefault()
+            body.on 'click', ':disabled', (e) -> e.preventDefault()

             body.on 'keyup', ':input.drm-valid-integer', ->
                 value = self.getValue.call @
-                validate = self.validateInteger value
+                validate = self.validateInteger value, patterns.integer
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-number', ->
                 value = self.getValue.call @
-                validate = self.validateNumber value
+                validate = self.validateNumber value, patterns.number
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-url', ->
                 value = self.getValue.call @
-                validate = self.validateURL value
+                validate = self.validateURL value, patterns.url
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-phone', ->
                 value = self.getValue.call @
-                validate = self.validatePhone value
+                validate = self.validatePhone value, patterns.phone
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-email', ->
                 value = self.getValue.call @
-                validate = self.validateEmail value
+                validate = self.validateEmail value, patterns.email
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-full-name', ->
                 value = self.getValue.call @
-                validate = self.validateFullName value
+                validate = self.validateFullName value, patterns.fullName
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-alpha', ->
                 value = self.getValue.call @
-                validate = self.validateAlpha value
+                validate = self.validateAlpha value, patterns.alpha
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-alphanum', ->
                 value = self.getValue.call @
-                validate = self.validateAlphaNum value
+                validate = self.validateAlphaNum value, patterns.alphaNum
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-alphadash', ->
                 value = self.getValue.call @
-                validate = self.validateAlphaNumDash value
+                validate = self.validateAlphaNumDash value, patterns.alphaNumDash
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-alpha-num-underscore', ->
                 value = self.getValue.call @
-                validate = self.validateAlphaNumUnderscore value
+                validate = self.validateAlphaNumUnderscore value, patterns.alphaNumUnderscore
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-no-spaces', ->
                 value = self.getValue.call @
-                validate = self.validateNoSpaces value
+                validate = self.validateNoSpaces value, patterns.noSpaces
                 validateField.call @, value, validate

             body.on 'keyup', ':input.drm-valid-no-tags', ->
                 value = self.getValue.call @
-                validate = self.validateNoTags value
+                validate = self.validateNoTags value, patterns.noTags
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-max-value]:not([data-min-value])', ->
                 value = self.getValue.call @
-                validate = self.validateMaxValue value
+                validate = self.validateMaxValue.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-min-value]:not([data-max-value])', ->
                 value = self.getValue.call @
-                validate = self.validateMinValue value
+                validate = self.validateMinValue.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-max-length]:not([data-min-length])', ->
                 value = self.getValue.call @
-                validate = self.validateMaxLength value
+                validate = self.validateMaxLength.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-min-length]:not([data-max-length])', ->
                 value = self.getValue.call @
-                validate = self.validateMinLength value
+                validate = self.validateMinLength.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-min-value][data-max-value]', ->
                 value = self.getValue.call @
-                validate = self.validateBetweenValue value
+                validate = self.validateBetweenValue.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input[data-min-length][data-max-length]', ->
                 value = self.getValue.call @
-                validate = self.validateBetweenLength value
+                validate = self.validateBetweenLength.call @, value
                 validateField.call @, value, validate

             body.on 'keyup', ':input', self.trackLength

-            body.on 'keyup', '[required]', ->
+            # validate empty fields
+
+            body.on 'blur', '[required]', ->
                 value = self.getValue.call @
                 validate = self.validateRequired value
                 validateField.call @, value, validate
@@ -124,7 +146,7 @@
                 value = self.getValue.call @
                 if not value
                     self.removeValidationClass.call @
-                    self.removeAllNotices.call @
+                    self.removeAllNotices.call @, self.speed

         trackLength: ->
             that = $ @
@@ -151,7 +173,12 @@

         getValue: ->
             value = $.trim $(@).val()
-            value
+
+            if value.length > 0
+                return value
+            else
+                value = null
+                value

         issueNotice: (status, message, issuer, speed) ->
             that = $ @
@@ -167,17 +194,22 @@

         removeNotice: (issuer, speed) ->
             notice = $ "p[data-issuer='#{issuer}']"
-            notice.slideUp speed, ->
-                $(@).remove()
-
-        removeAllNotices: ->
+
+            if notice.length isnt 0
+                notice.slideUp speed, ->
+                    $(@).remove()
+
+        removeAllNotices: (speed) ->
             that = $ @
             notices = that.nextUntil ':input','p.form-success-notice, p.form-warning-notice, p.form-danger-notice'
-            notices.slideUp @speed, ->
-                $(@).remove()
+
+            if notices.length isnt 0
+                notices.slideUp speed, ->
+                    $(@).remove()

         applyValidationClass: (status) ->
             that = $ @
+
             switch status
                 when 'danger' then that.addClass 'drm-form-danger'
                 when 'warning' then that.addClass 'drm-form-warning'
@@ -201,14 +233,6 @@
                     that.removeClass 'drm-form-warning'
                     that.removeClass 'drm-form-success'

-        validateEmpty: ->
-            that = $ @
-            value = $.trim that.val()
-
-            if not value
-                @removeValidationClass.call that
-                @removeAllNotices.call that
-
         validateRequired: (value) ->
             validate =
                 status: null
@@ -221,295 +245,494 @@

             validate

-        validateInteger: (value) ->
+        validateInteger: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'integer'
-            # an integer can be negative or positive and can include one comma separator followed by exactly 3 numbers
-            re = new RegExp "^\\-?\\d*"
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.message = 'please enter a valid integer'
                     validate.status = 'danger'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateNumber: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateNumber: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'number'
-            re = new RegExp "^\\-?\\d*\\.?\\d*"
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.message = 'please enter a valid number'
                     validate.status = 'danger'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateURL: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateURL: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'url'
-            re = new RegExp '^https?:\\/\\/[\\da-z\\.\\-]+[\\.a-z]{2,6}[\\/\\w/.\\-]*\\/?$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please enter a valid url'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateEmail: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateEmail: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'email'
-            re = new RegExp '^[a-z][a-z\\-\\_\\.\\d]*@[a-z\\-\\_\\.\\d]*\\.[a-z]{2,6}$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please enter a valid email address'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validatePhone: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validatePhone: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'phone'
-            # validates United States phone number patterns
-            re = new RegExp '^\\(?\\d{3}[\\)\\-\\.]?\\d{3}[\\-\\.]?\\d{4}(?:[xX]\\d+)?$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please enter a valid phone number'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateFullName: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateFullName: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'fullName'
-            # allows alpha . -
-            re = new RegExp '^[a-z]+ [a-z\\.\\- ]+$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please enter your first and last name'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateAlpha: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateAlpha: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'alpha'
-            re = new RegExp '^[a-z ]*','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please use alpha characters only'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateAlphaNum: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateAlphaNum: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'alphanum'
-            re = new RegExp '^[a-z\\d ]*$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please use alphanumeric characters only'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateNoSpaces: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateNoSpaces: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'noSpaces'
-            re = new RegExp '^\\S*$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'no spaces'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateAlphaNumDash: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateAlphaNumDash: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'alphaNumDash'
-            re = new RegExp '^[a-z\\d- ]*$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please use alphanumeric and dash characters only'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateAlphaNumUnderscore: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateAlphaNumUnderscore: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'alphaNumUnderscore'
-            # allows alphanumeric characters and underscores; no spaces; recommended for usernames
-            re = new RegExp '^[a-z\\d_]*$','gi'
-
-            evaluate = (result, value) ->
-                if result and value is result
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.message = null
                     validate.status = 'success'
                 else
                     validate.status = 'danger'
                     validate.message = 'please use alphanumeric and underscores only. no spaces'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
-
-            validate
-
-        validateNoTags: (value) ->
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateNoTags: (value, pattern) ->
             validate =
                 status: null
                 message: null
                 issuer: 'number'
-            re = new RegExp '<[a-z]+.*>.*<\/[a-z]+>','i'

             evaluate = (result) ->
                 if result
                     validate.status = 'danger'
                     validate.message = 'no html tags allowed'
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                result = $.trim re.exec value
-                validate.status = evaluate result, value
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateDate: (value, pattern) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'date'
+
+            evaluate = (result) ->
+                if result
+                    validate.status = 'danger'
+                    validate.message = 'please provide a valid date'
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateCreditCard: (value, pattern) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'creditCard'
+
+            evaluate = (result) ->
+                if result
+                    validate.status = 'danger'
+                    validate.message = 'please provide a valid credit card number'
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateCvv: (value, pattern) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'cvv'
+
+            evaluate = (result) ->
+                if result
+                    validate.status = 'danger'
+                    validate.message = 'please provide a valid cvv'
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                pattern = new RegExp pattern
+                result = $.trim pattern.exec value
+                validate = evaluate result, value
+
+            validate
+
+        validateEqual: (value) ->
+            that = $ @
+            equal = that.data 'equal'
+            validate =
+                status: null
+                message: null
+                issuer: 'equal'
+
+            evaluate = (equal, value) ->
+                if value == equal
+                    validate.message = null
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = "this field should be #{equal}"
+                validate
+
+            if value?
+                validate = evaluate equal, value
+
+            validate
+
+        validateNotEqual: (value) ->
+            that = $ @
+            notEqual = that.data 'not-equal'
+            validate =
+                status: null
+                message: null
+                issuer: 'notEqual'
+
+            evaluate = (notEqual, value) ->
+                if value == notEqual
+                    validate.status = 'danger'
+                    validate.message = "this field cannot be #{notEqual}"
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate notEqual, value
+
+            validate
+
+        validateCheckbox: () ->
+
+        validateRadio: () ->
+
+        validateInList: (value) ->
+            that = $ @
+            list = that.data 'in-list'
+            validate =
+                status: null
+                message: null
+                issuer: 'inList'
+
+            parseList = () ->
+                # doSomething
+
+            evaluate = (list, value) ->
+                if value == list
+                    validate.status = 'danger'
+                    validate.message = "this field cannot be #{list}"
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate list, value
+
+            validate
+
+        validateNotList: (value) ->
+            that = $ @
+            notEqual = that.data 'not-equal'
+            validate =
+                status: null
+                message: null
+                issuer: 'not-equal'
+
+            evaluate = (notEqual, value) ->
+                if value == notEqual
+                    validate.status = 'danger'
+                    validate.message = "this field cannot be #{notEqual}"
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate notEqual, value
+
+            validate
+
+        validateRequiredWith: (value) ->
+            that = $ @
+            requiredWith = that.data 'required-with'
+            validate =
+                status: null
+                message: null
+                issuer: 'not-equal'
+
+            evaluate = (requiredWith, value) ->
+                if value == requiredWith
+                    validate.status = 'danger'
+                    validate.message = "this field is required with #{requiredWith}"
+                else
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate requiredWith, value

             validate

         validateMaxValue: (value) ->
             that = $ @
             max = that.data 'max-value'
-            value = $.trim that.val()
+            validate =
+                status: null
+                message: null
+                issuer: 'maxValue'

             evaluate = (max, value) ->
                 if value > max
                     validate.status = 'danger'
                     validate.message = "please enter a value that is less than #{max + 1}"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate max, value
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate max, value

             validate

         validateMinValue: (value) ->
             that = $ @
             min = that.data 'min-value'
-            value = $.trim that.val()
+            validate =
+                status: null
+                message: null
+                issuer: 'minValue'

             evaluate = (min, value) ->
                 if value < min
                     validate.status = 'danger'
                     validate.message = "please enter a value of at least #{min}"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate(min, value)
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate(min, value)

             validate

@@ -517,56 +740,68 @@
             that = $ @
             min = that.data 'min-value'
             max = that.data 'max-value'
-            value = $.trim that.val()
+            validate =
+                status: null
+                message: null
+                issuer: 'betweenValue'

             evaluate = (min, max, value) ->
                 if (value < min) or (value > max)
                     validate.status = 'danger'
                     validate.message = "please enter a value that is between #{min - 1} and #{max + 1}"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate min, max, value
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                validate = evaluate min, max, value

             validate

         validateMaxLength: (value) ->
             that = $ @
             max = that.data 'max-length'
-            value = $.trim that.val()
-            length = value.length
+            validate =
+                status: null
+                message: null
+                issuer: 'maxLength'

             evaluate = (max, length) ->
                 if length > max
                     validate.status = 'danger'
                     validate.message = "please enter less than #{max + 1} characters"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate max, length
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                length = value.length
+                validate = evaluate max, length

             validate

         validateMinLength: (value) ->
             that = $ @
             min = that.data 'min-length'
-            value = $.trim that.val()
-            length = value.length
+            validate =
+                status: null
+                message: null
+                issuer: 'minLength'

             evaluate = (min, length) ->
                 if length < min
                     validate.status = 'danger'
                     validate.message = "please enter at least #{min} characters"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate min, length
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                length = value.length
+                validate = evaluate min, length

             validate

@@ -574,19 +809,23 @@
             that = $ @
             min = that.data 'min-length'
             max = that.data 'max-length'
-            value = $.trim that.val()
-            length = value.length
+            validate =
+                status: null
+                message: null
+                issuer: 'betweenLength'

             evaluate = (min, max, length) ->
                 if (length < min) or (length > max)
                     validate.status = 'danger'
                     validate.message = "please enter a value that is between #{min - 1} and #{max + 1} characters"
                 else
-                    validate.status = 'success'
-                validate.status
-
-            if value?
-                validate.status = evaluate min, max, length
+                    validate.message = null
+                    validate.status = 'success'
+                validate
+
+            if value?
+                length = value.length
+                validate = evaluate min, max, length

             validate


Test fix

Test fix

--- test/spec/sync/conventions/many.tests.coffee
+++ test/spec/sync/conventions/many.tests.coffee
@@ -19,6 +19,7 @@
   BASE_COUNT = 5

   class Reverse extends Backbone.Model
+    model_name: 'Reverse'
     urlRoot: "#{DATABASE_URL}/many_to_many_reverses"
     schema: _.defaults({
       owners: -> ['HasMany', Owner]
@@ -26,6 +27,7 @@
     sync: SYNC(Reverse)

   class Owner extends Backbone.Model
+    model_name: 'Owner'
     urlRoot: "#{DATABASE_URL}/many_to_many_owners"
     schema: _.defaults({
       reverses: -> ['has_many', Reverse]


Fixed requirements.

Fixed requirements.

--- tests/integration/error-handling-test.coffee
+++ tests/integration/error-handling-test.coffee
@@ -4,7 +4,7 @@
 sinon = require 'sinon'
 express = require 'express'

-api = require '../../lib/controllers/api.coffee'
+require '../../app.coffee'
 blueprint = require '../../lib/blueprint.coffee'


Add BasicTimer for performance tuning

Add BasicTimer for performance tuning


Remove unused scope binding.

Remove unused scope binding.

--- services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee
+++ services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee
@@ -8,7 +8,6 @@
 			onStartNew: "&"
 			onSubmit: "&"
 			onCancel: "&"
-			layoutToLeft: "="
 		link: (scope, element, attrs) ->
 			scope.state =
 				isAdding: false


use Session to back app state

use Session to back app state

--- client/main.cjsx
+++ client/main.cjsx
@@ -1,5 +1,6 @@
 React               = require 'react'
 { Meteor }          = require 'meteor/meteor'
+{ Session }         = require 'meteor/session'
 { createContainer } = require 'meteor/react-meteor-data'
 { render }          = require 'react-dom'
 { EventEmitter }    = require 'fbemitter'
@@ -20,6 +21,10 @@
     ddp.call 'stopInstance', instanceName
     console.log 'stop instance received for ', instanceName

+  eventEmitter.addListener 'app name selected', (appName) ->
+    console.log 'app name selected', appName
+    Session.set 'selectedAppName', appName
+
   emit: (evt, data) -> eventEmitter.emit evt, data
   collections:
     Instances: new Mongo.Collection 'instances', connection: ddp
@@ -27,6 +32,8 @@
   subscribe:
     allInstances: -> ddp.subscribe 'instances'
     allApps: -> ddp.subscribe 'applicationDefs'
+  state:
+    selectedAppName: -> Session.get 'selectedAppName'

 , Wrapper


Revert "Adding taigaComponents module"

Revert "Adding taigaComponents module"

This reverts commit c016394fa366967cbfcbf1f08abd3e972ae800b0.

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -265,7 +265,7 @@
     "taigaRelatedTasks",
     "taigaBacklog",
     "taigaTaskboard",
-    "taigaKanban",
+    "taigaKanban"
     "taigaIssues",
     "taigaUserStories",
     "taigaTasks",
@@ -279,10 +279,9 @@
     "taigaFeedback",
     "taigaPlugins",
     "taigaIntegrations",
-    "taigaComponents",

     # template cache
-    "templates",
+    "templates"

     # Vendor modules
     "ngRoute",


Permit creator of box to add sshkey in all cases

--- code/serv.coffee
+++ code/serv.coffee
@@ -60,14 +60,16 @@
 # These should make changes somewhere, likely to the mongodb database

 # TODO: should this be middleware?
+# Check if scraperwiki currently recognises the api key
+# If fails, check if that apikey has been valid in the past for box creation
 check_api_key = (req, res, next) ->
   res.header('Content-Type', 'application/json')
   if req.body.apikey?
     url = "https://scraperwiki.com/froth/check_key/#{req.body.apikey}"
     request.get url, (err, resp, body) ->
-      if resp.statusCode is 200
-        next()
-      else
+      return next() if resp.statusCode is 200
+      User.findOne {apikey: req.body.apikey}, (err, user) ->
+        return next() if user?
         res.send {error: "Unauthorised"}, 403
   else
     res.send {error: "No API key supplied"}, 403
@@ -80,8 +82,8 @@
   res.header('Content-Type', 'application/json')
   re = /^[a-zA-Z0-9_+-]+$/
   if not re.test req.params.box_name
-     return res.send {error:
-       "Box name should match the regular expression #{String(re)}"}, 404
+    return res.send {error:
+      "Box name should match the regular expression #{String(re)}"}, 404
   new User({apikey: req.body.apikey}).save()
   User.findOne {apikey: req.body.apikey}, (err, user) ->
     return res.send {error: "User not found" }, 404 unless user?


remove path.existsSync warning

remove path.existsSync warning

--- code/serv.coffee
+++ code/serv.coffee
@@ -4,9 +4,10 @@
 # http server for cobalt.

 fs      = require 'fs'
-# Only used for existsSync, can be removed when update to node vsn
-# with existsSync in the fs module
 path    = require 'path'
+# Avoids warning.  Remove when we definitely use a node version with
+# the "modern" fs.existsSync.
+existsSync = fs.existsSync || path.existsSync
 child_process = require 'child_process'
 os      = require 'os'

@@ -272,7 +273,7 @@
             return res.send {"error": "Internal creation error"}

 app.listen port
-if path.existsSync(port) && fs.lstatSync(port).isSocket()
+if existsSync(port) && fs.lstatSync(port).isSocket()
   fs.chmodSync port, 0o600
   child_process.exec "chown www-data #{port}"


Refactor focus rendering logic

Refactor focus rendering logic

--- app/talk/comment.cjsx
+++ app/talk/comment.cjsx
@@ -121,26 +121,21 @@
       <Markdown project={@props.project} content={comment.body} />

+  # Render the focus if the comment has a focus AND
+  #   - it's not in a discussion (recents) OR
+  #   - it's a focused discussion and this is the first comment OR
+  #   - it's not a focused discussion OR
+  #   - it's a focused discussion and this comment's focus is different
+  shouldShowFocus: ->
+    return false unless @props.data.focus_id
+
+    notInDiscussion = not @props.index
+    isFirstSubjectComment = @props.index is 0 and @props.data.discussion_focus_id
+    isDifferentFocus = @props.data.focus_id isnt @props.data.discussion_focus_id
+
+    notInDiscussion or isFirstSubjectComment or isDifferentFocus
+
   render: ->
-    # When the comment isn't in a discussion
-    isInDiscussion = @props.index
-
-    # When the discussion is about a focus and this is the first comment on the page
-    isFirstSubjectComment = @props.index is 0 and @props.data.discussion_focus_id
-
-    # When the discussion is about a focus, but this comment is about a different focus
-    isDifferentFocus = @props.data.focus_id isnt @props.data.discussion_focus_id
-
-    # Render the focus if
-    #   - it's not in a discussion
-    #   - it's not a focused discussion and this comment has a focus
-    #   - it's a focused discussion and this is the first comment
-    #   - it's a focused discussion and this comment is about a different focus
-    shouldRenderFocus = isFirstSubjectComment or isDifferentFocus
-
-    # Always render the focus outside of a discussion
-    shouldRenderFocus = true unless isInDiscussion
-
     feedback = @renderFeedback()
     activeClass = if @props.active then 'active' else ''
     isDeleted = if @props.data.is_deleted then 'deleted' else ''
@@ -185,7 +180,7 @@

{@props.title}
}

{timestamp(@props.data.created_at)}

-            {if @props.data.focus_id and shouldRenderFocus
+            {if @shouldShowFocus()
               <PromiseRenderer
                 promise={
                   apiClient.type('subjects').get(@props.data.focus_id)


the module is called `canvas` not `node-canvas`

--- Builder.coffee
+++ Builder.coffee
@@ -53,7 +53,7 @@
   spriteHelper = null

   canBuildSprites: ->
-    hasCanvas = !!try require 'node-canvas'
+    hasCanvas = !!try require 'canvas'
     (if hasCanvas then Promise.resolve() else which 'gm').catch (err) ->
       if /^not found:/.test err.message
         console.error "Unmet dependency!  You must install either graphicsmagick or cairo + node-canvas"


fix login bug

fix login bug

--- routes/index.coffee
+++ routes/index.coffee
@@ -54,7 +54,7 @@
 		if not user
 			req.flash 'error', '用户不存在'
 			return res.redirect '/login'
-		if user.password is not password
+		if user.password != password
 			req.flash 'error', '用户密码错误'
 			return res.redirect '/login'
 		req.session.user = user


primus: fix error in displaying connection error messages.

--- salvus/client_browser.coffee
+++ salvus/client_browser.coffee
@@ -31,7 +31,7 @@
             ondata(evt.data)

         conn.on 'error', (err) =>
-            console.log("websocket -- error: ", evt)
+            console.log("websocket -- error: ", err)
             @emit("error", err)

         conn.on 'close', () =>


get rid of some "return false"'s when not needed in some event handlers

--- salvus/page/project_new.cjsx
+++ salvus/page/project_new.cjsx
@@ -77,7 +77,7 @@
         name : rtypes.string
         icon : rtypes.string
         on_click : rtypes.func
-
+
     render : ->
         <Button onClick={@props.on_click}>
             <Icon name={@props.icon} /> {@props.name}
@@ -127,7 +127,7 @@
     create_folder : ->
         p = @path()
         if p.length == 0
-            return false
+            return
         page = project_page(project_id : @props.project_id)
         page.ensure_directory_exists
             path : p
@@ -141,28 +141,28 @@
             @setState(downloading : true)
             @new_file_from_web @state.filename, () =>
                 @setState(downloading : false)
-            return false
+            return
         if @state.filename[@state.filename.length - 1] == '/'
             for bad_char in BAD_FILENAME_CHARACTERS
                 if name.slice(0, -1).indexOf(bad_char) != -1
                     @setState(error: "Cannot use '#{bad_char}' in a folder name")
-                    return false
+                    return
             @create_folder()
-            return false
+            return
         p = @path(ext)
         if not p
-            return false
+            return
         ext = misc.filename_extension(p)
         if ext in BANNED_FILE_TYPES
             @setState(error: "Cannot create a file with the #{ext} extension")
-            return false
+            return
         if ext == 'tex'
             for bad_char in BAD_LATEX_FILENAME_CHARACTERS
                 if p.indexOf(bad_char) != -1
                     @setState(error: "Cannot use '#{bad_char}' in a LaTeX filename")
-                    return false
+                    return
         if p.length == 0
-            return false
+            return
         salvus_client.exec
             project_id  : @props.project_id
             command     : "new-file"


clean up

clean up

--- public/js/wireroom.coffee
+++ public/js/wireroom.coffee
@@ -109,11 +109,7 @@
     self = this
     @BootTime = new Date()
     @Identifier = CybozuLabs.SHA1.calc(Math.random().toString() + new Date().getTime())
-
-    console.info "Wireroom Started."
-
     @rooms = @options.rooms or ["Hall"]
-
     @tabs = new UITab($('#channelTabs'))

     # XXX: we may use navigator.onLine status to reconnect,
@@ -129,9 +125,6 @@

     @plugins.status        = new WRConnectionStatus(this, $('#connectionStatus') )
     @plugins.channelSearch = new WRChannelSearchInput(this,$('#channelSearch'))
-
-    @hasLogs = false
-

     # The unload event is sent to the window element when the user navigates
     # away from the page. This could mean one of many things.


update

update

--- examples/config.cson
+++ examples/config.cson
@@ -1,84 +1,84 @@
 "*":

-	# This is Atom's config.cson default properties, don't mind this
-	welcome:
-		showOnStartup: false
-	"tool-bar":
-		visible: false
-	"markdown-preview":
-		breakOnSingleNewline: true
+  # This is Atom's config.cson default properties, don't mind this
+  welcome:
+    showOnStartup: false
+  "tool-bar":
+    visible: false
+  "markdown-preview":
+    breakOnSingleNewline: true

-	# Here we go
-	"cmd-exec": # An object
+  # Here we go
+  "cmd-exec": # An object

-		commands: [ # An array
+    commands: [ # An array

-			{
+      {

-				target: ["atom-workspace"] # Target is an array or a string
+        target: ["atom-workspace"] # Target is an array or a string

-				data:
+        data:

-					"os:explorer": "explorer ." # (Windows only) Open containing folder (which contains ativated file in Atom) in File Explorer
+          "os:explorer": "explorer ." # (Windows only) Open containing folder (which contains ativated file in Atom) in File Explorer

-					"os:cmd": # (Windows only) Open containing folder in Command Prompt
-						type: "spawn"
-						path: "cmd /c start cmd" # Open new console
+          "os:cmd": # (Windows only) Open containing folder in Command Prompt
+            type: "spawn"
+            path: "cmd /c start cmd" # Open new console

-					"console:cmd": # (Windows only) Open containing folder in Command Prompt, but inside Atom (open a new pane item)
-						type: "spawn"
-						path: "cmd"
-						console: true
-						hideInputText: true
-						utils: [
-							"clear" # Enter 'clear' in console, your console would be cleared
-							"start" # Enter 'start <cmd>'' in console, a new console would be opened
-							"stdin" # Enter 'stdin <data>', '<data>' would be written to child_process.stdin,
-									# e.g. 'stdin start <cmd>' would run '<cmd>' in a new windows instead of inside Atom
-							"beep" # Just like atom.beep()
-							"alert" # 'alert <title> <msg>'
-						]
-						closeOnExit: true
+          "console:cmd": # (Windows only) Open containing folder in Command Prompt, but inside Atom (open a new pane item)
+            type: "spawn"
+            path: "cmd"
+            console: true
+            hideInputText: true
+            utils: [
+              "clear" # Enter 'clear' in console, your console would be cleared
+              "start" # Enter 'start <cmd>'' in console, a new console would be opened
+              "stdin" # Enter 'stdin <data>', '<data>' would be written to child_process.stdin,
+                  # e.g. 'stdin start <cmd>' would run '<cmd>' in a new windows instead of inside Atom
+              "beep" # Just like atom.beep()
+              "alert" # 'alert <title> <msg>'
+            ]
+            closeOnExit: true

-					"console:bash": # Open containing folder in Bash inside Atom
-						type: "spawn"
-						path: "bash"
-						console: true
-						utils: [
-							"clear"
-							"alert"
-							"error"
-							"write"
-							"stdin"
-							"environ"
-							"beep"
-							"start"
-							"execute"
-							"popup"
-						]
-						closeOnExit: true
+          "console:bash": # Open containing folder in Bash inside Atom
+            type: "spawn"
+            path: "bash"
+            console: true
+            utils: [
+              "clear"
+              "alert"
+              "error"
+              "write"
+              "stdin"
+              "environ"
+              "beep"
+              "start"
+              "execute"
+              "popup"
+            ]
+            closeOnExit: true

-					"test-example:current-file":
+          "test-example:current-file":

-						type: "spawn"
-						useJSTemplateString: true
+            type: "spawn"
+            useJSTemplateString: true

-						# Important: path here would be compiled as a JavaScript template string
-						path: 'bash -c "echo current file is ${CURRENT_FILE} which is placed inside ${CURRENT_DIR}"'
+            # Important: path here would be compiled as a JavaScript template string
+            path: 'bash -c "echo current file is ${CURRENT_FILE} which is placed inside ${CURRENT_DIR}"'

-						console: true # To see the result
+            console: true # To see the result

-					"compile:gnu-cplusplus": # Compile current C++ file by g++
+          "compile:gnu-cplusplus": # Compile current C++ file by g++

-						type: "spawn"
-						useJSTemplateString: true
+            type: "spawn"
+            useJSTemplateString: true

-						# Info: some available node modules: path, fs, os, url, Math, JSON, console (Atom's Chrome DevTools), utils (from CmdExec itself)
-						# Info: some available variables: CURRENT_FILE, CURRENT_DIR, env (Environment Variables)
-						path: "g++ ${CURRENT_FILE} -std=c++14 -o ${path.basename(CURRENT_FILE, '.cpp')}"
+            # Info: some available node modules: path, fs, os, url, Math, JSON, console (Atom's Chrome DevTools), utils (from CmdExec itself)
+            # Info: some available variables: CURRENT_FILE, CURRENT_DIR, env (Environment Variables)
+            path: "g++ ${CURRENT_FILE} -std=c++14 -o ${path.basename(CURRENT_FILE, '.cpp')}"

-						console: true
+            console: true

-			}
+      }

-		]
+    ]