More examples in the source comments

More examples in the source comments

--- src/dahelpers.coffee
+++ src/dahelpers.coffee
@@ -98,6 +98,12 @@
     #
     # All attribute values are double-quoted and any double quotes inside the
     # attribute values are escaped.
+    #
+    # Example:
+    #
+    #     dahelpers.obAttrs({foo: 1, bar: 'foo'});
+    #     // returns 'foo="1" bar="foo"'
+    #
     objAttrs: (o) ->
       attrs = []
       for key of o
@@ -115,6 +121,12 @@
     # If the `silence` argument is `true`, the whole `tag` is only rendered if
     # `content` is not null or undefined, and can be coerced into a non-empty
     # string.
+    #
+    # Example:
+    #
+    #     dahelpers.tag('a', 'click here', {href: '#'});
+    #     // returns 'click here'
+    #
     tag: (name, content='', attrs=null, silence=false) ->
       return '' if not name
       return '' if silence and (not content? or content.toString() is '')
@@ -174,6 +186,12 @@
     #
     # If the `lowerFirst` is `ture`, the whole string will be lower-cased
     # before applying the title case. Default is `false`.
+    #
+    # Example:
+    #
+    #     dahelpers.titleCase('This is a title');
+    #     // returns 'This Is A Title'
+    #
     titleCase: (s, lowerFirst=false) ->
       return '' if not s
       s = s.toLowerCase() if lowerFirst


setting initial progress for readings

setting initial progress for readings

--- src/flux/task.coffee
+++ src/flux/task.coffee
@@ -137,7 +137,7 @@
       steps = getSteps(@_steps[taskId])

       if steps.length
-        stepKey / steps.length * 100
+        (stepKey + 1) / (steps.length + 1) * 100
       else
         0


levels and default game states

levels and default game states

--- src/game_over.coffee
+++ src/game_over.coffee
@@ -10,7 +10,11 @@
     titleText = "Game Over"
     titleText = "You Win!" if I.won

+    self.centerText canvas, App.name

+    self.centerText canvas, "Press any key to start",
+      size: 12
+      y: App.height / 2 + 30

     drawTitleText canvas, titleText, "Press any key to start over"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_over.coffee
+++ src/game_over.coffee
@@ -7,8 +7,6 @@
 ###

 ###*
-
-
 @name update
 @methodOf GameOver#
 @event


Add menu to main window for non-OSX platform

--- views/components/etc/menu.coffee
+++ views/components/etc/menu.coffee
@@ -354,6 +354,10 @@
 if process.platform == 'darwin'
   Menu.setApplicationMenu(appMenu)
 else
+  win = remote.getCurrentWindow()
+  win.setMenu(appMenu)
+  win.setAutoHideMenuBar(true)
+  win.setMenuBarVisibility(false)
   if process.platform == 'win32'
     window.appIcon?.setContextMenu(appMenu)
 # Ugly hard-coded hack... Hope Electron can provide some better interface in the future...


Fix text in demo workflow

Fix text in demo workflow

--- app/data/workflows.coffee
+++ app/data/workflows.coffee
@@ -21,7 +21,7 @@
       answers: [
         {value: 'smooth', label: 'Smooth'}
         {value: 'features', label: 'Features'}
-        {value: 'other', label: 'Star or artifact (done!)'}
+        {value: 'other', label: 'Star or artifact'}
       ]
       next: 'roundness'

@@ -29,7 +29,7 @@
       type: 'single'
       question: 'How round is it?'
       answers: [
-        {value: 'very', label: 'Very', next: 'shape'}
+        {value: 'very', label: 'Very...', next: 'shape'}
         {value: 'sorta', label: 'In between'}
         {value: 'not', label: 'Cigar shaped'}
       ]


sidebar: debounce lazy load method to prevent duplicate channel list

--- client/Social/Activity/sidebar/searchmodal.coffee
+++ client/Social/Activity/sidebar/searchmodal.coffee
@@ -66,14 +66,16 @@

     @addSubView @listController.getView()

-    @listController.on 'LazyLoadThresholdReached', @bound 'handleLazyLoad'
+    debouncedLazyLoad = KD.utils.debounce 300, @bound 'handleLazyLoad'
+
+    @listController.on 'LazyLoadThresholdReached', debouncedLazyLoad

     @fetch {}, @bound 'populate'

   populate: (items) ->

-    return unless items?.length?
+    return  unless items?.length?

     @listController.addItem itemData for itemData in items


Removed overlay when visible tooltip length is 0.

--- client/app/CommonViews/introductiontooltip.coffee
+++ client/app/CommonViews/introductiontooltip.coffee
@@ -69,6 +69,7 @@
       @close parentView.getOptions().introId
       parentView.tooltip.destroy()
       @visibleTooltips.splice @visibleTooltips.indexOf(tooltip), 1
+      @overlay.remove() if @visibleTooltips.length is 0

   isExpired: (expiryDate) ->
     return new Date(expiryDate).getTime() < @currentTimestamp


:white_check_mark: Add test to ensure proper handling of stuff without braces

--- spec/template-spec.coffee
+++ spec/template-spec.coffee
@@ -59,3 +59,6 @@

     it 'resolves references', ->
       expect(template.render({test: '{String}'}, resolve: ['test'])).toEqual "String"
+
+    it 'does not resolve references without braces', ->
+      expect(template.render({test: '`undefined`'}, resolve: ['test'], markdown: ['test'])).toEqual '
undefined
\n'


Add gradients difference from Can I Use

Add gradients difference from Can I Use

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -35,12 +35,13 @@

 prefix = (names..., data) ->
   for name in names
-    if result[name]
-      result[name].browsers = sort(result[name].browsers.concat(data.browsers))
-    else
-      result[name] = { }
-      for i of data
-        result[name][i] = data[i]
+    result[name] = { }
+    for i of data
+      result[name][i] = data[i]
+
+add = (names..., data) ->
+  for name in names
+    result[name].browsers = sort(result[name].browsers.concat(data.browsers))

 module.exports = result

@@ -89,15 +90,9 @@
           browsers: browsers

 # Gradients
-feature require('caniuse-db/features-json/css-gradients'), (browsers) ->
-  browsers = map browsers, (browser, name, version) ->
-    if name == 'android' and version < 4 or
-       name == 'ios_saf' and version < 5 or
-       name == 'safari'  and version < 5.1
-      browser + ' old'
-    else
-      browser
-
+gradients = require('caniuse-db/features-json/css-gradients')
+
+feature gradients, match: /y\sx/, (browsers) ->
   prefix 'linear-gradient', 'repeating-linear-gradient',
          'radial-gradient', 'repeating-radial-gradient',
           props:    ['background', 'background-image', 'border-image',
@@ -105,6 +100,12 @@
           mistakes: ['-ms-']
           browsers: browsers

+feature gradients, match: /a\sx/, (browsers) ->
+  browsers = browsers.map (i) -> if /op/.test(i) then i else "#{i} old"
+  add 'linear-gradient', 'repeating-linear-gradient',
+      'radial-gradient', 'repeating-radial-gradient',
+       browsers: browsers
+
 # Box sizing
 feature require('caniuse-db/features-json/css3-boxsizing'), (browsers) ->
   prefix 'box-sizing',
@@ -148,15 +149,13 @@
           browsers: browsers

 feature flexbox, match: /y\sx/, (browsers) ->
-  prefix 'display-flex', 'inline-flex',
-          props:  ['display']
-          browsers: browsers
-  prefix 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
-          transition: true
-          browsers:   browsers
-  prefix 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content',
-         'order', 'align-items', 'align-self', 'align-content',
-          browsers: browsers
+  add 'display-flex', 'inline-flex',
+       browsers: browsers
+  add 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
+       browsers:   browsers
+  add 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content',
+      'order', 'align-items', 'align-self', 'align-content',
+       browsers: browsers

 # calc() unit
 feature require('caniuse-db/features-json/calc'), (browsers) ->


Update zh-HANS.coffee

Update zh-HANS.coffee

Translate items under editor.

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -318,15 +318,15 @@
     contact_us: "联系我们！"
     hipchat_prefix: "你也可以在这里找到我们"
     hipchat_url: "HipChat 房间。"
-#    back: "Back"
+    back: "后退"
     revert: "还原"
     revert_models: "还原模式"
-#    fork_title: "Fork New Version"
-#    fork_creating: "Creating Fork..."
-#    more: "More"
-#    wiki: "Wiki"
-#    live_chat: "Live Chat"
-#    level_publish: "Publish This Level (irreversible)?"
+    fork_title: "派生新版本"
+    fork_creating: "正在执行派生..."
+    more: "更多"
+    wiki: "维基"
+    live_chat: "在线聊天"
+    level_publish: "发布这个关卡吗 (发布后不能撤销)?"
     level_some_options: "有哪些选项？"
     level_tab_thangs: "物体"
     level_tab_scripts: "脚本"
@@ -334,11 +334,11 @@
     level_tab_components: "组件"
     level_tab_systems: "系统"
     level_tab_thangs_title: "目前所有物体"
-#    level_tab_thangs_all: "All"
+    level_tab_thangs_all: "所有"
     level_tab_thangs_conditions: "启动条件"
     level_tab_thangs_add: "增加物体"
-#    delete: "Delete"
-#    duplicate: "Duplicate"
+    delete: "删除"
+    duplicate: "复制"
     level_settings_title: "设置"
     level_component_tab_title: "目前所有组件"
     level_component_btn_new: "创建新的组件"


Update zh-HANS.coffee (#5556)

Update zh-HANS.coffee (#5556)

Very tiny change, for GD level, might be used for hackthon

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -551,7 +551,7 @@

   play_level:
     back_to_map: "返回地图"
-    directions: "方向"
+    directions: "游戏指引"
     edit_level: "编辑关卡"
     keep_learning: "不断学习"
     explore_codecombat: "探索CodeCombat"


Finished statements

Finished statements

--- grammars/d.cson
+++ grammars/d.cson
@@ -903,94 +903,120 @@
     ###########################################################################
     'statement': {
       'patterns': [
-
+        { 'include': '#semi-colon' },
+        { 'include': '#non-empty-statement' },
+        { 'include': '#scope-block-statement' }
       ]
     },
     'no-scope-non-empty-statement': {
       'patterns': [
-
+        { 'include': '#non-empty-statement' },
+        { 'include': '#block-statment'}
       ]
     },
     'no-scope-statement': {
       'patterns': [
-
+        { 'include': '#semi-colon' },
+        { 'include': '#non-empty-statement' },
+        { 'include': '#block-statement' }
       ]
     },
     'non-empty-or-scope-block-statement': {
       'patterns': [
-
+        { 'include': '#non-empty-statement' },
+        { 'include': '#scope-block-statement' }
       ]
     },
     'non-empty-statement': {
       'patterns': [
-
+        { 'include': '#non-empty-statement-no-case-no-default' },
+        { 'include': }
       ]
     },
     'non-empty-statement-no-case-no-default': {
       'patterns': [
-
+        { 'include': '#labeled-statement' },
+        { 'include': '#expression-statement' },
+        { 'include': '#declaration-statement' },
+        { 'include': '#if-statement' },
+        { 'include': '#while-statement' },
+        { 'include': '#do-statement' },
+        { 'include': '#for-statement' },
+        { 'include': '#foreach-statement' },
+        { 'include': '#switch-statement' },
+        { 'include': '#final-switch-statement' },
+        { 'include': '#continue-statement' },
+        { 'include': '#break-statement' },
+        { 'include': '#return-statement' },
+        { 'include': '#goto-statement' },
+        { 'include': '#with-statement' },
+        { 'include': '#synchronized-statement' },
+        { 'include': '#try-statement' },
+        { 'include': '#scope-guard-statement' },
+        { 'include': '#throw-statement' },
+        { 'include': '#asm-statement' },
+        { 'include': '#pragma-statement' },
+        { 'include': '#mixin-statement' },
+        { 'include': '#foreach-range-statement' },
+        { 'include': '#conditional-statement' },
+        { 'include': '#static-assert' },
+        { 'include': '#template-mixin' },
+        { 'include': '#import-declaration' },
       ]
     },
     'scope-statement': {
       'patterns': [
-
+        { 'include': '#non-empty-statement' },
+        { 'include': '#block-statement' }
       ]
     },
     'scope-block-statement': {
       'patterns': [
-
+        { 'include': '#block-statement' }
       ]
     },
     'labeled-statement': {
       'patterns': [
-
+        { 'include': '#colon' },
+        { 'include': '#no-scope-statement' },
+        { 'include': '#statement' },
+        { 'include': '#identifier' }
       ]
     },
     'block-statement': {
       'patterns': [
-
+        {
+          'begin': '\\{',
+          'beginCaptures': { '0': { 'name': 'support.type.brackets-expression.begin.d' } },
+          'end': '\\}',
+          'encCaptures': { '0': { 'name': 'support.type.brackets-expression.end.d' } },
+          'patterns': [
+            { 'include': '#statement-list' }
+          ]
+        },
       ]
     },
     'statement-list': {
       'patterns': [
-
+        { 'include': '#statement' }
       ]
     },
     'expression-statement': {
       'patterns': [
-
+        { 'include': '#expression' },
+        { 'include': '#semi-colon' }
       ]
     },
     'declaration-statement': {
       'patterns': [
-
+        { 'include': '#declaration' }
       ]
     },
     'if-statement': {
       'patterns': [
-
-      ]
-    },
-    'if-condition': {
-      'patterns': [
-
-      ]
-    },
-    'then-statement': {
-      'patterns': [
-
-      ]
-    },
-    'else-statement': {
-      'patterns': [
-
-      ]
-    },
-    'while-statement': {
-      'patterns': [
-        {
-          'match': 'while',
-          'name': 'keyword.control.while.d'
+        {
+          'match': 'if\\s+',
+          'name': 'keyword.control.if.d'
         },
         {
           'begin': '\\(',
@@ -998,10 +1024,57 @@
           'end': '\\)',
           'encCaptures': { '0': { 'name': 'support.type.parens-expression.end.d' } },
           'patterns': [
+            { 'include': '#if-condition' }
+          ]
+        },
+        { 'include': '#then-statement' },
+        {
+          'match': 'else\\s+',
+          'name': 'keyword.control.else.d'
+        },
+        { 'include': '#else-statement' }
+      ]
+    },
+    'if-condition': {
+      'patterns': [
+        {
+          'match': 'auto',
+          'name': 'storage.modifier.auto.d'
+        },
+        { 'include': '#equal' },
+        { 'include': '#ctors' },
+        { 'include': '#basic-type' },
+        { 'include': '#declarator' },
+        { 'include': '#expression' },
+        { 'include': '#identifier' }
+      ]
+    },
+    'then-statement': {
+      'patterns': [
+        { 'include': '#scope-statement' }
+      ]
+    },
+    'else-statement': {
+      'patterns': [
+        { 'include': '#scope-statement' }
+      ]
+    },
+    'while-statement': {
+      'patterns': [
+        {
+          'match': 'while',
+          'name': 'keyword.control.while.d'
+        },
+        {
+          'begin': '\\(',
+          'beginCaptures': { '0': { 'name': 'support.type.parens-expression.begin.d' } },
+          'end': '\\)',
+          'encCaptures': { '0': { 'name': 'support.type.parens-expression.end.d' } },
+          'patterns': [
             { 'include': '#expression' }
           ]
         },
-
+
       ]
     },
     'do-statement': {


Ignore missing dependencies

Ignore missing dependencies

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -10,7 +10,7 @@
 serverport = 5005

 gulp.task 'browserify', ->
-  browserify('./src/yayson.coffee', {extensions: ['.coffee']})
+  browserify('./src/yayson.coffee', {extensions: ['.coffee'], ignoreMissing: true})
     .transform(coffeeify)
     .bundle()
     .pipe(source('yayson.js'))


Fix packaging task

Fix packaging task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -132,10 +132,13 @@
 }
 gulp.task 'package:mac', ['browserify', 'html'], (done) ->
   packager(_.extend({}, packageConfig, platform: "darwin"), (err, output) ->
-    outputDir = path.join(path.dirname(output[0]), 'out')
-    outputArchive = path.join(outputDir, path.basename(output[0]))
+    distDir = path.dirname(output[0])
+    outputDir = path.join(distDir, 'out')
+    basename = path.basename(output[0])
+
     childProcess.execFileSync('mkdir', ['-p', outputDir])
-    childProcess.execFile('tar', ['cvzf', outputArchive + '.tar.gz', output[0]], (error, stdout, stderr) ->
+    outputArchive = path.join('out', basename + '.tar.gz')
+    childProcess.execFile('tar', ['cvzf', outputArchive, basename], {cwd: distDir}, (error, stdout, stderr) ->
       if (error)
         console.error(stderr)

@@ -144,10 +147,13 @@
   )
 gulp.task 'package:linux', ['browserify', 'html'], (done) ->
   packager(_.extend({}, packageConfig, platform: "linux"), (err, output) ->
-    outputDir = path.join(path.dirname(output[0]), 'out')
-    outputArchive = path.join(outputDir, path.basename(output[0]))
+    distDir = path.dirname(output[0])
+    outputDir = path.join(distDir, 'out')
+    basename = path.basename(output[0])
+
     childProcess.execFileSync('mkdir', ['-p', outputDir])
-    childProcess.execFile('tar', ['cvzf', outputArchive + '.tar.gz', output[0]], (error, stdout, stderr) ->
+    outputArchive = path.join('out', basename + '.tar.gz')
+    childProcess.execFile('tar', ['cvzf', outputArchive, basename], {cwd: distDir}, (error, stdout, stderr) ->
       if (error)
         console.error(stderr)


delete sc5

delete sc5

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -46,7 +46,6 @@

 spritesmith  = require 'gulp.spritesmith'

-styleguide   = require 'sc5-styleguide'
 hologram     = require 'gulp-hologram'
 outputPath   = 'styleguide'


enhance: compute source type

enhance: compute source type

--- app/models/invoice.coffee
+++ app/models/invoice.coffee
@@ -1,4 +1,5 @@
 `import DS from 'ember-data'`
+`import ENUMS from 'irene/enums'`

 Invoice = DS.Model.extend
   user : DS.belongsTo 'user', inverse: 'invoices'
@@ -7,4 +8,14 @@
   source: DS.attr 'number'
   pricing: DS.belongsTo 'pricing', inverse:'invoices'

+  sourceType: (->
+    switch @get "source"
+      when ENUMS.PAYMENT_SOURCE.PAYPAL then "paypal"
+      when ENUMS.PAYMENT_SOURCE.STRIPE_MANUAL then "stripeManual"
+      when ENUMS.PAYMENT_SOURCE.BANK_TRANSFER then "bankTransfer"
+      when ENUMS.PAYMENT_SOURCE.MANUAL then "manual"
+      when ENUMS.PAYMENT_SOURCE.STRIPE_RECURRING then "stripeRecurring"
+      else "none"
+  ).property "source"
+
 `export default Invoice`


cuting out one unnecessary jquery call

cuting out one unnecessary jquery call

--- web/scripts/gameSelection.coffee
+++ web/scripts/gameSelection.coffee
@@ -5,7 +5,7 @@
 		cont = $(tmp)
 		$(tmp).css(height:'100%', overflow:'auto',position:'relative')
 		$(tmp).attr("id","gameSelection")
-		$(@div).append(tmp)
+		@div.append(tmp)
 		return

 	destroy: () ->


[Front] Fix controller name for the ws connection params

--- app/scripts/app.js.coffee
+++ app/scripts/app.js.coffee
@@ -5,12 +5,11 @@
   App.addInitializer ->
     # Get websocket connection info
     connection_params = {}
-    $.get('websocket/', (data) ->
-      connection_params = JSON.parse(data)
+    $.get('ws/', (connection_params) ->
       # connect to server like normal
-      dispatcher = new WebSocketRails("#{connection_params.host}:#{connection_params.port}/websocket")
+      dispatcher = new WebSocketRails "#{connection_params.host}:#{connection_params.port}/websocket"
       # subscribe to the channel
-      channel = dispatcher.subscribe('my_game');
+      channel = dispatcher.subscribe 'my_game'
       channel.bind 'server_msg', (data) ->
         console.log data
     ).fail ->


fix toolpad show up bug

fix toolpad show up bug

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -216,7 +216,9 @@

 		# toolpad
 		$('body').delegate '#editor', 'mouseup', (e)=>
-			@detectToShowToolpad()
+			setTimeout =>
+				@detectToShowToolpad()
+			, 0
 		# apply link URL
 		$('#toolpad').delegate 'li:first-child', 'click', (e)->
 			self.openLinkInput()


refactor toggleFormat

refactor toggleFormat

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -142,6 +142,8 @@
 			height = $(@).outerHeight()
 			self.showTooltip()
 			self.setTooltipTop thisOffset.top - parentOffset.top
+		$('#tooltip').delegate '.left-panel li:nth-child(2)', 'click', (e)->
+			self.toggleFormatBlock $('.hovered').first(), 'h1'
 		$('body').delegate '#tooltip', 'mouseleave', (e)->
 			self.hideTooltip()

@@ -185,21 +187,20 @@
 		@status.titleEmpty = $('#editor-title').text() == '' || $('#editor-title').text() == @titlePromptMessage
 	checkEmpty: ->
 		@status.empty = $('#editor').text() == '' || $('#editor').text() == @promptMessage
-	toggleFormatBlock: (tag)->
-		el = @getSelectedElement()
-		if el.attr 'name'
+	toggleFormatBlock: (jqel, tag)->
+		if jqel.attr 'name'
 			@saveSelection()
-			if el.is tag
-				newEl = $('<p>').html el.html()
-				newEl.attr 'name', el.attr 'name'
-				el.after(newEl)
-				el.remove()
+			if jqel.is tag
+				newEl = $('<p>').html jqel.html()
+				newEl.attr 'name', jqel.attr 'name'
+				jqel.after(newEl)
+				jqel.remove()
 				@restoreSelection()
 			else
-				newEl = $("<#{tag}>").html el.html()
-				newEl.attr 'name', el.attr 'name'
-				el.after(newEl)
-				el.remove()
+				newEl = $("<#{tag}>").html jqel.html()
+				newEl.attr 'name', jqel.attr 'name'
+				jqel.after(newEl)
+				jqel.remove()
 				@restoreSelection()
 		else
 			document.execCommand('formatBlock', false, tag)
@@ -216,17 +217,17 @@
 		switch e.keyCode
 			when 49
 				if @status.cmd or @status.ctrl
-					@toggleFormatBlock 'h1'
+					@toggleFormatBlock @getSelectedElement(), 'h1'
 					e.preventDefault()
 					e.stopPropagation()
 			when 50
 				if @status.cmd or @status.ctrl
-					@toggleFormatBlock 'h2'
+					@toggleFormatBlock @getSelectedElement(), 'h2'
 					e.preventDefault()
 					e.stopPropagation()
 			when 51
 				if @status.cmd or @status.ctrl
-					@toggleFormatBlock 'pre'
+					@toggleFormatBlock @getSelectedElement(), 'pre'
 					e.preventDefault()
 					e.stopPropagation()
 			when 16


Help message

Help message

--- chat.coffee
+++ chat.coffee
@@ -48,6 +48,10 @@
 		@winList = [@systemWindow]

 		@partialNameLists = {}
+
+		@systemWindow.message '', "Welcome to irciii, a v2 Chrome app."
+		@systemWindow.message '', "Type /connect <server> [port] to connect, then /nick <my_nick> and /join <#channel>."
+		@systemWindow.message '', "Alt+[0-9] switches windows."

 	onConnected: => @status 'connected'
 	onDisconnected: => @status 'disconnected'


Update repl prompt to work with async requests

--- src/adapters/shell.coffee
+++ src/adapters/shell.coffee
@@ -4,12 +4,11 @@
 Adapter  = require '../adapter'

 class Shell extends Adapter
-  constructor: (robot) ->
-    super robot
-    @repl = null
-
   send: (user, strings...) ->
-    console.log str for str in strings
+    unless process.platform is "win32"
+      console.log "\033[01;32m#{str}\033[0m" for str in strings
+    else
+      console.log "#{str}" for str in strings
     @repl.prompt()

   reply: (user, strings...) ->
@@ -22,26 +21,17 @@
     process.on "uncaughtException", (err) =>
       @robot.logger.error "#{err}"

-    if Readline.createInterface.length > 3
-      @repl = Readline.createInterface stdin, null
-
-      stdin.on "data", (buffer) =>
-        @repl.write buffer
-    else
-      @repl = Readline.createInterface stdin, stdout, null
-
-    @repl.on "attemptClose", =>
-      @repl.close()
+    @repl = Readline.createInterface stdin, stdout, null

     @repl.on "close", =>
-      process.stdout.write "\n"
       stdin.destroy()
       process.exit 0

     @repl.on "line", (buffer) =>
+      @repl.close() if buffer.toLowerCase() is "exit"
+      @repl.prompt()
       user = @userForId '1', name: "Shell"
       @receive new Robot.TextMessage user, buffer
-      @repl.prompt()

     @repl.setPrompt "#{@robot.name}> "
     @repl.prompt()


don't fade out until ready to render

don't fade out until ready to render

--- app/views/js/views.coffee
+++ app/views/js/views.coffee
@@ -8,7 +8,6 @@

   initialize: ->
     @listenTo(@model, 'change:data change:error change:format', @dataUpdated)
-    @listenTo(@model, 'change:transform change:to change:from', @contentChanging)
     @listenTo(@model, 'refresh', @fetchData)

     @listenTo(@model, 'destroy', @remove)
@@ -17,8 +16,6 @@

   events:
     'click svg': 'cycleFormat'
-
-  contentChanging: -> @$el.removeClass('has-content')

   fetchData: -> @model.fetchData()
   getFormat: -> @model.get('format') || @defaultFormat
@@ -128,6 +125,7 @@

   renderGraph: ->
     return if @model.has('error')
+    @$el.removeClass('has-content')

     @setupGraph() unless @svg
     @updateDomain()


use new template naming

use new template naming

--- core/app/backbone/views/notifications_view.coffee
+++ core/app/backbone/views/notifications_view.coffee
@@ -1,5 +1,5 @@
 class NotificationsEmptyView extends Backbone.Marionette.ItemView
-   template: "notifications/_notifications"
+   template: "notifications/notifications"

 class window.NotificationsView extends Backbone.Factlink.CompositeView
@@ -10,7 +10,7 @@
   events:
     "click .unread": "clickHandler"

-  template: "notifications/_notifications"
+  template: "notifications/notifications"
   initialize: ->
     @itemView = NotificationView
     @setupNotificationsFetch()


update comments

update comments

--- src/bacon_and_eggs.coffee
+++ src/bacon_and_eggs.coffee
@@ -1,4 +1,4 @@
-# API docs here:  https://dev.twitter.com/docs/api/1.1/get/user
+# API:  https://dev.twitter.com/docs/api/1.1/get/user
 exports.requestUserStreaming = (params) ->
   {
     url: 'https://userstream.twitter.com/1.1/user.json'
@@ -6,7 +6,7 @@
     params: params
   }

-# API docs here: https://dev.twitter.com/docs/api/1.1/get/site
+# API: https://dev.twitter.com/docs/api/1.1/get/site
 exports.requestSiteStreaming = (params) ->
   {
     url: 'https://sitestream.twitter.com/1.1/site.json'
@@ -14,7 +14,7 @@
     params: params
   }

-# API docs here: https://dev.twitter.com/docs/api/1.1/get/statuses/sample
+# API: https://dev.twitter.com/docs/api/1.1/get/statuses/sample
 exports.requestStatusesSampleStreaming = (params) ->
   {
     url: 'https://stream.twitter.com/1.1/statuses/sample.json'
@@ -22,7 +22,7 @@
     params: params
   }

-# API docs here: https://dev.twitter.com/docs/api/1.1/get/statuses/firehose
+# API: https://dev.twitter.com/docs/api/1.1/get/statuses/firehose
 # NOTE: This endpoint requires special permission to access.
 exports.requestStatusesFirehoseStreaming = (params) ->
   {
@@ -31,7 +31,7 @@
     params: params
   }

-# API docs here: https://dev.twitter.com/docs/api/1.1/post/statuses/filter
+# API: https://dev.twitter.com/docs/api/1.1/post/statuses/filter
 # NOTE: At least one predicate parameter (follow, locations, or track)
 # must be specified in the POST body.
 exports.requestStatusesFilterStreaming = (params) ->
@@ -41,6 +41,7 @@
     params: params
   }

+# API:  https://dev.twitter.com/docs/api/1.1
 exports.request = (method, resource, params) ->
   {
     url: "https://api.twitter.com/1.1/#{resource}.json"


Allow skipping while moving backward

Allow skipping while moving backward

Add the ability to skip while moving backward (via previous click or other) in the tour. If the previous step is not available, then the tour will try the step before it.

--- src/bootstrap-tour.coffee
+++ src/bootstrap-tour.coffee
@@ -290,6 +290,7 @@
     ###
     _showStep: (i, def) ->
       step = @_getStep(i)
+      direction = if i >= @_current then "next" else "prev"

       unless step
         def.reject("Step #{i} undefined") if def
@@ -313,7 +314,7 @@
         # If step element is hidden or does not exist, skip step
         if $el.length is 0 or not $el.is(":visible")
           @trigger("skip", element:$el, step:step)
-          @next(def:def)
+          @[direction](def:def)
           return

         # Show popover


set breakpoints for vessel list

set breakpoints for vessel list

--- imports/ui/pages/vessels/vessels.coffee
+++ imports/ui/pages/vessels/vessels.coffee
@@ -18,15 +18,15 @@
 			columns = [
 				{ name: 'id', title: '', breakpoints: '', filterable: false }
 				{ name: 'localName', title: TAPi18n.__('vessels.localName'), breakpoints: '' }
-				{ name: 'flag', title: TAPi18n.__('vessels.flag'), breakpoints: '' }
-				{ name: 'type', title: TAPi18n.__('vessels.type'), breakpoints: '' }
+				{ name: 'flag', title: TAPi18n.__('vessels.flag'), breakpoints: 'xs' }
+				{ name: 'type', title: TAPi18n.__('vessels.type'), breakpoints: 'xs' }
 				{ name: 'callsign', title: TAPi18n.__('vessels.callsign'), breakpoints: '' }
-				{ name: 'eni', title: TAPi18n.__('vessels.eni'), breakpoints: '' }
-				{ name: 'imo', title: TAPi18n.__('vessels.imo'), breakpoints: '' }
-				{ name: 'mmsi', title: TAPi18n.__('vessels.mmsi'), breakpoints: '' }
-				{ name: 'lastVisit', title: TAPi18n.__('vessels.lastVisit'), breakpoints: '' }
-				{ name: 'contactPoint', title: TAPi18n.__('vessels.contactPoint'), breakpoints: '' }
-				{ name: 'nextVisit', title: TAPi18n.__('vessels.nextVisit'), breakpoints: '' }
+				{ name: 'eni', title: TAPi18n.__('vessels.eni'), breakpoints: 'xs' }
+				{ name: 'imo', title: TAPi18n.__('vessels.imo'), breakpoints: 'xs' }
+				{ name: 'mmsi', title: TAPi18n.__('vessels.mmsi'), breakpoints: 'xs' }
+				{ name: 'lastVisit', title: TAPi18n.__('vessels.lastVisit'), breakpoints: 'xs' }
+				{ name: 'contactPoint', title: TAPi18n.__('vessels.contactPoint'), breakpoints: 'xs' }
+				{ name: 'nextVisit', title: TAPi18n.__('vessels.nextVisit'), breakpoints: 'xs' }
 			]

 			rows = []


Add march=native flag to OFast

Add march=native flag to OFast

--- src/compiler-flags.coffee
+++ src/compiler-flags.coffee
@@ -1,5 +1,5 @@
 module.exports = FLAGS =
-    oF: '-Ofast'
+    oF: ['-Ofast', '-march=native']
     oS: '-Os'
     o3: ['-O3', '-march=native']
     o2: '-O2'


Refactoring. Now using capture filter to filter for ARP where source mac matches one of the given vendor ids.

--- amazing-dash-button.coffee
+++ amazing-dash-button.coffee
@@ -34,25 +34,17 @@
         @lastId = null

         @arpPacketHandler = (arp) =>
-          candidateArp = arp.info.srcmac.toUpperCase()
-          strippedArpTrunc = candidateArp.replace(/:/g,'').substring(0,6)
-
-          # List of registered Mac addresses with IEEE as of 18 July 2016 for Amazon Technologies Inc.
-          # source: https://regauth.standards.ieee.org/standards-ra-web/pub/view.html#registries
-          amazonVendorIds = [
-            "747548", "F0D2F1", "8871E5", "74C246", "F0272D", "0C47C9",
-            "A002DC", "AC63BE", "44650D", "50F5DA", "84D6D0"
-          ]
-          if strippedArpTrunc in amazonVendorIds and candidateArp not in @candidatesSeen
-            @base.debug 'Amazon device (possibly a dash-button) detected: ' + candidateArp
-            @candidatesSeen.push candidateArp
+          candidateArpAddress = arp.info.srcmac.toUpperCase()
+          if candidateArpAddress not in @candidatesSeen
+            @base.debug 'Amazon device (possibly a dash-button) detected: ' + candidateArpAddress
+            @candidatesSeen.push candidateArpAddress
             @lastId = @base.generateDeviceId @framework, "dash", @lastId

             deviceConfig =
               id: @lastId
               name: @lastId
               class: 'AmazingDashButton'
-              macAddress: candidateArp
+              macAddress: candidateArpAddress

             @framework.deviceManager.discoveredDevice(
               'pimatic-amazing-dash-button', "#{deviceConfig.name} (#{deviceConfig.macAddress})", deviceConfig
@@ -72,7 +64,18 @@
         device = cap.findDevice()
       @base.debug "Sniffing for ARP requests on device", device

-      linkType = @capture.open device, 'arp', 1024 * 1024, @buffer
+      # List of registered Mac addresses with IEEE as of 18 July 2016 for Amazon Technologies Inc.
+      # source: https://regauth.standards.ieee.org/standards-ra-web/pub/view.html#registries
+      amazonVendorIds = [
+        "747548", "F0D2F1", "8871E5", "74C246", "F0272D", "0C47C9", "A002DC", "AC63BE", "44650D", "50F5DA", "84D6D0"
+      ]
+      filter = amazonVendorIds.map( (vendorId) ->
+        "(ether[6:2] == 0x#{vendorId.substring 0,4} and ether[8:1] == 0x#{vendorId.substring 4,6})"
+      ).reduce( (left, right) ->
+        left + " or " + right
+      )
+
+      linkType = @capture.open device, "arp and (#{filter})", 10 * 65536, @buffer
       try
         @capture.setMinBytes 0
       catch e
@@ -117,7 +120,6 @@

     getContact: () -> Promise.resolve @_contact

-
   # ###Finally
   # Create a instance of my plugin
   # and return it to the framework.


Immediately fetch new buffers' git status in GitRepository

We used to emit buffer added events before the buffers were loaded, so we
would end up fetching their git status soon afterwords, when they emitted
the did-reload event.

--- src/git-repository.coffee
+++ src/git-repository.coffee
@@ -437,6 +437,7 @@
       if bufferPath = buffer.getPath()
         @getPathStatus(bufferPath)

+    getBufferPathStatus()
     bufferSubscriptions = new CompositeDisposable
     bufferSubscriptions.add buffer.onDidSave(getBufferPathStatus)
     bufferSubscriptions.add buffer.onDidReload(getBufferPathStatus)


Remove empty package-generator keymap

Remove empty package-generator keymap


add a apply db file change operation to filesystem operations

--- backend/filesystem.coffee
+++ backend/filesystem.coffee
@@ -62,6 +62,7 @@
             if err
                 callback err
             else
+                log.info "Directory ensured: #{absPath}"
                 binary.infoPublisher.emit 'directoryCreated', absPath

                 # Update directory information
@@ -76,7 +77,6 @@
     # Changes is the queue of operations, it contains
     # files that are being downloaded, and files to upload.
     changes: async.queue (task, callback) ->
-        #console.log task.operation, task.file
         deviceName = config.getDeviceName()

         if task.operation in [
@@ -89,14 +89,8 @@
             filesystem.watchingLocked = true
             callbackOrig = callback
             callback = (err, res) ->
-                #console.log 'done'
                 filesystem.watchingLocked = false
                 callbackOrig err, res
-        #else
-        #    callback_orig = callback
-        #    callback = (err, res) ->
-        #        console.log 'done'
-        #        callback_orig err, res

         switch task.operation
             when 'post'
@@ -122,6 +116,8 @@
                 filesystem.applyFileDBChanges true, callback
             when 'reDownload'
                 filesystem.applyFileDBChanges false, callback
+            when 'applyFileDBChanges'
+                filesystem.applyFileDBChanges false, callback
             else
                 # 'applyFolderDBChanges'
                 filesystem.applyFolderDBChanges callback
@@ -411,6 +407,7 @@
             if err and err.status isnt 404
                 callback err
             else if res?.path? and fs.existsSync res.path
+                log.info "Remove element at #{res.path}"
                 fs.unlink res.path, ->
                     callback null
             else


use spaces

use spaces

--- layout.coffee
+++ layout.coffee
@@ -1,135 +1,135 @@
 hackstyle = do ->
-	# hack jQuery to send a style event when CSS changes
-	stylemap= {left: 'x', top: 'y', 'background-color': 'bgcolor'}
-	origstyle = $.style
-	styletap = (elem, name, value) ->
-	  returnval = origstyle.apply(this, arguments)
-	  name = stylemap[name] or name
-  	view = elem.$view
-	  if (view[name] != value and view?.events?[name])
-#		  console.log('sending style', name, elem.$view._locked) if sendstyle
-	    view.setAttribute(name, value, true)
-
-	  returnval
-
-	return (active) ->
-		if active
-			$.style = styletap
-		else
-			$.style = origstyle
+  # hack jQuery to send a style event when CSS changes
+  stylemap= {left: 'x', top: 'y', 'background-color': 'bgcolor'}
+  origstyle = $.style
+  styletap = (elem, name, value) ->
+    returnval = origstyle.apply(this, arguments)
+    name = stylemap[name] or name
+    view = elem.$view
+    if (view[name] != value and view?.events?[name])
+#      console.log('sending style', name, elem.$view._locked) if sendstyle
+      view.setAttribute(name, value, true)
+
+    returnval
+
+  return (active) ->
+    if active
+      $.style = styletap
+    else
+      $.style = origstyle

 window.lz = do ->
-	# from https://github.com/spine/spine/tree/dev/src
-	Events =
-	  bind: (ev, callback) ->
-#	    console.log 'binding', ev, callback
-	    evs   = ev.split(' ')
-	    calls = @hasOwnProperty('events') and @events or= {}
-	    for name in evs
-	      calls[name] or= []
-	      calls[name].push(callback)
-	    this
-
-	  one: (ev, callback) ->
-	    @bind ev, ->
-	      @unbind(ev, arguments.callee)
-	      callback.apply(this, arguments)
-
-	  trigger: (args...) ->
-	    ev = args.shift()
-	    list = @hasOwnProperty('events') and @events?[ev]
-	    return unless list
-#	    if list then console.log 'trigger', ev, list
-	    for callback in list
-	      if callback.apply(this, args) is false
-	        break
-	    true
-
-	  listenTo: (obj, ev, callback) ->
-	    obj.bind(ev, callback)
-	    @listeningTo or= []
-	    @listeningTo.push obj
-	    this
-
-	  listenToOnce: (obj, ev, callback) ->
-	    listeningToOnce = @listeningToOnce or = []
-	    listeningToOnce.push obj
-	    obj.one ev, ->
-	      idx = listeningToOnce.indexOf(obj)
-	      listeningToOnce.splice(idx, 1) unless idx is -1
-	      callback.apply(this, arguments)
-	    this
-
-	  stopListening: (obj, ev, callback) ->
-	    if obj
-	      obj.unbind(ev, callback)
-	      for listeningTo in [@listeningTo, @listeningToOnce]
-	        continue unless listeningTo
-	        idx = listeningTo.indexOf(obj)
-	        listeningTo.splice(idx, 1) unless idx is -1
-	    else
-	      for obj in @listeningTo
-	        obj.unbind()
-	      @listeningTo = undefined
-
-	  unbind: (ev, callback) ->
-	    unless ev
-	      @events = {}
-	      return this
-	    evs  = ev.split(' ')
-	    for name in evs
-	      list = @events?[name]
-	      continue unless list
-	      unless callback
-	        delete @events[name]
-	        continue
-	      for cb, i in list when (cb is callback)
-	        list = list.slice()
-	        list.splice(i, 1)
-	        @events[name] = list
-	        break
-	    this
-
-	Events.on = Events.bind
-	Events.off = Events.unbind
-
-
-	# mixins adapted from https://github.com/spine/spine/tree/dev/src
-	moduleKeywords = ['included', 'extended']
-	class Module
-	  @include: (obj) ->
-	    throw new Error('include(obj) requires obj') unless obj
-	    for key, value of obj when key not in moduleKeywords
-#		    	console.log key, obj, @::
-	      @::[key] = value
-	    obj.included?.call(this, obj)
-	    this
-
-
-	class Node extends Module
-		@include Events
-
-		constructor: (el, attributes = {}) ->
-			@types = {x: 'number', y: 'number', width: 'number', height: 'number'}
-			if attributes.types
-				for name, type of attributes.types
-#					console.log 'adding type', name, type
-					@types[name] = type
-				delete attributes.types
-
-#			console.log 'new node', @, attributes
-			@init(attributes)
-
-		scopes = null
-		propertyBindings =
-			find: (expression) ->
-				ast = acorn.parse(expression)
-				scopes = []
-				acorn.walkDown(ast, @)
-				return scopes
-			MemberExpression: (n) ->
-				# grab the property name
+  # from https://github.com/spine/spine/tree/dev/src
+  Events =
+    bind: (ev, callback) ->
+#      console.log 'binding', ev, callback
+      evs   = ev.split(' ')
+      calls = @hasOwnProperty('events') and @events or= {}
+      for name in evs
+        calls[name] or= []
+        calls[name].push(callback)
+      this
+
+    one: (ev, callback) ->
+      @bind ev, ->
+        @unbind(ev, arguments.callee)
+        callback.apply(this, arguments)
+
+    trigger: (args...) ->
+      ev = args.shift()
+      list = @hasOwnProperty('events') and @events?[ev]
+      return unless list
+#      if list then console.log 'trigger', ev, list
+      for callback in list
+        if callback.apply(this, args) is false
+          break
+      true
+
+    listenTo: (obj, ev, callback) ->
+      obj.bind(ev, callback)
+      @listeningTo or= []
+      @listeningTo.push obj
+      this
+
+    listenToOnce: (obj, ev, callback) ->
+      listeningToOnce = @listeningToOnce or = []
+      listeningToOnce.push obj
+      obj.one ev, ->
+        idx = listeningToOnce.indexOf(obj)
+        listeningToOnce.splice(idx, 1) unless idx is -1
+        callback.apply(this, arguments)
+      this
+
+    stopListening: (obj, ev, callback) ->
+      if obj
+        obj.unbind(ev, callback)
+        for listeningTo in [@listeningTo, @listeningToOnce]
+          continue unless listeningTo
+          idx = listeningTo.indexOf(obj)
+          listeningTo.splice(idx, 1) unless idx is -1
+      else
+        for obj in @listeningTo
+          obj.unbind()
+        @listeningTo = undefined
+
+    unbind: (ev, callback) ->
+      unless ev
+        @events = {}
+        return this
+      evs  = ev.split(' ')
+      for name in evs
+        list = @events?[name]
+        continue unless list
+        unless callback
+          delete @events[name]
+          continue
+        for cb, i in list when (cb is callback)
+          list = list.slice()
+          list.splice(i, 1)
+          @events[name] = list
+          break
+      this
+
+  Events.on = Events.bind
+  Events.off = Events.unbind
+
+
+  # mixins adapted from https://github.com/spine/spine/tree/dev/src
+  moduleKeywords = ['included', 'extended']
+  class Module
+    @include: (obj) ->
+      throw new Error('include(obj) requires obj') unless obj
+      for key, value of obj when key not in moduleKeywords
+#          console.log key, obj, @::
+        @::[key] = value
+      obj.included?.call(this, obj)
+      this
+
+
+  class Node extends Module
+    @include Events
+
+    constructor: (el, attributes = {}) ->
+      @types = {x: 'number', y: 'number', width: 'number', height: 'number'}
+      if attributes.types
+        for name, type of attributes.types
+#          console.log 'adding type', name, type
+          @types[name] = type
+        delete attributes.types
+
+#      console.log 'new node', @, attributes
+      @init(attributes)
+
+    scopes = null
+    propertyBindings =
+      find: (expression) ->
+        ast = acorn.parse(expression)
+        scopes = []
+        acorn.walkDown(ast, @)
+        return scopes
+      MemberExpression: (n) ->
+        # grab the property name
         name = n.property.name

         # remove the property so we can compute the rest of the expression
@@ -139,375 +139,375 @@
 #       console.log 'MemberExpression', name, acorn.stringify n
         return true

-		matchConstraint = /\${(.+)}/
-		applyConstraint: (name, expression) ->
-			@constraints ?= {}
-			@constraints[name] = (new Function([], 'return ' + expression)).bind(@)
-#			console.log 'adding constraint', name, expression, @
-#				console.log 'eval', @constraints[name]()
-
-			scopes = propertyBindings.find(expression)
-#				console.log 'found scopes', scopes
-
-			constraintBinding = @constraints[name]
-			bindings = constraintBinding.bindings or= {}
-
-			for scope in scopes
-				bindexpression = scope.binding
-				scope.compiled = (new Function([], 'return ' + bindexpression)).bind(@)
-				bindings[bindexpression] = scope
-#				console.log 'applied', scope.property, bindexpression, 'for', @
-
-#				console.log 'matched constraint', name, @, expression
-
-		setAttribute: (name, value) ->
-			if @[name] != value
-				constraint = value.match?(matchConstraint)
-				if constraint
-			  	@applyConstraint(name, constraint[1])
-			  	return
-
-				# coerce value to type
-				if name of @types
-					type = @types[name]
-					if type == 'number'
-						value = parseFloat(value)
-#				console.log 'type', name, type, value
-
-#			console.log 'setAttribute', name, value
-				setter = 'set_' + name
-				if setter of @
-	#				console.log 'calling setter', setter, value #, @[setter]
-					@[setter]?(value)
-				else if name.indexOf('on') == 0
-					name = name.substr(2)
-#					console.log('binding to event expression', name, value, @)
-					@bind(name, @eventCallback(name, value, @))
-					return
-				else
-		#			console.log 'setting style', name, value
-					@[name] = value
-
-			# send event
-			@trigger(name, @, name, value) if @events?[name]
-
-		# generate a callback for an event expression in a way that preserves scope, e.g. on_x="console.log(value, this, ...)"
-		eventCallback: (name, js, scope) ->
-#			console.log 'binding to event expression', name, js, scope
-			() ->
-				val = scope[name]
-#				console.log 'event callback', name, val, scope
-				(new Function(['value'], js)).bind(scope)(val)
-
-		bindConstraints: () ->
-			# register constraints last
-			for name, value of @constraints
-#				console.log 'binding constraint', name, value, this
-				@setAttribute(name, value())
-				for bindexpression, binding of @constraints[name].bindings
-					property = binding.property
-					boundref = binding.compiled()
-					boundref = boundref.$view if boundref.$view
-#					console.log 'binding to', property, 'on', boundref
-					boundref.bind(property, @constraintCallback(name, value))
-
-		# generate a callback for a constraint expression, e.g. x="${this.parent.baz.x + 10}"
-		constraintCallback: (name, value) ->
-#			console.log('binding to constraint expression', name, fn, @)
-			() =>
-#				console.log 'setting', name, fn(), @
-				@setAttribute(name, value())
-
-		init: (attributes) ->
-			for name, value of attributes
-				@setAttribute(name, value)
-			@bindConstraints() if @constraints
-			@trigger('init', @) if @events?[name]
-
-		set_parent: (parent) ->
-#			console.log 'set_parent', parent, @
-			# normalize to jQuery object
-			if parent instanceof Node
-				# store references to parent and children
-				@parent = parent
-				parent[@name] = @ if @name?
-				parent.subnodes ?= []
-				parent.subnodes.push(@)
-				parent.trigger('subnodes', @) if parent.events?['subnodes']
-
-		set_name: (@name) ->
-#			console.log 'set_name', name, this
-			@parent?[name] = @
-
-
-	class Sprite
-#		guid = 0
-		stylemap= {x: 'left', y: 'top', bgcolor: 'background-color'}
-
-		constructor: (@jqel = $('<div/>'), view) ->
-#			console.log 'new sprite', @jqel, view
-			@jqel = $(@jqel) unless @jqel instanceof jQuery
-			@jqel[0].$view = view
-
-			# normalize to jQuery object
-#			guid++
-#			@jqel.attr('id', 'jqel-' + guid) if not @jqel.attr('id')
-			@jqel.addClass('sprite')
-		setStyle: (name, value) ->
-			value ?= ''
-			name = stylemap[name] if name of stylemap
-			console.log('setStyle', name, value)
-			@jqel.css(name, value)
-		set_parent: (parent) ->
-			if parent instanceof Sprite
-				parent = parent.jqel
-
-			parent = $(parent) unless parent instanceof jQuery
-	#		console.log 'set_parent', parent
-			parent.append(@jqel)
-		set_id: (@id) ->
-#			console.log('setid', @id)
-			@jqel.attr('id', @id)
-		animate: =>
-#			console.log 'sprite animate', arguments, @jqel
-			@jqel.animate.apply(@jqel, arguments)
-
-
-	ignoredAttributes = {parent: true, id: true, name: true, extends: true}
-	class View extends Node
-		constructor: (el, attributes = {}) ->
-			if (el instanceof HTMLElement and el.$view)
-				console.warn 'already bound view', el.$view, el
-				return
-
-			if (el and el instanceof View)
-				el = el.sprite
-
-			@sprite = new Sprite(el, @)
-
-			super(el, attributes)
-#			console.log 'new view', el, attributes, @
-
-		setAttribute: (name, value, skipsend) ->
-			if (skipsend or ignoredAttributes[name] or value == this[name])
-#				console.log 'skipping style', name, this[name], value, @
-			else
-				@sprite.setStyle(name, value)
-			super(name, value)
-
-		set_parent: (parent) ->
-#			console.log 'view set_parent', parent, @
-			super(parent)
-
-			# store references subviews
-			if parent instanceof View
-				parent.subviews ?= []
-				parent.subviews.push(@)
-				parent.trigger('subviews', @) if parent.events?['subviews']
-				parent = parent.sprite
-			@sprite.set_parent parent
-
-		set_id: (@id) ->
-			@sprite.set_id(id)
-
-		animate: ->
-#			console.log 'animate', arguments, @sprite.animate
-			@sprite.animate.apply(this, arguments)
-
-
-	# init classes based on an existing element
-	initFromElement = (el, parent) ->
-		tagname = el.localName
-		if not (tagname of lz)
-			console.warn 'could not find class for tag', tagname, el
-			return
-
-		attributes = {}
-		for i in el.attributes
-	#				console.log 'attribute', i.name, i.value
-			attributes[i.name] = i.value
-
-		# swallow builtin attributes
-		for event in mouseEvents
-			el.removeAttribute('on' + event)
-
-		parent ?= el.parentNode
-		attributes.parent = parent
-#		console.log 'parent', tagname, attributes, parent
-
-		children = (child for child in el.childNodes when child.nodeType == 1)
-
-		parent = new lz[tagname](el, attributes)
-
-		for child in children
-#			console.log 'creating child', child, parent
-	#		console.dir(child)
-			if tagname is 'class'
-				child.$defer = true
-			else
-				initFromElement(child, parent)
-
-	# init all views in the DOM recursively
-	init = (selector = $('view')) ->
-		for el, i in selector
-			initFromElement(el) unless el.$defer
-		# listen for jQuery style changes
-		hackstyle(true)
-
-
-	class Class
-		constructor: (el, classattributes = {}) ->
-			name = classattributes.name
-			ext = classattributes.extends ?= 'view'
-			for ignored of ignoredAttributes
-				delete classattributes[ignored]
-
-			body = el.innerHTML
-			el.innerHTML = ''
-#			console.log('new class', name, classattributes)
-			console.warn 'class exists, overwriting', name if name of lz
-			lz[name] = (instanceel, instanceattributes) ->
-				attributes = {}
-				for key, value of classattributes
-					attributes[key] = instanceattributes[key] ? value
-				for key, value of instanceattributes
-#  				console.log 'overriding class attribute', key, value
-					attributes[key] = value
-#				console.log 'creating class instance', name, attributes
-				parent = new lz[ext](instanceel, attributes)
-#				console.log 'created instance', name, parent
-
-				viewel = parent.sprite?.jqel?[0]
-				return if not viewel
-
-				viewel.innerHTML = body
-				children = (child for child in viewel.childNodes when child.nodeType == 1)
-				for child in children
-					delete child.$defer
-#					console.log 'creating class child in parent', child, parent
-					initFromElement(child, parent)
-
-
-	class Layout extends Node
-		constructor: (el, attributes = {}) ->
-			@locked = true
-			super(el, attributes)
-			@parent.bind('subviews', @added)
-			subviews = @parent.subviews
-			if subviews
-				for subview in subviews
-					@added(subview)
-			@locked = false
-			@update()
-			#console.log('layout', @parent, attributes)
-
-		added: (child) =>
-#			console.log 'added', child, @
-			@update(child)
-
-		update: (sender) ->
-#			console.log 'update', sender
-
-
-	class SimpleLayout extends Layout
-		attribute = 'x'
-		axis = 'width'
-		spacing = 10
-		inset = 10
-
-		constructor: (el, attributes = {}) ->
-			attributes.types ?= {}
-			attributes.types.spacing = 'number'
-			attributes.types.inset = 'number'
-			super(el, attributes)
-			@update()
-
-		set_attribute: (attr) ->
-			axis = switch attr
-				when 'x' then 'width'
-				when 'y' then 'height'
-			axis = 'width' if attr is 'x'
-			attribute = attr
-#			console.log('set_attribute', attr, typeof attr)
-			@update()
-
-		set_spacing: (space) ->
-#			console.log('set_spacing', space, typeof space)
-			spacing = space
-			@update()
-
-		set_inset: (i) ->
-#			console.log('set_spacing', space, typeof space)
-			inset = i
-			@update()
-
-		added: (child) ->
-#			console.log 'added', child
-			child.bind(axis, @update)
-			super(child)
-
-		update: (sender) ->
-			if @locked
-#				console.log 'locked'
-				return
-			subviews = @parent.subviews
-			if not subviews
-				return
-			super(sender)
-			pos = inset
-			skip = true if sender
-			for subview in subviews
-				if (skip and subview != sender)
-#					console.log 'skipping', subview
-				else
-#					console.log 'updating', subview, attribute, pos
-					subview.setAttribute(attribute, pos) unless subview[attribute] == pos
-					skip = false
-
-				pos += spacing + subview[axis]
-
-
-	# singleton that listens for mouse position and holds the most recent left and top coordinates
-	mouseEvents = ['click', 'mouseover', 'mouseout', 'mousedown', 'mouseup']
-	class Mouse extends Module
-		constructor: () ->
-		  @docSelector = $(document)
-		  for event in mouseEvents
-		  	@docSelector.on(event, @handler)
-	  sender: () ->
-	    trigger("mousemove", left, top)
-	  handler: (event) ->
-	  	view = event.target.$view
-	  	type = event.type
-	  	if view?.events?[type]
-		  	view.trigger(event.type, view)
-#	  	console.log 'event', event.type, event.target.$view
-	  	if @started and type == 'mousemove' and @events['mousemove']
-		    requestTick 0, sender
-		    @left = event.pageX
-		    @top = event.pageY
-	  start: () ->
-	    return if @started
-	    @started = true
-	    @docSelector.on("mousemove", @handler).one("mouseout", @stop)
-	  stop: () ->
-	    return if not @started
-	    @started = false
-	    @docSelector.off("mousemove", @handler).one("mouseover", @start)
-
-	  mouse = new Mouse()
-
-	exports = {
-		view: View,
-		class: Class,
-		node: Node,
-		layout: Layout,
-		simplelayout: SimpleLayout,
-		initViews: init
-	}
+    matchConstraint = /\${(.+)}/
+    applyConstraint: (name, expression) ->
+      @constraints ?= {}
+      @constraints[name] = (new Function([], 'return ' + expression)).bind(@)
+#      console.log 'adding constraint', name, expression, @
+#        console.log 'eval', @constraints[name]()
+
+      scopes = propertyBindings.find(expression)
+#        console.log 'found scopes', scopes
+
+      constraintBinding = @constraints[name]
+      bindings = constraintBinding.bindings or= {}
+
+      for scope in scopes
+        bindexpression = scope.binding
+        scope.compiled = (new Function([], 'return ' + bindexpression)).bind(@)
+        bindings[bindexpression] = scope
+#        console.log 'applied', scope.property, bindexpression, 'for', @
+
+#        console.log 'matched constraint', name, @, expression
+
+    setAttribute: (name, value) ->
+      if @[name] != value
+        constraint = value.match?(matchConstraint)
+        if constraint
+          @applyConstraint(name, constraint[1])
+          return
+
+        # coerce value to type
+        if name of @types
+          type = @types[name]
+          if type == 'number'
+            value = parseFloat(value)
+#        console.log 'type', name, type, value
+
+#      console.log 'setAttribute', name, value
+        setter = 'set_' + name
+        if setter of @
+  #        console.log 'calling setter', setter, value #, @[setter]
+          @[setter]?(value)
+        else if name.indexOf('on') == 0
+          name = name.substr(2)
+#          console.log('binding to event expression', name, value, @)
+          @bind(name, @eventCallback(name, value, @))
+          return
+        else
+    #      console.log 'setting style', name, value
+          @[name] = value
+
+      # send event
+      @trigger(name, @, name, value) if @events?[name]
+
+    # generate a callback for an event expression in a way that preserves scope, e.g. on_x="console.log(value, this, ...)"
+    eventCallback: (name, js, scope) ->
+#      console.log 'binding to event expression', name, js, scope
+      () ->
+        val = scope[name]
+#        console.log 'event callback', name, val, scope
+        (new Function(['value'], js)).bind(scope)(val)
+
+    bindConstraints: () ->
+      # register constraints last
+      for name, value of @constraints
+#        console.log 'binding constraint', name, value, this
+        @setAttribute(name, value())
+        for bindexpression, binding of @constraints[name].bindings
+          property = binding.property
+          boundref = binding.compiled()
+          boundref = boundref.$view if boundref.$view
+#          console.log 'binding to', property, 'on', boundref
+          boundref.bind(property, @constraintCallback(name, value))
+
+    # generate a callback for a constraint expression, e.g. x="${this.parent.baz.x + 10}"
+    constraintCallback: (name, value) ->
+#      console.log('binding to constraint expression', name, fn, @)
+      () =>
+#        console.log 'setting', name, fn(), @
+        @setAttribute(name, value())
+
+    init: (attributes) ->
+      for name, value of attributes
+        @setAttribute(name, value)
+      @bindConstraints() if @constraints
+      @trigger('init', @) if @events?[name]
+
+    set_parent: (parent) ->
+#      console.log 'set_parent', parent, @
+      # normalize to jQuery object
+      if parent instanceof Node
+        # store references to parent and children
+        @parent = parent
+        parent[@name] = @ if @name?
+        parent.subnodes ?= []
+        parent.subnodes.push(@)
+        parent.trigger('subnodes', @) if parent.events?['subnodes']
+
+    set_name: (@name) ->
+#      console.log 'set_name', name, this
+      @parent?[name] = @
+
+
+  class Sprite
+#    guid = 0
+    stylemap= {x: 'left', y: 'top', bgcolor: 'background-color'}
+
+    constructor: (@jqel = $('<div/>'), view) ->
+#      console.log 'new sprite', @jqel, view
+      @jqel = $(@jqel) unless @jqel instanceof jQuery
+      @jqel[0].$view = view
+
+      # normalize to jQuery object
+#      guid++
+#      @jqel.attr('id', 'jqel-' + guid) if not @jqel.attr('id')
+      @jqel.addClass('sprite')
+    setStyle: (name, value) ->
+      value ?= ''
+      name = stylemap[name] if name of stylemap
+      console.log('setStyle', name, value)
+      @jqel.css(name, value)
+    set_parent: (parent) ->
+      if parent instanceof Sprite
+        parent = parent.jqel
+
+      parent = $(parent) unless parent instanceof jQuery
+  #    console.log 'set_parent', parent
+      parent.append(@jqel)
+    set_id: (@id) ->
+#      console.log('setid', @id)
+      @jqel.attr('id', @id)
+    animate: =>
+#      console.log 'sprite animate', arguments, @jqel
+      @jqel.animate.apply(@jqel, arguments)
+
+
+  ignoredAttributes = {parent: true, id: true, name: true, extends: true}
+  class View extends Node
+    constructor: (el, attributes = {}) ->
+      if (el instanceof HTMLElement and el.$view)
+        console.warn 'already bound view', el.$view, el
+        return
+
+      if (el and el instanceof View)
+        el = el.sprite
+
+      @sprite = new Sprite(el, @)
+
+      super(el, attributes)
+#      console.log 'new view', el, attributes, @
+
+    setAttribute: (name, value, skipsend) ->
+      if (skipsend or ignoredAttributes[name] or value == this[name])
+#        console.log 'skipping style', name, this[name], value, @
+      else
+        @sprite.setStyle(name, value)
+      super(name, value)
+
+    set_parent: (parent) ->
+#      console.log 'view set_parent', parent, @
+      super(parent)
+
+      # store references subviews
+      if parent instanceof View
+        parent.subviews ?= []
+        parent.subviews.push(@)
+        parent.trigger('subviews', @) if parent.events?['subviews']
+        parent = parent.sprite
+      @sprite.set_parent parent
+
+    set_id: (@id) ->
+      @sprite.set_id(id)
+
+    animate: ->
+#      console.log 'animate', arguments, @sprite.animate
+      @sprite.animate.apply(this, arguments)
+
+
+  # init classes based on an existing element
+  initFromElement = (el, parent) ->
+    tagname = el.localName
+    if not (tagname of lz)
+      console.warn 'could not find class for tag', tagname, el
+      return
+
+    attributes = {}
+    for i in el.attributes
+  #        console.log 'attribute', i.name, i.value
+      attributes[i.name] = i.value
+
+    # swallow builtin attributes
+    for event in mouseEvents
+      el.removeAttribute('on' + event)
+
+    parent ?= el.parentNode
+    attributes.parent = parent
+#    console.log 'parent', tagname, attributes, parent
+
+    children = (child for child in el.childNodes when child.nodeType == 1)
+
+    parent = new lz[tagname](el, attributes)
+
+    for child in children
+#      console.log 'creating child', child, parent
+  #    console.dir(child)
+      if tagname is 'class'
+        child.$defer = true
+      else
+        initFromElement(child, parent)
+
+  # init all views in the DOM recursively
+  init = (selector = $('view')) ->
+    for el, i in selector
+      initFromElement(el) unless el.$defer
+    # listen for jQuery style changes
+    hackstyle(true)
+
+
+  class Class
+    constructor: (el, classattributes = {}) ->
+      name = classattributes.name
+      ext = classattributes.extends ?= 'view'
+      for ignored of ignoredAttributes
+        delete classattributes[ignored]
+
+      body = el.innerHTML
+      el.innerHTML = ''
+#      console.log('new class', name, classattributes)
+      console.warn 'class exists, overwriting', name if name of lz
+      lz[name] = (instanceel, instanceattributes) ->
+        attributes = {}
+        for key, value of classattributes
+          attributes[key] = instanceattributes[key] ? value
+        for key, value of instanceattributes
+#          console.log 'overriding class attribute', key, value
+          attributes[key] = value
+#        console.log 'creating class instance', name, attributes
+        parent = new lz[ext](instanceel, attributes)
+#        console.log 'created instance', name, parent
+
+        viewel = parent.sprite?.jqel?[0]
+        return if not viewel
+
+        viewel.innerHTML = body
+        children = (child for child in viewel.childNodes when child.nodeType == 1)
+        for child in children
+          delete child.$defer
+#          console.log 'creating class child in parent', child, parent
+          initFromElement(child, parent)
+
+
+  class Layout extends Node
+    constructor: (el, attributes = {}) ->
+      @locked = true
+      super(el, attributes)
+      @parent.bind('subviews', @added)
+      subviews = @parent.subviews
+      if subviews
+        for subview in subviews
+          @added(subview)
+      @locked = false
+      @update()
+      #console.log('layout', @parent, attributes)
+
+    added: (child) =>
+#      console.log 'added', child, @
+      @update(child)
+
+    update: (sender) ->
+#      console.log 'update', sender
+
+
+  class SimpleLayout extends Layout
+    attribute = 'x'
+    axis = 'width'
+    spacing = 10
+    inset = 10
+
+    constructor: (el, attributes = {}) ->
+      attributes.types ?= {}
+      attributes.types.spacing = 'number'
+      attributes.types.inset = 'number'
+      super(el, attributes)
+      @update()
+
+    set_attribute: (attr) ->
+      axis = switch attr
+        when 'x' then 'width'
+        when 'y' then 'height'
+      axis = 'width' if attr is 'x'
+      attribute = attr
+#      console.log('set_attribute', attr, typeof attr)
+      @update()
+
+    set_spacing: (space) ->
+#      console.log('set_spacing', space, typeof space)
+      spacing = space
+      @update()
+
+    set_inset: (i) ->
+#      console.log('set_spacing', space, typeof space)
+      inset = i
+      @update()
+
+    added: (child) ->
+#      console.log 'added', child
+      child.bind(axis, @update)
+      super(child)
+
+    update: (sender) ->
+      if @locked
+#        console.log 'locked'
+        return
+      subviews = @parent.subviews
+      if not subviews
+        return
+      super(sender)
+      pos = inset
+      skip = true if sender
+      for subview in subviews
+        if (skip and subview != sender)
+#          console.log 'skipping', subview
+        else
+#          console.log 'updating', subview, attribute, pos
+          subview.setAttribute(attribute, pos) unless subview[attribute] == pos
+          skip = false
+
+        pos += spacing + subview[axis]
+
+
+  # singleton that listens for mouse position and holds the most recent left and top coordinates
+  mouseEvents = ['click', 'mouseover', 'mouseout', 'mousedown', 'mouseup']
+  class Mouse extends Module
+    constructor: () ->
+      @docSelector = $(document)
+      for event in mouseEvents
+        @docSelector.on(event, @handler)
+    sender: () ->
+      trigger("mousemove", left, top)
+    handler: (event) ->
+      view = event.target.$view
+      type = event.type
+      if view?.events?[type]
+        view.trigger(event.type, view)
+#      console.log 'event', event.type, event.target.$view
+      if @started and type == 'mousemove' and @events['mousemove']
+        requestTick 0, sender
+        @left = event.pageX
+        @top = event.pageY
+    start: () ->
+      return if @started
+      @started = true
+      @docSelector.on("mousemove", @handler).one("mouseout", @stop)
+    stop: () ->
+      return if not @started
+      @started = false
+      @docSelector.off("mousemove", @handler).one("mouseover", @start)
+
+    mouse = new Mouse()
+
+  exports = {
+    view: View,
+    class: Class,
+    node: Node,
+    layout: Layout,
+    simplelayout: SimpleLayout,
+    initViews: init
+  }

 $(window).on('load', () ->
-	lz.initViews()
-	canvas = new lz.view(null, {x: 100, y: 100, bgcolor: 'red', width: 100, height: 100, transform: 'rotate(45deg)', parent: $('#canvas')})
-	aview = new lz.view(null, {x: 50, y:50, width:20, height:20, bgcolor: 'green', 'border-radius': '4px', parent:canvas})
+  lz.initViews()
+  canvas = new lz.view(null, {x: 100, y: 100, bgcolor: 'red', width: 100, height: 100, transform: 'rotate(45deg)', parent: $('#canvas')})
+  aview = new lz.view(null, {x: 50, y:50, width:20, height:20, bgcolor: 'green', 'border-radius': '4px', parent:canvas})
 )


Remove kloud command line options

Remove kloud command line options

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/workers.coffee
+++ config/workers.coffee
@@ -44,7 +44,7 @@
       ports             :
         incoming        : "#{KONFIG.kloud.port}"
       supervisord       :
-        command         : "#{GOBIN}/kloud -networkusageendpoint http://localhost:#{KONFIG.vmwatcher.port} -planendpoint #{KONFIG.socialapi.proxyUrl}/payments/subscriptions -credentialendpoint #{KONFIG.socialapi.proxyUrl}/credential -hostedzone #{options.userSitesDomain} -region #{options.region} -environment #{options.environment} -port #{KONFIG.kloud.port}  -userprivatekey #{KONFIG.kloud.userPrivateKey} -userpublickey #{KONFIG.kloud.userPublicKey}  -publickey #{KONFIG.kontrol.publicKey} -privatekey #{KONFIG.kontrol.privateKey} -kontrolurl #{KONFIG.kontrol.url}  -registerurl #{KONFIG.kloud.registerUrl} -mongourl #{KONFIG.mongo} -prodmode=#{options.configName is "prod"} -awsaccesskeyid=#{credentials.awsKeys.vm_kloud.accessKeyId} -awssecretaccesskey=#{credentials.awsKeys.vm_kloud.secretAccessKey} -slusername=#{credentials.slKeys.vm_kloud.username} -slapikey=#{credentials.slKeys.vm_kloud.apiKey} -janitorsecretkey=#{KONFIG.socialapi.janitor.secretKey} -vmwatchersecretkey=#{KONFIG.vmwatcher.secretKey} -paymentwebhooksecretkey=#{KONFIG.paymentwebhook.secretKey} -kloudsecretkey=#{credentials.kloud.secretKey} -tunnelurl #{KONFIG.kloud.tunnelUrl}"
+        command         : "#{GOBIN}/kloud"
       nginx             :
         websocket       : yes
         locations       : [


Populate `CHOICES`

Populate `CHOICES`

--- app/enums.coffee
+++ app/enums.coffee
@@ -68,4 +68,12 @@
     UPLOADING: 2
     PROCESSING: 3

+
+# Populate `CHOICES`
+for enumName, enumValues of ENUMS
+  choices = []
+  for key, value of enumValues
+    choices.push {key: key, value: value}
+  ENUMS[enumName]['CHOICES'] = choices
+
 `export default ENUMS;`


Typo - Capstones to Capstone in module name


Add analytics

Add analytics

--- app/index.coffee
+++ app/index.coffee
@@ -30,5 +30,8 @@
 User = require 'zooniverse/models/user'
 User.fetch()

-window.app = {api, navigation, stack, topBar}
+GoogleAnalytics = require 'zooniverse/lib/google-analytics'
+analytics = new GoogleAnalytics account: 'UA-1224199-33'
+
+window.app = {api, navigation, stack, topBar, analytics}
 module.exports = window.app


New female and male names added!

New female and male names added!

Female = Anabel, Paulark
Male = Eugen, Toremon

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -1,6 +1,7 @@
 module.exports.thangNames = thangNames =
   'Ogre Munchkin F': [
     # Female
+    'Anabel'
     'Dosha'
     'Gurzunn'
     'Hoot'
@@ -8,6 +9,7 @@
     'Iyert'
     'Lacos'
     'Palt'
+    'Paulark'
     'Pripp'
     'Shmeal'
     'Upfish'
@@ -19,9 +21,11 @@
     'Brack'
     'Dobo'
     'Draff'
+    'Eugen'
     'Gert'
     'Godel'
     'Goreball'
+    'Toremon'
     'Gort'
     'Kog'
     'Kogpole'


Thangs can now respect custom Component colorization.

--- app/lib/world/thang.coffee
+++ app/lib/world/thang.coffee
@@ -179,6 +179,8 @@
       options.colorConfig.team = teamColor
     if @color and color = @grabColorConfig @color
       options.colorConfig.color = color
+    if @colors
+      options.colorConfig[colorType] = colorValue for colorType, colorValue of @colors
     options

   grabColorConfig: (color) ->


Properly handle stopping status

Properly handle stopping status

--- lib/engines/dbgp/dbgp-instance.coffee
+++ lib/engines/dbgp/dbgp-instance.coffee
@@ -149,6 +149,8 @@
             lineno = thing['lineno']
             breakpoint = new Breakpoint(filepath: filepath, line:lineno)
             @GlobalContext.notifyBreak(breakpoint)
+          when 'stopping'
+            @executeStop()
           else
             console.dir response
             console.error "Unhandled status: " + response.$.status


Add support for jQuery .slim suffix (#599)

--- config.cson
+++ config.cson
@@ -2090,7 +2090,7 @@
 	jQuery:
 		icon: "jquery"
 		priority: 2
-		match: /^jquery([-.](min|latest|\d\.\d+(\.\d+)?))*\.([jt]sx?|es6?|coffee|map)$/i
+		match: /^jquery([-.](latest|min|slim|\d\.\d+(\.\d+)?))*\.([jt]sx?|es6?|coffee|map)$/i
 		colour: "dark-blue"

 	"jQuery UI":


update instanssi 2012 config

update instanssi 2012 config

--- config.cson
+++ config.cson
@@ -5,6 +5,7 @@
   hosts:
     enttec1:
       # path: "/dev/serial/by-id/usb-FTDI_FT245R_USB_FIFO_ENP9D7H7-if00-port0"
+      # path: "/dev/serial/by-id/usb-FTDI_USB__-__Serial-if00-port0"
       path: "/dev/serial/by-id/usb-FTDI_USB__-__Serial-if00-port0"
       # path: "/dev/serial/by-id/usb-ENTTEC_DMX_USB_PRO_ENR35XBU-if00-port0"
       type: "enttec"
@@ -16,197 +17,197 @@
       #   type: "rgb"
       #   address: 8

+      0:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 1
+      1:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 6
+      2:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 11
+      3:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 16
+      4:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 21
+      5:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 26
+      6:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 31
+      7:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 36
+      8:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 41
+      9:
+        name: "sali rgb"
+        host: "enttec1"
+        type: "rgb"
+        address: 46
       10:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 1
+        address: 51
       11:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 6
+        address: 56
       12:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 11
+        address: 61
       13:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 16
+        address: 66
       14:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 21
+        address: 71
       15:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 26
+        address: 76
       16:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 31
+        address: 81
       17:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 36
+        address: 86
       18:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 41
+        address: 91
       19:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 46
+        address: 96
       20:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 51
+        address: 101
       21:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 56
+        address: 106
       22:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 61
+        address: 111
       23:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 66
+        address: 116
       24:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 71
+        address: 121
       25:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 76
+        address: 126
       26:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 81
+        address: 131
       27:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 86
+        address: 136
       28:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 91
+        address: 141
       29:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 96
+        address: 146
       30:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 101
+        address: 151
       31:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 106
+        address: 156
       32:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 111
+        address: 161
       33:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 116
+        address: 166
       34:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 121
+        address: 171
       35:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 126
+        address: 176
       36:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 131
+        address: 181
       37:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"
-        address: 136
+        address: 186
       38:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 141
-      39:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 146
-      40:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 151
-      41:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 156
-      42:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 161
-      43:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 166
-      44:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 171
-      45:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 176
-      46:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 181
-      47:
-        name: "sali rgb"
-        host: "enttec1"
-        type: "rgb"
-        address: 186
-      48:
         name: "sali rgb"
         host: "enttec1"
         type: "rgb"


Add more atom config

Add more atom config

--- config.cson
+++ config.cson
@@ -8,11 +8,15 @@
       "one-light-syntax"
     ]
   editor:
-    fontSize: 25
+    fontSize: 20
     scrollPastEnd: true
     softWrap: true
   "exception-reporting":
     userId: "c0f59876-8ce3-42c6-824c-22e0f8fe3238"
+  "markdown-preview":
+    useGitHubStyle: true
+  tablr:
+    copyPaste: {}
   welcome:
     showOnStartup: false
 ".css.source.stylus":


Consolidate optimization steps

Consolidate optimization steps

--- cli/Gruntfile.coffee
+++ cli/Gruntfile.coffee
@@ -125,7 +125,8 @@
   ]

   # Script optimization tasks
-  optimizeScriptTasks = [
+  optimizeTasks = [
+    'optimizeStyle'
     'uglify'
   ]

@@ -319,8 +320,8 @@

     cssshrink:
       default:
-        files:
-          'public/style.css': 'public/style.css'
+        src: 'public/style.css'
+        dest: 'public/style.css'

     ## Template-specific

@@ -349,9 +350,8 @@

     uglify:
       default:
-        files:
-          src: 'public/script.js'
-          dest: 'public/script.js'
+        src: 'public/script.js'
+        dest: 'public/script.js'

   ####
@@ -380,9 +380,8 @@
     ]

   grunt.registerTask 'optimize', ->
-    # TODO: turn on after our refactoring
     if NODE_ENV isnt 'dev'
-      grunt.task.run(optimizeScriptTasks)
+      grunt.task.run(optimizeTasks)

   grunt.registerTask 'updateComponent', ->
     manifest = grunt.file.readJSON('component.json')
@@ -430,14 +429,10 @@
         if grunt.file.exists(styleVariableFile)
           grunt.task.run [
             'stylus:withVariables'
-            'runAutoprefixer'
-            'runCssShrink'
           ]
         else
           grunt.task.run [
             'stylus:noVariables'
-            'runAutoprefixer'
-            'runCssShrink'
           ]

       when 'jade'
@@ -447,14 +442,12 @@
       when 'deps'
         runOnDeps('buildDep')

-  grunt.registerTask 'runAutoprefixer', ->
+  grunt.registerTask 'optimizeStyle', ->
     if grunt.file.exists('public/style.css')
-      grunt.task.run 'autoprefixer'
-
-  # Optimize only when we're in production and if there are stylesheets
-  grunt.registerTask 'runCssShrink', ->
-    if NODE_ENV isnt 'dev' and grunt.task.exists('public/style.css')
-      grunt.task.run('cssshrink')
+      grunt.task.run [
+        'autoprefixer'
+        'cssshrink'
+      ]

   # We need to manually handle components as the `cwd` path is dynamic
   # depending on the assets


Update config.coffee

Update config.coffee

--- client/config.coffee
+++ client/config.coffee
@@ -1,8 +1,8 @@
 define ['./fields'], (fields) ->
   showDebugMessages = true

-  host = 'http://eval.versionone.net';
-  service = 'http://eval.versionone.net/platformtest/rest-1.v1/Data/';
+  host = 'https://www14.v1host.com/v1sdktesting';
+  service = 'https://www14.v1host.com/v1sdktesting/rest-1.v1/Data/';
   versionOneAuth = 'admin:admin';

   #host = 'http://localhost';


disable upload button while still processing upload

--- client/images.coffee
+++ client/images.coffee
@@ -233,6 +233,16 @@
       body_data: null
       save_label: "Upload"
       render_complete: (parent_template) ->
+        body_template = window.image_upload_template
+
+        Tracker.autorun ->
+          upload_ready = body_template.upload_ready.get()
+          jq_button = parent_template.$('#modal-dialog-save')
+          if upload_ready
+            jq_button.removeClass('disabled')
+          else
+            jq_button.addClass('disabled')
+
         parent_template.$("#modal-dialog-save").on("click", (event) ->
           template = window.image_upload_template
           if !template?


Style.

Style.

--- client/import.coffee
+++ client/import.coffee
@@ -157,7 +157,7 @@

       # TODO: Remove the following workaround for a bug
       # Deps.flush does not seem to really do it, so we have to use Meteor.setTimeout to workaround
-      # See: https://github.com/meteor/meteor/issues/1619
+      # See https://github.com/meteor/meteor/issues/1619
       Meteor.setTimeout ->
         # TODO: We should read in chunks, not whole file
         reader.readAsArrayBuffer file


Style.

Style.

--- client/import.coffee
+++ client/import.coffee
@@ -8,16 +8,15 @@
   # canceled: true when user cancels import
   # publicationId: publication ID for the imported file
   # sha256: SHA256 hash for the file
-  # state: current document state, one of the following strings:
-  #        - new            -> file in queue for preprocessing
-  #        - preprocessing  -> file currently being preprocessed
-  #        - preprocessed   -> file in queue for importing
-  #        - importing      -> file currently being imported
-  #        - finished       -> file processing finished successfully, but it
-  #                            was not imported (import canceled or aready exists)
-  #        - imported       -> file processing finished successfully and
-  #                            it was imported
-  #        - errored        -> file processing errored
+  # state: current document state, one of the following constant strings
+  #   new: file in queue for preprocessing
+  #   preprocessing: file currently being preprocessed
+  #   preprocessed: file in queue for importing
+  #   importing: file currently being imported
+  #   finished: file processing finished successfully, but it
+  #             was not imported (import canceled or already exists)
+  #   imported: file processing finished successfully and at was imported
+  #   errored: file processing errored

   @Meta
     name: 'ImportingFile'


Fix utils import

Fix utils import

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -16,7 +16,7 @@
 TaskHistory = require '../models/TaskHistory'
 TaskPoller = require '../views/TaskPoller'

-Utils = require '../utils'
+Utils = require('../utils').default

 vex = require('vex.dialog').default
 juration = require 'juration'


That's not how you close notifications in webkit

--- Web/js/classes/notification-center.coffee
+++ Web/js/classes/notification-center.coffee
@@ -47,12 +47,15 @@
 			noti = new Notification title,
 				icon: 'rohpod.png'
 				body: body
+			denoti = ->
+				noti?.close()
+				noti = null
 		else
 			noti = webkitNotifications.createNotification 'rohpod.png', title, body
+			denoti = ->
+				noti?.cancel()
+				noti = null

-		denoti = () ->
-			noti?.close()
-			noti = null
 		setTimeout denoti, 3000
 		noti.addEventListener 'click', denoti
 		noti.show?()


Remove unused code

Remove unused code

--- js/outer.coffee
+++ js/outer.coffee
@@ -111,12 +111,6 @@
   pricing = new Page("pricing", "Plans and Pricing")
   docs = new Docs("docs", "Documentation")

-  # Define Libs
-  highlight = =>
-    # Not happy with how this looks (esp since it doesnt support yaml). Rethinking.
-    #$("pre code").each (i, e) =>
-    #  hljs.highlightBlock e
-
   placeholder = =>
     $("input, textarea").placeholder()

@@ -133,12 +127,6 @@

   # Per-Page Libs
   home.lib = =>
-    trigger = =>
-      $("#testimonials").waypoint ((event, direction) ->
-        $("#testimonials").addClass("scrolled")
-      ),
-        offset: "80%"
-    trigger()
     placeholder()
     follow()

@@ -148,7 +136,6 @@
       $("ul.topics").stickyMojo
         footerID: "#footer"
         contentID: ".article article"
-    highlight()
     sidebar()

   about.lib = =>


tool improvements

tool improvements

--- lib/lt3.coffee
+++ lib/lt3.coffee
@@ -157,7 +157,7 @@
         rl.prompt();
     when 'get:allsites'
       getDB (db) ->
-       db.get('sites').find {}, (err, sites) ->
+       db.get('sites').find {},{limit: 100}, (err, sites) ->
         for site in sites
           log "_id: #{site.data._id}, slug:#{site.data.slug}"
         rl.prompt();
@@ -839,7 +839,7 @@

     ## get old site
     getDB (db) ->
-      db.get('sites').findOne {'_id': old_site_id},{limit: 1000} (err, old_site) ->
+      db.get('sites').findOne {'_id': old_site_id}, (err, old_site) ->
         throw err if err

         #remove and reset attributes
@@ -860,7 +860,7 @@
           new_site_id=new_site.get('_id').val()

           #get objects of old site
-          db.get('objects').find {site_id:old_site_id} , (err, objects) =>
+          db.get('objects').find {site_id:old_site_id},{limit: 1000}, (err, objects) =>
             throw err if err
             async.forEach objects , (obj, next) =>
               #wipe data on old objects


don't put "show and hide courses menu" on user files

test plan:
 - enable new styles in the root account
 - on a course files page, when the window is shrunk to where
   the course nav doesn't fit, a hamburger menu should appear
   to toggle it
 - on a user files page, the hamburger menu should not be there
   (because there is no course nav to toggle; currently it is
    toggling an empty space)

fixes CNVS-21105

Change-Id: I229e308ead615f6790002aee3d17de436655f727
Reviewed-on: https://gerrit.instructure.com/60803
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins
QA-Review: Charles Kimball <46fea98154686b8132aa2745574cc8021812cb41@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/react_files/components/FilesApp.coffee
+++ app/coffeescripts/react_files/components/FilesApp.coffee
@@ -114,7 +114,7 @@
         header {},
           h1 {className: 'screenreader-only'},
             I18n.t('files_heading', "Files")
-        if ENV.use_new_styles
+        if ENV.use_new_styles and contextType == 'courses'
           div {className: 'ic-app-nav-toggle-and-crumbs ic-app-nav-toggle-and-crumbs--files'},
             button {
               className:'Button Button--link Button--small ic-app-course-nav-toggle',


in ValidateAndRedirectLoggedInUser, only start LoadBar if user validated

--- app/services/validate-and-redirect-logged-in-user.coffee
+++ app/services/validate-and-redirect-logged-in-user.coffee
@@ -4,10 +4,10 @@

 global.cobudgetApp.factory 'ValidateAndRedirectLoggedInUser', ($auth, Error, LoadBar, $location, Records) ->
   ->
-    LoadBar.start()
-    Error.clear()
-    $auth.validateUser()
-    global.cobudgetApp.membershipsLoaded.then (data) ->
-      groupId = data.groups[0].id
-      $location.path("/groups/#{groupId}")
-      LoadBar.stop()
+    $auth.validateUser().then ->
+      LoadBar.start()
+      Error.clear()
+      global.cobudgetApp.membershipsLoaded.then (data) ->
+        groupId = data.groups[0].id
+        $location.path("/groups/#{groupId}")
+        LoadBar.stop()


add some debug text to drawing area

add some debug text to drawing area

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -14,6 +14,7 @@
   resizing: false

   getInitialState: ->
+    console.log 'props.tool: ', @props.tool
     imageWidth: 0
     imageHeight: 0

@@ -91,6 +92,9 @@
       y: ey
       timestamp: (new Date).toJSON()
     marks.push newMark
+
+    console.log 'MARKS: ', marks[marks.length-1]
+
     @setState
       marks: marks
       lastMarkKey: @state.lastMarkKey + 1
@@ -153,6 +157,25 @@
         x: mark.x - ex
         y: mark.y - ey
       # , => @forceUpdate()
+
+
+          # SCRATCH CODE TAKEN FROM WITHIN SVG TAG
+          #       { @state.marks.map ((mark, i) ->
+          #     <ToolComponent
+          #       key={i}
+          #       mark={mark}
+          #       subject={@state.subject}
+          #       workflow={@props.workflow}
+          #       getEventOffset={@getEventOffset}
+          #       isSelected={mark is @state.selectedMark}
+          #       handleMarkClick={@handleMarkClick.bind null, mark}
+          #       onClickDelete={@onClickDelete}
+          #       clickOffset={@state.clickOffset}
+          #       imageWidth={@state.imageWidth}
+          #       imageHeight={@state.imageHeight}
+          #     />
+          #   ), @
+          # }

   render: ->
     # return null if @state.subjects is null or @state.subjects.length is 0
@@ -160,6 +183,8 @@
     # console.log 'SUBJECT: ', @state.subject
     viewBox = [0, 0, @state.imageWidth, @state.imageHeight]
     ToolComponent = @state.tool
+
+    console.log 'ToolCOmponent: ', @state.tool

     actionButton =
       if @state.loading
@@ -192,22 +217,28 @@
               height = {@state.imageHeight} />
           </Draggable>

-          { @state.marks.map ((mark, i) ->
-              <ToolComponent
-                key={i}
-                mark={mark}
-                subject={@state.subject}
-                workflow={@props.workflow}
-                getEventOffset={@getEventOffset}
-                isSelected={mark is @state.selectedMark}
-                handleMarkClick={@handleMarkClick.bind null, mark}
-                onClickDelete={@onClickDelete}
-                clickOffset={@state.clickOffset}
-                imageWidth={@state.imageWidth}
-                imageHeight={@state.imageHeight}
-              />
-            ), @
+          { for annotation in @props.classification.annotations
+              console.log 'annotation: ', annotation
+              annotation._key ?= Math.random()
+              isPriorAnnotation = annotation isnt @props.annotation
+              taskDescription = @props.workflow.tasks[annotation.task]
+
+              <g key={annotation._key} className="marks-for-annotation" data-disabled={isPriorAnnotation or null} >
+                { if @state.selectedMark?
+                    <text
+                      x={@state.selectedMark.x}
+                      y={@state.selectedMark.y}
+                      fontFamily="Verdana"
+                      fontSize="55"
+                    >
+                      BLAH
+                    </text>
+                }
+              </g>
+
+
           }
+
         </svg>

     <div className="subject-viewer">


Returning objects in state is now deprecated. Return string.

--- app/assets/javascripts/stores/validation_store.coffee
+++ app/assets/javascripts/stores/validation_store.coffee
@@ -37,7 +37,7 @@
     else true
   firstMessage: ->
     if _errorQueue.length > 0
-      _validations[_errorQueue[0]]
+      _validations[_errorQueue[0]].message
     else
       null
 , (payload) ->


a11y: Made media comments accessible to screen reader and keyboard users

Fixes CNVS-17889

Test plan:
- Using the keyboard only, navigate to and play an audio and a video comment.
- Do the same with a screen reader

Change-Id: I1ac3584581b81dadb73f4607a8f683e331c87a39
Reviewed-on: https://gerrit.instructure.com/47672
Tested-by: Jenkins
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Nathan Rogowski <2e8aa918660411855c6d44d5bb2da677aa033255@instructure.com>
Product-Review: Cody Cutrer <c4fe2b1d90ef8f2548c8aeabfa633434316f0305@instructure.com>

--- app/coffeescripts/jquery/mediaCommentThumbnail.coffee
+++ app/coffeescripts/jquery/mediaCommentThumbnail.coffee
@@ -46,6 +46,7 @@
         $link.empty()

       $a
+        .prop('href', '#')
         .addClass('instructure_inline_media_comment')
         .append($thumbnail)
         .css


WebVR: Avoid calling getVRDisplays() at the beginning due to FF bug 1423912.

--- engine/webvr.coffee
+++ engine/webvr.coffee
@@ -81,11 +81,11 @@

 displays = []
-navigator.getVRDisplays?().then (_displays) ->
-    displays = _displays
 vrdisplaypresentchange = null

 exports.has_HMD = ->
+    # NOTE: Firefox gets stuck all the time after calling this,
+    # call it only if you're sure you have an HMD or it's not Firefox
     new Promise (resolve, reject) ->
         if not navigator.getVRDisplays
             if navigator.getVRDevices


fix showing contents

fix showing contents

--- modules/gui/lib/widgets/Popup.coffee
+++ modules/gui/lib/widgets/Popup.coffee
@@ -108,17 +108,23 @@
                         @close()

                 setTimeout ()=>
-
-                    if @data.fade
-                        @find('.hidden').fadeIn 'fast'
-
-                    else
-                        @find('.hidden').show()
-
+                    @_show()
                 , 100 ## das merkt man sogut wie nicht und die gui wirkt stabiler dadurch... weniger aufbau-gewusel

                 done()
+
+        ##
+        ##
+        ##
+        _show: ()->
+            if @data.fade
+                @__root.fadeIn if typeof @data.fade is 'string' then @data.fade else 'slow'
+
+            else
+                @__root.show()

+
+
         ##
         ##
         ##


events: Update calendar for local event data

--- assets/lib/calendar.coffee
+++ assets/lib/calendar.coffee
@@ -8,11 +8,9 @@
   today = Date.now()
   formatted = []

-  for year of events
-    for item of events[year]
-      event = events[year][item]
-
-      if event['start']
+  Object.values(events)
+    .forEach (event) ->
+      if event && event['start']

         event['title'] = event['name']

@@ -142,11 +140,9 @@

 $ ->
-  # TODO: Make the URL configurable via liquid
-  # TODO: Dynamically load by year (so not all events are loaded at once)
-  yearData = '//rhevents-duckosas.rhcloud.com/all.json'
+  eventData = "{{ site.baseurl }}/assets/lib/events.json"

   now = Date.now()

   # Grab and format events
-  $.get yearData, processCalendar
+  $.get eventData, processCalendar


Updated translation

Updated translation

--- app/locale/bg.coffee
+++ app/locale/bg.coffee
@@ -204,7 +204,7 @@
     medium: "Средно"
     hard: "Трудно"
     player: "Играч"
-#    player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
+    player_level: "Ниво" # Like player level 5, not like level: Dungeons of Kithgard
 #    warrior: "Warrior"
 #    ranger: "Ranger"
 #    wizard: "Wizard"
@@ -245,7 +245,7 @@
 #    control_bar_multiplayer: "Multiplayer"
     control_bar_join_game: "Присъединяване"
     reload: "Презареди"
-#    reload_title: "Reload All Code?"
+    reload_title: "Презареди целият код?"
 #    reload_really: "Are you sure you want to reload this level back to the beginning?"
     reload_confirm: "Презареди всички"
     victory: "Победа"
@@ -255,15 +255,15 @@
 #    victory_sign_up_poke: "Want to save your code? Create a free account!"
 #    victory_rate_the_level: "Rate the level: " # Only in old-style levels.
 #    victory_return_to_ladder: "Return to Ladder"
-#    victory_play_continue: "Continue"
+    victory_play_continue: "Продължи"
 #    victory_saving_progress: "Saving Progress"
 #    victory_go_home: "Go Home" # Only in old-style levels.
 #    victory_review: "Tell us more!" # Only in old-style levels.
-#    victory_hour_of_code_done: "Are You Done?"
-#    victory_hour_of_code_done_yes: "Yes, I'm finished with my Hour of Code™!"
-#    victory_experience_gained: "XP Gained"
-#    victory_gems_gained: "Gems Gained"
-#    guide_title: "Guide"
+    victory_hour_of_code_done: "Готов ли си?"
+    victory_hour_of_code_done_yes: "Да аз съм готов с моят Hour of Code™!"
+    victory_experience_gained: "Спечелен опит"
+    victory_gems_gained: "Спечелени скъпоценни камъни"
+    guide_title: "Упътване"
 #    tome_minion_spells: "Your Minions' Spells" # Only in old-style levels.
 #    tome_read_only_spells: "Read-Only Spells" # Only in old-style levels.
 #    tome_other_units: "Other Units" # Only in old-style levels.
@@ -276,29 +276,29 @@
 #    tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methdos).
 #    tome_select_a_thang: "Select Someone for "
 #    tome_available_spells: "Available Spells"
-#    tome_your_skills: "Your Skills"
-#    tome_help: "Help"
+    tome_your_skills: "Твоите Умения"
+    tome_help: "Помощ"
 #    tome_current_method: "Current Method"
-#    hud_continue_short: "Continue"
-#    code_saved: "Code Saved"
-#    skip_tutorial: "Skip (esc)"
-#    keyboard_shortcuts: "Key Shortcuts"
-#    loading_ready: "Ready!"
-#    loading_start: "Start Level"
-#    problem_alert_title: "Fix Your Code"
-#    problem_alert_help: "Help"
-#    time_current: "Now:"
+    hud_continue_short: "Продължи"
+    code_saved: "Кодът е записан"
+    skip_tutorial: "Пропусни (esc)"
+    keyboard_shortcuts: "Клавишни комбинации"
+    loading_ready: "Готово!"
+    loading_start: "Стартирай Ниво"
+    problem_alert_title: "Оправи си кода."
+    problem_alert_help: "Помощ"
+    time_current: "Now:"
 #    time_total: "Max:"
 #    time_goto: "Go to:"
-#    infinite_loop_try_again: "Try Again"
-#    infinite_loop_reset_level: "Reset Level"
-#    infinite_loop_comment_out: "Comment Out My Code"
+    infinite_loop_try_again: "Пробвай отново"
+    infinite_loop_reset_level: "Ресетване на Ниво"
+    infinite_loop_comment_out: "Коментирай моят Код"
 #    tip_toggle_play: "Toggle play/paused with Ctrl+P."
 #    tip_scrub_shortcut: "Ctrl+[ and Ctrl+] rewind and fast-forward."
 #    tip_guide_exists: "Click the guide, inside game menu (at the top of the page), for useful info."
-#    tip_open_source: "CodeCombat is 100% open source!"
+    tip_open_source: "CodeCombat e 100% проект с отворен код!"
 #    tip_beta_launch: "CodeCombat launched its beta in October, 2013."
-#    tip_think_solution: "Think of the solution, not the problem."
+    tip_think_solution: "Помисли върху решението,не проблема."
 #    tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
 #    tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
 #    tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"


Update en.coffee

Update en.coffee

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -166,8 +166,10 @@
     version_history: "Version History"
     version_history_for: "Version History for: "
     select_changes: "Select two changes below to see the difference."
-    undo: "Undo (Ctrl+Z)"
-    redo: "Redo (Ctrl+Shift+Z)"
+    undo_prefix: "Undo"
+    undo_shortcut: "(Ctrl+Z)"
+    redo_prefix: "Redo"
+    redo_shortcut: "(Ctrl+Shift+Z)"
     play_preview: "Play preview of current level"
     result: "Result"
     results: "Results"