fixed coffeelint errors on workers/social/lib/social/models/computeproviders/computeprovider.coffee

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -1,9 +1,8 @@
-
-{Base, secure, signature, daisy} = require 'bongo'
+{ Base, secure, signature, daisy } = require 'bongo'
 KodingError = require '../../error'

-{argv} = require 'optimist'
-KONFIG = require('koding-config-manager').load("main.#{argv.c}")
+{ argv } = require 'optimist'
+KONFIG   = require('koding-config-manager').load("main.#{argv.c}")

 module.exports = class ComputeProvider extends Base

@@ -14,7 +13,7 @@

   @trait __dirname, '../../traits/protected'

-  {permit} = require '../group/permissionset'
+  { permit } = require '../group/permissionset'

   JMachine   = require './machine'
   JWorkspace = require '../workspace'
@@ -24,12 +23,12 @@
   @set
     permissions           :
       'sudoer'            : []
-      'ping machines'     : ['member','moderator']
-      'list machines'     : ['member','moderator']
-      'create machines'   : ['member','moderator']
-      'delete machines'   : ['member','moderator']
-      'update machines'   : ['member','moderator']
-      'list own machines' : ['member','moderator']
+      'ping machines'     : ['member', 'moderator']
+      'list machines'     : ['member', 'moderator']
+      'create machines'   : ['member', 'moderator']
+      'delete machines'   : ['member', 'moderator']
+      'update machines'   : ['member', 'moderator']
+      'list own machines' : ['member', 'moderator']
     sharedMethods         :
       static              :
         ping              :
@@ -56,23 +55,21 @@

   @providers      = PROVIDERS

-  @fetchProviders = secure (client, callback)->
+  @fetchProviders = secure (client, callback) ->
     callback null, Object.keys PROVIDERS

-  @ping = (client, options, callback)->
-
-    {provider} = options
+  @ping = (client, options, callback) ->
+
+    { provider } = options
     provider.ping client, options, callback

-  @ping$ = permit 'ping machines', success: revive
-
-    shouldReviveClient   : yes
-    shouldPassCredential : yes
-
-  , @ping
-
+  @ping$ = permit 'ping machines',
+    success: revive {
+      shouldReviveClient   : yes
+      shouldPassCredential : yes
+    }, @ping

   @create = revive
@@ -80,12 +77,12 @@
     shouldReviveClient       : yes
     shouldReviveProvisioners : yes

-  , (client, options, callback)->
+  , (client, options, callback) ->

     { provider, stack, label, provisioners, users, generatedFrom } = options
     { r: { group, user, account } } = client

-    provider.create client, options, (err, machineData)->
+    provider.create client, options, (err, machineData) ->

       return callback err  if err

@@ -97,7 +94,7 @@
         provider : provider.slug
         label, meta, group, user, generatedFrom
         users, credential, provisioners
-      }, (err, machine)->
+      }, (err, machine) ->

         # TODO if any error occurs here which means user paid for
         # not created vm ~ GG
@@ -105,30 +102,30 @@

         provider.postCreate client, {
           postCreateOptions, machine, meta, stack: stack._id
-        }, (err)->
+        }, (err) ->

           return callback err  if err

-          stack.appendTo machines: machine.getId(), (err)->
+          stack.appendTo { machines: machine.getId() }, (err) ->
             callback err, machine

-  @create$ = permit 'create machines', success: revive
+  @create$ = permit 'create machines', { success: revive

     shouldReviveClient   : yes
     shouldPassCredential : yes
     shouldReviveProvider : no
     shouldLockProcess    : yes

-  , (client, options, callback)->
+  , (client, options, callback) ->

     { r: { account } } = client
     { stack } = options

     JComputeStack = require '../stack'
-    JComputeStack.getStack account, stack, (err, revivedStack)=>
+    JComputeStack.getStack account, stack, (err, revivedStack) =>
       return callback err  if err?
-      return callback new KodingError "No such stack"  unless revivedStack
+      return callback new KodingError 'No such stack'  unless revivedStack

       options.stack = revivedStack

@@ -140,7 +137,7 @@
       delete options.generatedFrom

       @create client, options, callback
-
+  }

   @fetchAvailable = secure revive
@@ -148,13 +145,13 @@
     shouldReviveClient   : no
     shouldPassCredential : yes

-  , (client, options, callback)->
-
-    {provider} = options
+  , (client, options, callback) ->
+
+    { provider } = options
     provider.fetchAvailable client, options, callback

-  @fetchUsage$ = secure (client, options, callback)->
+  @fetchUsage$ = secure (client, options, callback) ->
     ComputeProvider.fetchUsage client, options, callback

   @fetchUsage = revive
@@ -162,14 +159,14 @@
     shouldReviveClient   : yes
     shouldPassCredential : yes

-  , (client, options, callback)->
-
-    {slug} = options.provider
-    fetchUsage client, provider: slug, callback
+  , (client, options, callback) ->
+
+    { slug } = options.provider
+    fetchUsage client, { provider: slug }, callback

   @fetchPlans = permit 'create machines',
-    success: (client, callback)->
+    success: (client, callback) ->
       callback null, PLANS

@@ -178,9 +175,9 @@
     shouldReviveClient   : yes
     shouldPassCredential : yes

-  , (client, options, callback)->
-
-    {provider} = options
+  , (client, options, callback) ->
+
+    { provider } = options
     provider.update client, options, callback

@@ -189,25 +186,25 @@
     shouldReviveClient   : yes
     shouldPassCredential : yes

-  , (client, options, callback)->
-
-    {provider} = options
+  , (client, options, callback) ->
+
+    { provider } = options
     provider.remove client, options, callback

-  @createStackFromTemplate = permit 'create machines', success: revive
-
-    shouldReviveClient   : yes
-    shouldReviveProvider : no
-
-  , (client, options, callback) ->
-
-    { account, user, group } = client.r
-    { template } = options
-
-    ComputeProvider.generateStackFromTemplate {
-      account, user, group, template, client
-    }, {}, callback
+  @createStackFromTemplate = permit 'create machines',
+    success: revive {
+      shouldReviveClient   : yes
+      shouldReviveProvider : no
+
+    }, (client, options, callback) ->
+
+      { account, user, group } = client.r
+      { template } = options
+
+      ComputeProvider.generateStackFromTemplate {
+        account, user, group, template, client
+      }, {}, callback

   @generateStackFromTemplate = (data, options, callback) ->
@@ -291,7 +288,7 @@

       queue.push ->

-        callback null, {stack, results}
+        callback null, { stack, results }

       daisy queue

@@ -315,7 +312,7 @@

       return callback err  if err

-      {template, account} = res
+      { template, account } = res
       checkTemplateUsage template, account, (err) ->
         return callback err  if err

@@ -329,7 +326,7 @@
   do ->

     JGroup = require '../group'
-    JGroup.on 'MemberAdded', ({group, member}) ->
+    JGroup.on 'MemberAdded', ({ group, member }) ->

       # No need to try creating group stacks for guests or koding group members
       return  if group.slug in ['guests', 'koding']
@@ -337,17 +334,17 @@
       client =
         connection :
           delegate : member
-        context    : group : group.slug
+        context    : { group : group.slug }

       ComputeProvider.createGroupStack client,
         addGroupAdminToMachines: no # Marked this as no until
                                     # we find a better solution ~ GG
       , (err, res = {}) ->

-        {stack, results} = res
+        { stack, results } = res

         if err?
-          {nickname} = member.profile
+          { nickname } = member.profile
           console.log "Create group #{group.slug} stack failed for #{nickname}:", err, results

@@ -362,16 +359,18 @@
       console.log "Removing user #{oldUsername} vms..."

       JMachine.update
-        provider      : $in: ['koding', 'managed']
+        provider      : { $in: ['koding', 'managed'] }
         credential    : oldUsername
       ,
         $set          :
           userDeleted : yes
       ,
         multi         : yes
-      , (err)->
+      , (err) ->
         if err?
           console.error \
             "Failed to mark them as deleted for #{oldUsername}:", err

       return
+
+


Simplify detectDirty logic a little.

Simplify detectDirty logic a little.

--- lib/side-view.coffee
+++ lib/side-view.coffee
@@ -37,9 +37,8 @@
       @side.originalText

   detectDirty: ->
-    wasDirty = @side.isDirty
     currentText = @editor().getTextInBufferRange @side.marker.getBufferRange()
     @side.isDirty = currentText isnt @side.originalText

-    @addClass 'dirty' if @side.isDirty and not wasDirty
-    @removeClass 'dirty' if not @side.isDirty and wasDirty
+    @removeClass 'dirty'
+    @addClass 'dirty' if @side.isDirty


Use path.join instead of concatenating with path.sep

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -616,7 +616,7 @@
                 fullExtension = nextExtension.concat(fullExtension)
                 nextExtension = path.extname(path.basename(originalNewPath, fullExtension))

-              filePath = path.dirname(originalNewPath) + path.sep + path.basename(originalNewPath, fullExtension)
+              filePath = path.join(path.dirname(originalNewPath), path.basename(originalNewPath, fullExtension))

               newPath = "#{filePath}#{fileCounter.toString()}#{fullExtension}"
             fileCounter += 1


Add cavalry to webserver

Add cavalry to webserver

--- lib/webserver.coffee
+++ lib/webserver.coffee
@@ -2,6 +2,7 @@
 url = require 'url'
 model = require('../lib/model')
 gitter = require '../lib/gitter'
+cavalry = require '../lib/cavalry'

 server = http.createServer()


typofix

typofix

--- lib/websocket.coffee
+++ lib/websocket.coffee
@@ -2,7 +2,7 @@
 model = require('../lib/model')
 surveyor = require '../lib/surveyor'

-SECRET = process.env.Secret or "testingpass"
+SECRET = process.env.SECRET or "testingpass"

 wss = new WebSocketServer({port: 4000})
 wss.on 'connection', (ws) =>


Replace upperCase with shift-lowerCase

Replace upperCase with shift-lowerCase

--- keymaps/darwin.cson
+++ keymaps/darwin.cson
@@ -11,9 +11,9 @@
   'ctrl-b': 'core:move-left'
   'ctrl-f': 'core:move-right'
   'ctrl-P': 'core:select-up'
-  'ctrl-N': 'core:select-down'
-  'ctrl-F': 'core:select-right'
-  'ctrl-B': 'core:select-left'
+  'ctrl-shift-n': 'core:select-down'
+  'ctrl-shift-f': 'core:select-right'
+  'ctrl-shift-b': 'core:select-left'
   'ctrl-h': 'core:backspace'
   'ctrl-d': 'core:delete'

@@ -34,19 +34,19 @@

   # Sublime Parity
   'cmd-,': 'application:show-settings'
-  'cmd-N': 'application:new-window'
-  'cmd-W': 'window:close'
+  'cmd-shift-n': 'application:new-window'
+  'cmd-shift-w': 'window:close'
   'cmd-o': 'application:open'
-  'cmd-O': 'application:add-project-folder'
-  'cmd-T': 'pane:reopen-closed-item'
+  'cmd-shift-o': 'application:add-project-folder'
+  'cmd-shift-t': 'pane:reopen-closed-item'
   'cmd-n': 'application:new-file'
   'cmd-s': 'core:save'
-  'cmd-S': 'core:save-as'
+  'cmd-shift-s': 'core:save-as'
   'cmd-alt-s': 'window:save-all'
   'cmd-w': 'core:close'
   'cmd-ctrl-f': 'window:toggle-full-screen'
   'cmd-z': 'core:undo'
-  'cmd-Z': 'core:redo'
+  'cmd-shift-z': 'core:redo'
   'cmd-y': 'core:redo'
   'cmd-x': 'core:cut'
   'cmd-c': 'core:copy'
@@ -116,8 +116,8 @@
   'cmd-backspace': 'editor:delete-to-beginning-of-line'
   'cmd-shift-backspace': 'editor:delete-to-beginning-of-line'
   'cmd-delete': 'editor:delete-to-end-of-line'
-  'ctrl-A': 'editor:select-to-first-character-of-line'
-  'ctrl-E': 'editor:select-to-end-of-line'
+  'ctrl-shift-a': 'editor:select-to-first-character-of-line'
+  'ctrl-shift-e': 'editor:select-to-end-of-line'
   'cmd-left': 'editor:move-to-first-character-of-line'
   'cmd-right': 'editor:move-to-end-of-screen-line'
   'cmd-shift-left': 'editor:select-to-first-character-of-line'
@@ -129,19 +129,19 @@
   'ctrl-k': 'editor:cut-to-end-of-line'

   # Atom Specific
-  'ctrl-W': 'editor:select-word'
+  'ctrl-shift-w': 'editor:select-word'
   'cmd-ctrl-left': 'editor:move-selection-left'
   'cmd-ctrl-right': 'editor:move-selection-right'

   # Emacs
   'alt-f': 'editor:move-to-end-of-word'
   'alt-ctrl-f': 'editor:move-to-next-subword-boundary'
-  'alt-F': 'editor:select-to-end-of-word'
-  'alt-ctrl-F': 'editor:select-to-next-subword-boundary'
+  'alt-shift-f': 'editor:select-to-end-of-word'
+  'alt-ctrl-shift-f': 'editor:select-to-next-subword-boundary'
   'alt-b': 'editor:move-to-beginning-of-word'
   'alt-ctrl-b': 'editor:move-to-previous-subword-boundary'
-  'alt-B': 'editor:select-to-beginning-of-word'
-  'alt-ctrl-B': 'editor:select-to-previous-subword-boundary'
+  'alt-shift-b': 'editor:select-to-beginning-of-word'
+  'alt-ctrl-shift-b': 'editor:select-to-previous-subword-boundary'
   'alt-h': 'editor:delete-to-beginning-of-word'
   'alt-ctrl-h': 'editor:delete-to-beginning-of-subword'
   'alt-d': 'editor:delete-to-end-of-word'
@@ -178,8 +178,8 @@
   'ctrl-cmd-down': 'editor:move-line-down'
   'cmd-/': 'editor:toggle-line-comments'
   'cmd-j': 'editor:join-lines'
-  'cmd-D': 'editor:duplicate-lines'
-  'cmd-L': 'editor:split-selections-into-lines'
+  'cmd-shift-d': 'editor:duplicate-lines'
+  'cmd-shift-l': 'editor:split-selections-into-lines'
   'ctrl-shift-up': 'editor:add-selection-above'
   'ctrl-shift-down': 'editor:add-selection-below'

@@ -202,10 +202,10 @@
   'cmd-alt-=': 'pane:increase-size'
   'cmd-alt--': 'pane:decrease-size'

-# allow standard input fields to work correctly
+# Allow standard input fields to work correctly
 'body .native-key-bindings':
   'cmd-z': 'native!'
-  'cmd-Z': 'native!'
+  'cmd-shift-z': 'native!'
   'cmd-x': 'native!'
   'cmd-c': 'native!'
   'cmd-v': 'native!'


get rid of hackish approach to testing exception handling (just use %javascript in a sage worksheet!)

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -95,15 +95,7 @@
         count : rtypes.number

     on_click: (e) ->
-        if e.shiftKey and DEBUG # comment 'and DEBUG' to test throwing and reporting errors
-            console.warn("upcoming error")
-            setTimeout((->console.error("this is the error")), 100)
-            setTimeout((->throw new Error("setTimeout error")), 500)
-            window.requestAnimationFrame((-> misc.this_fails()))
-            # used to trigger an exception to test catching it
-            misc.this_fails()
-        else
-            @actions('page').toggle_show_file_use()
+        @actions('page').toggle_show_file_use()

     notification_count: ->
         count_styles =


Revert "update tests"

Revert "update tests"

This reverts commit 501e891760fd5c5bd0e6ffc75f866fd565561f31.

--- services/document-updater/test/unit/coffee/DocumentManager/flushDocTests.coffee
+++ services/document-updater/test/unit/coffee/DocumentManager/flushDocTests.coffee
@@ -24,7 +24,7 @@
 	describe "when the doc is in Redis", ->
 		beforeEach ->
 			@RedisManager.getDoc = sinon.stub().callsArgWith(1, null, @lines, @version)
-			@TrackChangesManager.flushDocChangesIfNeeded = sinon.stub().callsArg(2)
+			@TrackChangesManager.flushDocChanges = sinon.stub().callsArg(2)
 			@PersistenceManager.setDoc = sinon.stub().callsArgWith(4)
 			@DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback


Removed unnecessary dependency

Removed unnecessary dependency

--- client/account/lib/views/accounteditusername.coffee
+++ client/account/lib/views/accounteditusername.coffee
@@ -1,4 +1,3 @@
-$ = require 'jquery'
 kd = require 'kd'
 KDButtonView = kd.ButtonView
 KDCustomHTMLView = kd.CustomHTMLView


IDE: Debounce drawing line widgets.

IDE: Debounce drawing line widgets.

--- client/ide/lib/workspace/panes/ideeditorpane.coffee
+++ client/ide/lib/workspace/panes/ideeditorpane.coffee
@@ -348,7 +348,7 @@
     return data

-  setLineWidgets: (row, col, username) ->
+  setLineWidgets: kd.utils.debounce 500, (row, col, username) ->

     return  unless editor = @getEditor()
     return  if username is nick()


Add max resolution cap.

Add max resolution cap.

--- views/components/settings/parts/display-config.cjsx
+++ views/components/settings/parts/display-config.cjsx
@@ -144,7 +144,22 @@
     @setState
       gameWidth: @refs[node].getValue()
     width = parseInt @refs[node].getValue()
-    return if isNaN(width) || width < 0 || !@state.useFixedResolution || (config.get('poi.layout', 'horizontal') == 'horizontal' && width > window.innerWidth - 150)
+    return if isNaN(width) || width < 0 || !@state.useFixedResolution
+    if config.get('poi.layout', 'horizontal') == 'horizontal'
+      width = Math.min(width, window.innerWidth - 150)
+
+    # Avoid setting a huge size by mistake
+    max_height = screen.height
+    max_width = screen.width
+    if config.get('poi.layout', 'horizontal') == 'horizontal'
+      max_width = max_width - (if window.doubleTabbed then 600 else 400)
+    else
+      max_height = max_height - 200
+    console.log max_width, max_height, width
+    max_width = max_height / 480 * 800
+    if width > max_width
+      width = max_width
+
     window.webviewWidth = width
     window.dispatchEvent new Event('webview.width.change')
     config.set 'poi.webview.width', width


Add autoinit

Add autoinit

--- tooltip.coffee
+++ tooltip.coffee
@@ -1,19 +1,40 @@
 DropTooltip = Drop.createContext()

 defaults =
-    attach: 'top center'
+  attach: 'top center'

 class Tooltip
-    constructor: (@options) ->
-        @options.attach ?= defaults.attach
+  constructor: (@options) ->
+    @options.attach ?= @options.el.getAttribute('data-tooltip-attach') ? defaults.attach
+    @options.content ?= @options.el.getAttribute('data-tooltip')

-        @drop = new DropTooltip
-            target: @options.el
-            className: 'drop-tooltip-theme-arrows'
-            attach: @options.attach
-            constrainToWindow: true
-            constrainToScrollParent: false
-            openOn: 'hover'
-            content: @options.content ? @$target.attr('data-tooltip-content')
+    @drop = new DropTooltip
+      target: @options.el
+      className: 'drop-tooltip-theme-arrows'
+      attach: @options.attach
+      constrainToWindow: true
+      constrainToScrollParent: false
+      openOn: 'hover'
+      content: @options.content
+
+initialized = []
+Tooltip.init = ->
+  for el in document.querySelectorAll('[data-tooltip]') when el not in initialized
+    new Tooltip {el}
+
+    initialized.push el
+
+if document.documentElement?.doScroll? and document.createEventObject?
+  # IE < 9
+  _old = document.onreadystatechange
+  document.onreadystatechange = ->
+    if document.readyState is 'complete'
+      Tooltip.init()
+
+    _old?.apply this, arguments
+else
+  document.addEventListener 'DOMContentLoaded', ->
+    Tooltip.init()
+  , false

 window.Tooltip = Tooltip


JGroup::modify: invalidate in-memory cache on permissions after modify

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -16,7 +16,7 @@
   { Inflector, ObjectId, ObjectRef, secure, signature } = require 'bongo'

   JPermissionSet = require './permissionset'
-  { permit }     = JPermissionSet
+  { permit, expireCache } = JPermissionSet

   JAccount       = require '../account'
   KodingError    = require '../../error'
@@ -1077,6 +1077,10 @@
         return callback err  if err
         return callback new KodingError 'No such group!'  unless group

+        kallback = (rest...) ->
+          expireCache group.getAt 'slug'
+          callback rest...
+
         # we need to make sure if given stack template is
         # valid for the current group limit ~ GG
         templates = data.stackTemplates
@@ -1084,9 +1088,9 @@
           ComputeProvider = require '../computeproviders/computeprovider'
           ComputeProvider.validateTemplates client, templates, group, (err) =>
             return callback err  if err
-            @updateAndNotify notifyOptions, { $set: data }, callback
+            @updateAndNotify notifyOptions, { $set: data }, kallback
         else
-          @updateAndNotify notifyOptions, { $set: data }, callback
+          @updateAndNotify notifyOptions, { $set: data }, kallback

   setLimit    : permit


Group/index: remove the favicon field from model

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -256,7 +256,6 @@
       customize     :
         coverPhoto  : String
         logo        : String
-        favicon     : String
       payment       :
         plan        : String
         paymentQuota: Number


[networking] show cidr on subnet at floating ip allocation

--- plugins/networking/app/assets/javascripts/networking/global/floating_ips.coffee
+++ plugins/networking/app/assets/javascripts/networking/global/floating_ips.coffee
@@ -6,7 +6,7 @@
   $select.empty()

   $select.append('<option value=""></option>')
-  $select.append('<option value="'+subnet.id+'">'+subnet.name+'</option>') for subnet in subnets
+  $select.append('<option value="'+subnet.id+'">'+subnet.name+' ('+subnet.cidr+')'+'</option>') for subnet in subnets

   $('fieldset#subnets .form-group').show()


fixed request an invite for non-group and profile routes/entry points

--- client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
+++ client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
@@ -48,7 +48,7 @@
       attributes  :
         href      : "/Join"
       click       : (event)=>
-        if entryPoint.slug isnt ''
+        if entryPoint?.type is 'group'
           @utils.stopDOMEvent event
           requiresLogin => @getSingleton('mainController').emit "groupAccessRequested", @group, no
         else


[MOD] Changes uuid to use underscores instead of dashes

This allows us to pass generated uuids to the server and have them
serialized back in the same way.

--- src/thinkspace/client/thinkspace-common/addon/mixins/helpers/common/uuid.coffee
+++ src/thinkspace/client/thinkspace-common/addon/mixins/helpers/common/uuid.coffee
@@ -3,7 +3,7 @@
 export default ember.Mixin.create

   uuid: ->
-    'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
+    'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
       r = Math.random() * 16 | 0
       v = if c == 'x' then r else r & 0x3 | 0x8
       v.toString 16


Removed fancy new selection persistance.  3-5s UI change times were unacceptable.

--- client/list.coffee
+++ client/list.coffee
@@ -6,22 +6,23 @@

 		# Template._listForm.helpers(data.form.helpers)

-		Template._listRow.events(data.row.events)
+		Template._listRow.events(data?.row?.events)
 		if data.row?.helpers?
 			Template._listRow.helpers(data.row.helpers)

-		Template.lists.events(data.list.events)
+		Template.lists.events(data?.list?.events)

 		items = []
-		for item in data.items()
-			data = Object.reject(data, 'items')
-			item.context = data
-			items.add item
+		if data.items? and typeof data.items is 'function'
+			for item in data.items()
+				data = Object.reject(data, 'items')
+				item.context = data
+				items.add item

-		if items.length > 0
-			return items
+			if items.length > 0
+				return items

 Template._listRow.helpers
 	disabled: () ->
@@ -30,9 +31,8 @@
 	selected: () ->
 		context = this.context
 		single = context.single
-		user = Meteor.user()
-
-		return getUserSelectedItem(user, single, this._id) and 'alert-info'
+		if Session.get("selected-#{single}") is this._id
+			return 'alert-info'

 Template._listRow.events
 	"click .close-form": () ->
@@ -43,17 +43,18 @@
 # names in listFields.
 Handlebars.registerHelper 'listColumnHeaders', (data) ->
 	listFields = data.listFields
-	columnHeaders =
-		for field in listFields
-			if field.label?
-				field.label.titleize()
-			else
-				field.name.titleize()
+	if Array.isArray(listFields) and listFields.length > 0
+		columnHeaders =
+			for field in listFields
+				if field.label?
+					field.label.titleize()
+				else
+					field.name.titleize()

-	returnData =
-		columnHeaders: columnHeaders
+		returnData =
+			columnHeaders: columnHeaders

-	return new Handlebars.SafeString Template._listColumnHeaders(returnData)
+		return new Handlebars.SafeString Template._listColumnHeaders(returnData)

 Handlebars.registerHelper 'listRow', () ->
@@ -85,7 +86,10 @@

 Handlebars.registerHelper 'ListForm', (data, context) ->
 	fields = []
-	providedFields = data.form.helpers.fields
+	if data.helpers?
+		providedFields = data.helpers.fields
+	else
+		providedFields = data.form.helpers.fields
 	if not Array.isArray(providedFields)
 		for key in Object.keys(providedFields)
 			options = providedFields[key]


Fix merge bug.

Fix merge bug.

--- client/loot.coffee
+++ client/loot.coffee
@@ -115,7 +115,7 @@
       delete new_item._id
       new_item.quantity = 0
       for item in set
-        new_item.quantity += item.quantity
+        new_item.quantity += parseInt(item.quantity)
         Inventory.remove item._id
       Meteor.call "add", new_item, this.bag

@@ -148,7 +148,6 @@
   "click .action_stats": (event) ->
     event.preventDefault()
     items = Inventory.find({_id: {$in: Session.get "selected"}}).fetch()
-    Session.set "selected", []
     values = (item.quantity * item.value for item in items)
     value = values.reduce (a, b) -> a + b
     bootbox.alert "This is worth a total of #{value} gold."


Minor typo

Minor typo

--- client/main.coffee
+++ client/main.coffee
@@ -43,7 +43,7 @@
           socket.on 'unauthorized', =>
             @connecting = false
             console.log 'Auth Failed'
-          socket.on 'authenticated', =>
+          socket.on 'Authenticated', =>
             console.log 'Sucess!'
             @connecting = false
             @socket = socket


Correctley reference member method won countdown

--- coffee/game.coffee
+++ coffee/game.coffee
@@ -633,7 +633,7 @@
           $('#tick-noclick').hide()
           if $('#dial-col').data('max-level') == $('#dial-col').data('level')
             $('#won').show()
-            beginWonCountdown(5)
+            @beginWonCountdown(5)
           else
             $('#continue').show()


Debugs grid

Debugs grid

--- coffee/grid.coffee
+++ coffee/grid.coffee
@@ -7,40 +7,45 @@

     @gridWidth = @source.outerWidth()
     @gridHeight = @source.outerHeight()
-    @verticalStreetNumber = Math.floor(@gridWidth / @blockSize)
-    @horizontalStreetNumber = Math.floor(@gridHeight / @blockSize)
+    @verticalStreetNumber = Math.floor(@gridWidth / @blockSize) + 1
+    @horizontalStreetNumber = Math.floor(@gridHeight / @blockSize) + 1

   render: () ->
     for i in [0..Math.max(@horizontalStreetNumber, @verticalStreetNumber)]
       z = i * @blockSize - @streetSize / 2
-      horizontalStreet = @snap.rect(0, z, @gridWidth, @streetSize) unless i > @horizontalStreetNumber
-      verticalStreet = @snap.rect(z, 0, @streetSize, @gridHeight) unless i > @verticalStreetNumber
+      horizontalStreet = @snap.rect(0, z, @gridWidth, @streetSize) unless i >= @horizontalStreetNumber
+      verticalStreet = @snap.rect(z, 0, @streetSize, @gridHeight) unless i >= @verticalStreetNumber
+  # TODO: customize streets

   # Returns a random cross street junction (via a point)
   randomCrossStreets: () ->
-    i = Math.round(Math.random() * (@horizontalStreetNumber - 1))
-    j = Math.round(Math.random() * (@verticalStreetNumber - 1))
+    i = Math.round(Math.random() * (@verticalStreetNumber - 1))
+    j = Math.round(Math.random() * (@horizontalStreetNumber - 1))

     return new Point(i * @blockSize, j * @blockSize)

   isWithinAStreet: (position) ->
-    return ((position.getX() % @blockSize) <= @streetSize) or ((position.getY() % @blockSize) <= @streetSize)
+    return ((position.getX() % @blockSize) is 0) or ((position.getY() % @blockSize) is 0)

   isACrossStreet: (position) ->
-    return ((position.getX() % @blockSize) <= @streetSize) and ((position.getY() % @blockSize) <= @streetSize)
+    return ((position.getX() % @blockSize) is @streetSize) and ((position.getY() % @blockSize) is 0)

   getPrevHorizontalCross: (position) ->
-    j = Math.floor(position.getY() / @horizontalStreetNumber)
+    j = Math.floor(position.getY() / @horizontalStreetNumber) - 1
+    return position if j < 0
     return new Point(position.getX(), j * @blockSize)

   getNextHorizontalCross: (position) ->
-    p = @getPrevHorizontalCross(position)
-    return new Point(p.getX(), p.getY() + @blockSize)
+    j = Math.floor(position.getY() / @horizontalStreetNumber) + 1
+    return position if j >= @horizontalStreetNumber
+    return new Point(position.getX(), j * @blockSize)

   getPrevVerticalCross: (position) ->
-    i = Math.floor(position.getX() / @verticalStreetNumber)
+    i = Math.floor(position.getX() / @verticalStreetNumber) - 1
+    return position if j < 0
     return new Point(i * @blockSize, position.getY())

   getNextVerticalCross: (position) ->
-    p = @getPrevVerticalCross(position)
-    return new Point(p.getX() + @blockSize, p.getY())+    i = Math.floor(position.getX() / @verticalStreetNumber) + 1
+    return position if j >= @verticalStreetNumber
+    return new Point(i * @blockSize, position.getY())


fix null propagate on props editor save

fix null propagate on props editor save

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -1056,7 +1056,7 @@
   save: () ->
       for k, v of @cprops
         for e in @elements
-          e.props[k] = v
+          e.props[k] = v if v != "..."
       @ve.addie.update(@elements)

   hide: () ->
@@ -1109,7 +1109,7 @@
       if isUniform(v)
         e[k] = v[0]
       else
-        e[k] = ""
+        e[k] = "..."
       true

     reduceUniform = (xps) ->


fix for appending file

fix for appending file

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -830,7 +830,7 @@
               makeRequest(val,$(elem).attr("name"))
             file: ->
               $("#file_upload_link_"+cid).html(
-                "
"+val.split("/").pop().split("?")[0]+"
"
+                "
"+val.split("/").pop().split("?")[0]+"
"
               ) if $('#file_upload_link_'+cid) and val
             take_pic_video_audio: ->
               $(elem).attr("href",val)


payment: fix type

payment: fix type

BuyerReportRequested => BuyersReportRequested

--- client/app/Applications/Dashboard.kdapplication/views/productsettingsview.coffee
+++ client/app/Applications/Dashboard.kdapplication/views/productsettingsview.coffee
@@ -37,7 +37,7 @@
       @forwardEvents @["#{category}sView"], [
         'DeleteRequested'
         'EditRequested'
-        'BuyerReportRequested'
+        'BuyersReportRequested'
       ], category.capitalize()

   setProducts: (products) ->


Add Client-ID header for Twitch API

Add Client-ID header for Twitch API

--- src/provider/twitch-vod.coffee
+++ src/provider/twitch-vod.coffee
@@ -3,8 +3,16 @@
 request = require '../request'
 Media = require '../media'

+CLIENT_ID = null
+
 exports.lookup = (id) ->
-    return request.getJSON("https://api.twitch.tv/kraken/videos/#{id}").then((result) ->
+    if not CLIENT_ID
+        return Promise.reject(new Error('Client ID not set for Twitch API'))
+
+    return request.getJSON("https://api.twitch.tv/kraken/videos/#{id}",
+        headers:
+            'Client-ID': CLIENT_ID
+    ).then((result) ->
         media = new Media(
             id: id
             title: result.title
@@ -42,3 +50,6 @@
         }

     return null
+
+exports.setClientID = (clientID) ->
+    CLIENT_ID = clientID


Syntax highlighting is saved in blog edit, so shouldn't need it again on show?

--- client/views/blog/blog.coffee
+++ client/views/blog/blog.coffee
@@ -94,12 +94,6 @@
   if not @data.published
     $('<meta>', { name: 'robots', content: 'noindex,nofollow' }).appendTo 'head'

-  # Syntax Highlighting
-  if Blog.settings.syntaxHighlighting
-    hljs.configure({userBR: true})
-    $('pre').each (i, block) ->
-      hljs.highlightBlock(block)
-

 Template.disqus.rendered = ->


remove fileuploadbuilder

remove fileuploadbuilder

--- vendor/assets/javascripts/rails_admin/themes/glyph/src/glyph-theme.coffee
+++ vendor/assets/javascripts/rails_admin/themes/glyph/src/glyph-theme.coffee
@@ -1,32 +1,32 @@
-# class FileUploadBuilder
-#   template:
-#     """
-#

-#

-#

-#
-#
-#

-#
-#           Sélectionnez
-#           Changer
-#
-#         Supprimer
-#

-#

-#     """
+class FileUploadBuilder
+  template:
+    """
+

+

+

+
+
+

+
+          Sélectionnez
+          Changer
+
+        Supprimer
+

+

+    """

-#   constructor: (@$el) ->
-#     $uploadedFile = @$el.parent().find('a, img').addClass('uploaded-file').remove()
+  constructor: (@$el) ->
+    $uploadedFile = @$el.parent().find('a, img').addClass('uploaded-file').remove()

-#     unless @$el.data("file-upload")
-#       $container = @$el.parent().html(@template)
-#       # Append input to file buttons
-#       $container.find(".btn-file").append(@$el)
-#       # Add preview link if existing
-#       $container.prepend($uploadedFile) if $uploadedFile.length
-#       # Store object in "file-upload" data key on dom element
-#       @$el.data("file-upload", this)
+    unless @$el.data("file-upload")
+      $container = @$el.parent().html(@template)
+      # Append input to file buttons
+      $container.find(".btn-file").append(@$el)
+      # Add preview link if existing
+      $container.prepend($uploadedFile) if $uploadedFile.length
+      # Store object in "file-upload" data key on dom element
+      @$el.data("file-upload", this)

 class SpinnerBuilder
@@ -71,7 +71,7 @@

 class FormInputs
   fieldTypes:
-    'input[type="file"]': FileUploadBuilder
+    # 'input[type="file"]': FileUploadBuilder
     'input[type="number"]': SpinnerBuilder
     'select': SelectBuilder


Home Install Mobile Fix

Home Install Mobile Fix

--- docs/components/home/HomeFeature.cjsx
+++ docs/components/home/HomeFeature.cjsx
@@ -21,7 +21,6 @@
         textAlign: 'center'
         zIndex: '2'
         position: 'relative'
-        background: 'rgba(150, 0, 0, .2)'

       headline:
         paddingLeft: '10px'
@@ -30,10 +29,12 @@
         fontSize: '34px'
         color: 'rgba(255, 255, 255, .87)'
         WebkitFontSmoothing: 'antialiased'
-        background: 'rgba(0, 150, 0, .2)'

       install:
-        marginTop: '70px'
+        position: 'absolute'
+        bottom: '-120px'
+        left: '50%'
+        transform: 'translateX(-50%)'
         display: 'inline-block'
         padding: '0 18px'
         height: '54px'
@@ -44,8 +45,8 @@
         color: 'rgba(255, 255, 255, .87)'
         fontWeight: '200'
         background: '#4A90E2'
-        position: 'relative'
         zIndex: '2'
+        minWidth: '180px'

       dollar:
         paddingRight: '10px'
@@ -55,7 +56,6 @@
       particles:
         Absolute: 'auto 0 0 0'
         height: '130px'
-        background: 'rgba(0, 0, 150, .2)'

   render: ->
     <div is="homeFeature">


#269 change toggle-link to button in dashboard

--- app/assets/javascripts/dashboard/views/dashboard_task_list_view.coffee
+++ app/assets/javascripts/dashboard/views/dashboard_task_list_view.coffee
@@ -13,7 +13,7 @@

     <% if (userID) { %>
       <a href="<%= jsRoutes.controllers.admin.NMLIO.userDownload(user.id).url %>"
-         class="btn btn-default"
+         class="btn btn-primary"
          title="download all finished tracings">
           download

@@ -25,7 +25,9 @@

     <% } %>

-    Show finished tasks
+
+      Show finished tasks
+


hatena authorizeが残っていたので除去

hatena authorizeが残っていたので除去

--- coffee/qiitaController.coffee
+++ coffee/qiitaController.coffee
@@ -87,43 +87,6 @@
     alert "start contoroller show"

-  postItemToHatena: () ->
-    Ti.API.info(Ti.App.Properties.getString('stockURL'))
-    configJSON = Titanium.Filesystem.getFile(Titanium.Filesystem.resourcesDirectory, 'config/hatena.json')
-    file = configJSON.read().toString()
-    hatenaKey = JSON.parse(file)
-    hatena = require('lib/hatena').Hatena({
-      consumerKey: hatenaKey.consumerKey,
-  		consumerSecret: hatenaKey.consumerSecret,
-  		accessTokenKey: Ti.App.Properties.getString("hatenaAccessTokenKey", ""),
-  		accessTokenSecret: Ti.App.Properties.getString("hatenaAccessTokenSecret", ""),
-  		scope: "read_public,write_public,write_private",
-    })
-
-    hatena.addEventListener('login', (e)->
-      Ti.API.info "start hanate login"
-
-
-      if e.success
-        Ti.App.Properties.setString('hatenaAccessTokenKey', e.accessTokenKey)
-        Ti.App.Properties.setString('hatenaAccessTokenSecret', e.accessTokenSecret)
-        hatena.request('applications/my.json',{}, {}, 'POST',(req)->
-          Ti.API.info req
-          if req.success
-            Ti.API.info "start hatena API request"
-            data = JSON.parse req.result.text
-            Ti.API.info data
-          # if e.success
-          #   Ti.API.info e.result.text
-        )
-
-    )
-
-    hatena.authorize()
-
-    return true
-    #
-		#

 module.exports = qiitaController


Update WAVE 7 for german and some fixes on old cards german translastions

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -17,6 +17,7 @@
         "Jam": "Störsignal"
         "Coordinate": "Koordination"
         "Cloak": "Tarnen"
+        "SLAM": "SLAM"
     slot:
         "Astromech": "Astromech"
         "Bomb": "Bombe"
@@ -30,7 +31,7 @@
         "Cargo": "Fracht"
         "Hardpoint": "Hardpoint"
         "Team": "Team"
-        "Illicit": "illegales"
+        "Illicit": "Illegales"
         "Salvaged Astromech": "geborgener Astromech"
     sources: # needed?
         "Core": "Grundspiel"
@@ -60,8 +61,12 @@
         "M3-A Interceptor Expansion Pack": "M3-A Abfangjäger Erweiterung"
         "IG-2000 Expansion Pack": "IG-2000 Erweiterung"
         "Most Wanted Expansion Pack": "Abschaum und Kriminelle Erweiterung"
-        "Imperial Raider Expansion Pack": "Imperial Raider Expansion Pack"
-        "The Force Awakens Core Set": "The Force Awakens Core Set"
+        "Imperial Raider Expansion Pack": "Imperiale Sturm-Korvette Erweiterung"
+        "Hound's Tooth Expansion Pack": "Reisszahn Erweiterung"
+        "Kihraxz Fighter Expansion Pack": "Kihraxz-Jäger Erweiterung"
+        "K-Wing Expansion Pack": "K-Wing Erweiterung"
+        "TIE Punisher Expansion Pack": "TIE-Vergelter Erweiterung"
+        "The Force Awakens Core Set": "Das Erwachen der Macht Grundspiel"
     ui:
         shipSelectorPlaceholder: "Wähle ein Schiff"
         pilotSelectorPlaceholder: "Wähle einen Piloten"
@@ -75,7 +80,7 @@
         epic: "Episch"
         limited: "limitiert"
     byCSSSelector:
-        '.translate.sort-cards-by': 'Sortiere Karten per'
+        '.translate.sort-cards-by': 'Sortiere Karten nach'
         '.xwing-card-browser option[value="name"]': 'Name'
         '.xwing-card-browser option[value="source"]': 'Quelle'
         '.xwing-card-browser option[value="type-by-points"]': 'Typ (Punkte)'
@@ -89,19 +94,21 @@
         '.info-well .info-upgrades td.info-header': 'Aufwertungen'
         '.info-well .info-range td.info-header': 'Reichweite'
         # Squadron edit buttons
-        '.clear-squad' : 'Neues Squad'
+        '.clear-squad' : 'Neue Staffel'
         '.save-list' : 'Speichern'
         '.save-list-as' : 'Speichern als…'
         '.delete-list' : 'Löschen'
-        '.backend-list-my-squads' : 'Squad laden'
+        '.backend-list-my-squads' : 'Staffel laden'
         '.view-as-text' : ' Drucken/Anzeigen als Text'
+        '.collection' : ' Deine Sammlung'
         '.randomize' : 'Zufallsliste!'
-        '.randomize-options' : 'Zufallslistenoptionen…'
+        '.randomize-options' : 'Zufallslisten-Optionen…'
+        '.notes-container' : 'Staffelnotizen'
         # Print/View modal
         '.bbcode-list' : 'Kopiere den BBCode von unten und füge ihn in deine Forenposts ein.<textarea></textarea>'
-        '.vertical-space-checkbox' : """Platz für Schadenskarten und Upgrades im Druck berücksichtigen. <input type="checkbox" class="toggle-vertical-space" />"""
-        '.color-print-checkbox' : """Ausdrucken in farbe. <input type="checkbox" class="toggle-color-print" />"""
-        '.print-list' : ' Print'
+        '.vertical-space-checkbox' : """Platz für Schadenskarten und Aufwertungen im Druck berücksichtigen. <input type="checkbox" class="toggle-vertical-space" />"""
+        '.color-print-checkbox' : """Ausdrucken in Farbe. <input type="checkbox" class="toggle-color-print" />"""
+        '.print-list' : ' Druck'
         # Randomizer options
         '.do-randomize' : 'Zufall!'
         # Top tab bar
@@ -110,6 +117,9 @@
         '#scumTab' : 'Abschaum und Kriminelle'
         '#browserTab' : 'Karten Browser'
         '#aboutTab' : 'Über'
+        '.from-xws' : 'Import von XWS (beta)'
+        '.to-xws' : 'Export nach XWS (beta)'
+
     singular:
         'pilots': 'Pilot'
         'modifications': 'Modifikation'
@@ -145,6 +155,16 @@
     exportObj.renameShip 'CR90 Corvette (Aft)', 'CR90-Korvette (Heck)'
     # Move M3-A Interceptor to M3-A Abfangjäger
     exportObj.renameShip 'M3-A Interceptor', 'M3-A Abfangjäger'
+    # Move Raider-class Corvette (Fore) to Korv. der Sturm-Klasse (Bug)
+    exportObj.renameShip 'Raider-class Corvette (Fore)', 'Korv. der Sturm-Klasse (Bug)'
+    # Move Raider-class Corvette (Aft) to Korv. der Sturm-Klasse (Heck)
+    exportObj.renameShip 'Raider-class Corvette (Aft)', 'Korv. der Sturm-Klasse (Heck)'
+    # Move Kihraxz Fighter to Kihraxz-Jäger
+    exportObj.renameShip 'Kihraxz Fighter', 'Kihraxz-Jäger'
+    # Move TIE Punisher to TIE-Vergelter
+    exportObj.renameShip 'TIE Punisher', 'TIE-Vergelter'
+    # Move StarViper to SternenViper
+    exportObj.renameShip 'StarViper', 'SternenViper'

     pilot_translations =
         "Wedge Antilles":
@@ -346,7 +366,7 @@
         "Wes Janson":
             text: """Nachdem du einen Angriff durchgeführt hast, darfst du 1 Fokus-, Ausweich- oder blauen Zielerfassungsmarker vom Verteidiger entfernen."""
         "Jek Porkins":
-            text: """Wenn du einen Stressmarker erhälst, darfst du ihn entfernen und 1 Angriffswürfel werfen. Bei %HIT% bekommt dein Schiff 1 verdeckte Schadenskarte."""
+            text: """Wenn du einen Stressmarker erhältst, darfst du ihn entfernen und 1 Angriffswürfel werfen. Bei %HIT% bekommt dein Schiff 1 verdeckte Schadenskarte."""
         '"Hobbie" Klivian':
             text: """Wenn du ein Schiff in die Zielerfassung nimmst oder einen Zielerfassungsmarker ausgibst, kannst du 1 Stressmarker von deinem Schiff entfernen."""
         "Tarn Mison":
@@ -385,13 +405,17 @@
         "Captain Oicunn":
             text: """Nach dem Ausführen eines Manövers nimmt jedes feindliche Schiff, das du berührst, 1 Schaden."""
         "Prince Xizor":
+            ship: "SternenViper"
             name: "Prinz Xizor"
             text: """Sobald du verteidigst, darf ein freundliches Schiff in Reichweite 1 ein nicht-negiertes %HIT% oder %CRIT% an deiner Stelle nehmen."""
         "Guri":
+            ship: "SternenViper"
             text: """Wenn du zu Beginn der Kampfphase in Reichweite 1 zu einem feindlichen Schiff bist, darfst du 1 Fokusmarker auf dein Schiff legen."""
         "Black Sun Vigo":
+            ship: "SternenViper"
             name: "Vigo der Schwarzen Sonne"
         "Black Sun Enforcer":
+            ship: "SternenViper"
             name: "Vollstrecker der Schwarzen Sonne"
         "Serissu":
             ship: "M3-A Abfangjäger"
@@ -441,38 +465,69 @@
             name: "Spiceschmuggler"
         "N'Dru Suhlak":
             ship: "Z-95-Kopfjäger"
-            text: """Sobald du angreifst, erhälst du 1 zusätzlichen Angriffswürfel, falls keine anderen freundlichen Schiffe in Reichweite 1-2 zu dir sind."""
+            text: """Sobald du angreifst, erhältst du 1 zusätzlichen Angriffswürfel, falls keine anderen freundlichen Schiffe in Reichweite 1-2 zu dir sind."""
         "Kaa'To Leeachos":
             ship: "Z-95-Kopfjäger"
             text: """Zu Beginn der Kampfphase darfst du 1 Fokus- oder Ausweichmarker von einem anderem freundlichen Schiff in Reichweite 1-2 entfernen und dir selbst zuordnen."""
         "Binayre Pirate":
             ship: "Z-95-Kopfjäger"
-            name: "Binayre-Pilot"
+            name: "Binayre-Pirat"
         "Black Sun Soldier":
             ship: "Z-95-Kopfjäger"
             name: "Kampfpilot der Schwarzen Sonne"
         "Commander Alozen":
-            text: """At the start of the Combat phase, you may acquire a target lock on an enemy ship at Range 1."""
+            text: """Zu Beginn der Kampfphase darfst du ein feindliches Schiff in Reichweite 1 in die Zielerfassung nehmen."""
         "Raider-class Corvette (Fore)":
-            text: """Once per round, ??? perform a primary ??? attack, you may spend 2 e??? perform another primary wea???"""
+            name: "Korv. der Sturm-Klasse (Bug)"
+            text: """Ein Mal pro Runde darfst du, nachdem du einen Primärwaffen-Angriff durchgeführt hast, 2 Energie ausgeben, um einen weiteren Primärwaffen-Angriff durchzuführen."""
+        "Raider-class Corvette (Aft)":
+            name: "Korv. der Sturm-Klasse (Heck)"
+        "Talonbane Cobra":
+            text: """Sobald du angreifst oder verteidigst, wird der Effekt deiner Kampfvorteile durch Reichweite verdoppelt."""
+        "Miranda Doni":
+            text: """Ein Mal pro Runde darfst du, sobald du angreifst, entweder 1 Schild ausgeben, um 1 zusätzlichen Angriffswürfel zu werfen, oder 1 Angriffswürfel weniger werfen, um 1 Schild wiederaufzuladen."""
+        '"Redline"':
+            name: '"Rote Linie"'
+            text: """Du darfst 2 Zielerfassungen auf demselben Schiff haben. Sobald du ein Schiff in die Zielerfassung nimmst, darfst du es ein zweites Mal in die Zielerfassung nehmen."""
+        '"Deathrain"':
+            name: '"Todesregen"'
+            text: """Sobald du eine Bombe legst, darfst du die Stopper am Bug deines Schiffs benutzen. Nachdem du eine Bombe gelegt hast, darfst du als freie Aktion eine Fassrolle durchführen."""
         "Juno Eclipse":
-            text: """When you reveal your maneuver, you may increase or decrease its speed by 1 (to a minimum of 1)."""
+            text: """Sobald du dein Manöver aufdeckst, darfst du die Geschwindigkeit um 1 erhöhen oder reduzieren (bis zu einem Minimum von 1)."""
         "Zertik Strom":
-            text: """Enemy ships at Range 1 cannot add their range combat bonus when attacking."""
+            text: """Sobald feindliche Schiffe in Reichweite 1 angreifen, können sie ihren Kampfvorteil durch Reichweite nicht hinzufügen."""
         "Lieutenant Colzet":
-            text: """At the start of the End phase, you may spend a target lock you have on an enemy ship to flip 1 random facedown Damage card assigned to it faceup."""
+            text: """Zu Beginn der Endphase darfst du einen Zielerfassungsmarker , den du auf einem feindlichen Schiff liegen hast, ausgeben, um 1 seiner verdeckten Schadenskarten (zufällig bestimmt) aufzudecken."""
         "Latts Razzi":
-            text: """When a friendly ship declares an attack, you may spend a target lock you have on the defender to reduce its agility by 1 for that attack."""
+            text: """Sobald ein freundliches Schiff einen Angriff deklariert und du den Verteidiger in der Zielerfassung hast, kannst du einen Zielerfassungsmarker ausgeben, um die Wendigkeit des Verteidigers für diesen Angriff um 1 zu senken."""
         "Graz the Hunter":
-            text: """When defending, if the attacker is inside your firing arc, roll 1 additional defense die."""
+            name: "Graz der Jäger"
+            text: """Wirf 1 zusätzlichen Verteidigungswürfel, wenn der Angreifer in deinem Feuerwinkel ist, sobald du verteidigst."""
         "Esege Tuketu":
-            text: """When another friendly ship at Range 1-2 is attacking, it may treat your focus tokens as its own."""
-        '"Redline"':
-            text: """You may maintain 2 target locks on the same ship.  When you acquire a target lock, you may acquire a second lock on that ship."""
-        '"Deathrain"':
-            text: """When dropping a bomb, you may use the front guides of your ship.  After dropping a bomb, you may perform a free barrel roll action."""
+            text: """Sobald ein anderes freundliches Schiff in Reichweite 1-2 angreifst, darf es deine Fokusmarker wie seine eigenen behandeln."""
         "Moralo Eval":
-            text: """You can perform %CANNON% secondary attacks against ships inside your auxiliary firing arc."""
+            text: """Du darfst Angriffe mit %CANNON%-Sekundärwaffen gegen Schiffe in deinem Zusatz-Feuerwinkel durchführen."""
+        "Warden Squadron Pilot":
+            ship: "K-Wing"
+            name: "Pilot der Beschützer-Staffel"
+        "Guardian Squadron Pilot":
+            ship: "K-Wing"
+            name: "Pilot der Wächter-Staffel"
+        "Cutlass Squadron Pilot":
+            ship: "TIE-Vergelter"
+            name: "Pilot der Entermesser-Staffel"
+        "Black Eight Squadron Pilot":
+            ship: "TIE-Vergelter"
+            name: "Pilot der Schwarzen-Acht-Staffel"
+        "Cartel Marauder":
+            ship: "Kihraxz-Jäger"
+            name: "Kartell-Marodeur"
+        "Black Sun Ace":
+            ship: "Kihraxz-Jäger"
+            name: "Fliegerass der schwarzen Sonne"
+        "Trandoshan Slaver":
+            ship: "YV-666"
+            name: "Trandoshanischer Sklavenjäger"
         'Gozanti-class Cruiser':
             text: """After you execute a maneuver, you may deploy up to 2 attached ships."""
         '"Scourge"':
@@ -544,7 +599,7 @@
             text: """Angriff (Zielerfassung): Gib deine Zielerfassungsmaker aus und lege diese Karte ab, um mit dieser Sekundärwaffe zwei Mal anzugreifen."""
         "Daredevil":
             name: "Draufgänger"
-            text: """Aktion: Führe ein weißes (%TURNLEFT% 1) oder (%TURNRIGHT% 1) Manöver aus. Dann erhälst du einen Stresmarker.

Wenn du kein %BOOST%-Aktionssymbol hast, musst du dann 2 Angriffswürfel werfen. Du nimmst allen gewürfelten Schaden (%HIT%) und kritischen Schaden (%CRIT%)."""
+            text: """Aktion: Führe ein weißes (%TURNLEFT% 1) oder (%TURNRIGHT% 1) Manöver aus. Dann erhältst du einen Stressmarker.

Wenn du kein %BOOST%-Aktionssymbol hast, musst du dann 2 Angriffswürfel werfen. Du nimmst allen gewürfelten Schaden (%HIT%) und kritischen Schaden (%CRIT%)."""
         "Elusiveness":
             name: "Schwer zu Treffen"
             text: """Wenn du verteidigst, darfst du 1 Stressmarker nehmen, um 1 Angriffswürfel zu wählen. Diesen muss der Angreifer neu würfeln.

Du kannst diese Fähigkeit nicht einsetzen, solange du 1 oder mehrere Stressmarker hast."""
@@ -712,7 +767,7 @@
             text: """Behandle in der Aktivierungsphase deinen Pilotenwert als "0"."""
         "Chardaan Refit":
             name: "Chardaan-Nachrüstung"
-            text: """Diese Karte hat negative Kommandopunktekosten."""
+            text: """Nur für A-Wing%LINEBREAK%Diese Karte hat negative Kommandopunktekosten."""
         "Proton Rockets":
             name: "Protonenraketen"
             text: """Angriff (Fokussierung): Lege diese Karte ab, um mit dieser Sekundärwaffe anzugreifen.

Du darfst so viele zusätzliche Angriffswürfel werfen, wie du Wendigkeit hast (maximal 3 zusätzliche Würfel)."""
@@ -814,7 +869,7 @@
             text: """Sobald du angreifst, darfst du alle deine Würfelergebnisse negieren. Dann darfst du 2 %HIT% hinzufügen.%LINEBREAK%Deine Würfel können während dieses Angriffs nicht noch einmal modifiziert werden."""
         "Inertial Dampeners":
             name: "Trägheitsdämpfer"
-            text: """Sobald du dein Manöverraf aufdeckst, darfst du diese Karte ablegen, um [0%STOP%]-Manöver auszuführen. Dann erhältst du 1 Stressmarker."""
+            text: """Sobald du dein Manöverrad aufdeckst, darfst du diese Karte ablegen, um stattdessen ein weißes [0%STOP%]-Manöver auszuführen. Dann erhältst du 1 Stressmarker."""
         "Flechette Cannon":
             name: "Flechettekanonen"
             text: """Angriff: Greife 1 Schiffe an.%LINEBREAK%Wenn dieser Angriff trifft, nimmt das verteidigende Schiff 1 Schaden und erhält 1 Stressmarker (falls es noch keinen hat.) Dann werden alle Würfelergebnisse negiert."""
@@ -826,14 +881,14 @@
             text: """Sobald du zerstörst wirst, nimmt jedes Schiff in Reichweite 1 einen Schaden."""
         "Feedback Array":
             name: "Rückkopplungsfeld"
-            text: """In der Kampfphase darfst du statt einen Angriff durchzuführen 1 Ionenmarker und 1 Schaden nehmen, um eine feindliches Schiff in Reichweite 1 zu wählen. Das gewählte Schiff nimmt 1 Schaden."""
+            text: """In der Kampfphase darfst du statt einen Angriff durchzuführen 1 Ionenmarker und 1 Schaden nehmen, um ein feindliches Schiff in Reichweite 1 zu wählen. Das gewählte Schiff nimmt 1 Schaden."""
         '"Hot Shot" Blaster':
             text: """Angriff: Lege diese Karte ab, um 1 Schiff (auch außerhalb deines Feuerwinkels) anzugreifen."""
         "Greedo":
             text: """%DE_SCUMONLY%%LINEBREAK%In jeder Runde wird bei deinem ersten Angriff und deiner ersten Verteidigung die erste Schadenskarte offen zugeteilt."""
         "Salvaged Astromech":
             name: "Abgewrackter Astromechdroide"
-            text: """Sobald du eine Schadenskarte mit dem Attribut Schiff erhälst, darfst du sie sofort ablegen (bevor ihr Effekt abgehandelt wird).%LINEBREAK%Danach wird diese Aufwertungskarte abgelegt."""
+            text: """Sobald du eine Schadenskarte mit dem Attribut Schiff erhältst, darfst du sie sofort ablegen (bevor ihr Effekt abgehandelt wird).%LINEBREAK%Danach wird diese Aufwertungskarte abgelegt."""
         "Bomb Loadout":
             name: "Bombenladung"
             text: """Nur für Y-Wing.%LINEBREAK%Füge deiner Aufwertungsleiste das %BOMB%-Symbol hinzu."""
@@ -858,41 +913,68 @@
             name: "Gesetzloser Techniker"
             text: """%DE_SCUMONLY%%LINEBREAK%Nachdem du ein rotes Manöver ausgeführt hast, darfst du deinem Schiff 1 Fokusmarker zuweisen."""
         "Advanced Targeting Computer":
-            text: """TIE Advanced only.%LINEBREAK%When attacking with your primary weapon, if you have a target lock on the defender, you may add 1 %CRIT% result to your roll.  If you do, you cannot spend target locks during this attack."""
+            name: "Verbesserter Zielcomputer"
+            text: """Nur für TIE Advanced%LINEBREAK%Sobald du mit deiner Primärwaffe angreifst, darfst du deinem Würfelwurf 1 %CRIT% hinzufügen, wenn du den Verteidiger in der Zielerfassung hast. Wenn du das tust, kannst du während dieses Angriffs keine Zielerfassungen ausgeben."""
         "Ion Cannon Battery":
-            text: """Attack (energy): Spend 2 energy from this card to perform this attack.  If this attack hits, the defender suffers 1 critical damage and receives 1 ion token.  Then cancel all dice results."""
+            name: "Ionengeschützbatterie"
+            text: """Angriff (Energie): Gib 2 Energie von dieser Karte aus, um diesen Angriff durchzuführen. Wenn dieser Angriff trifft, nimmt der Verteidiger 1 kritischen Schaden und erhält 1 Ionenmarker. Dann werden alle Würfelergebnisse negiert."""
+        "Extra Munitions":
+            name: "Ersatzmunition"
+            text: """Sobald du diese Karte ausrüstest, lege 1 Munitionsmarker auf jede ausgerüstete %TORPEDO%, %MISSILE% oder %BOMB% Aufwertungskarte. Sobald du angewiesen wirst eine Aufwertungskarte abzulegen, darfst du stattdessen 1 Munitionsmarker von der entsprechenden Karte ablegen."""
+        "Cluster Mines":
+            name: "Clusterminen"
+            text: """Aktion: Lege diese Karte ab, um 1 Satz Clusterminenmarker zu legen.

Ein Clusterminenmarker detoniert, sobald sich eine Schiffsbasis oder Manöverschablone mit diesem Marker überschneidet."""
+        "Glitterstim":
+            name: "Glitzerstim"
+            text: """Zu Beginn der Kampfphase darfst du diese Karte ablegen und bekommst 1 Stressmarker. Tust du das , darfst du bis zum Ende der Runde, sobald du angreifst oder verteidigst, alle deine %FOCUS% in %HIT% oder %EVADE% ändern."""
+        "Grand Moff Tarkin":
+            name: "Grossmoff Tarkin"
+            text: """%DE_HUGESHIPONLY% %DE_IMPERIALONLY%%LINEBREAK%Zu Beginn der Kampfphase darfst du ein anderes Schiff in Reichweite 1-4 wählen. Entweder weist du ihm 1 Fokusmarker zu oder du entfernst 1 Fokusmarker von ihm."""
+        "Captain Needa":
+            text: """%DE_HUGESHIPONLY% %DE_IMPERIALONLY%%LINEBREAK%Wenn du dich in der Aktivierungsphase mit einem Hindernis überschneidest, erhältst du keine offene Schadenskarte. Stattdessen wirfst du 1 Angriffswürfel. Bei %HIT% oder %CRIT% nimmst du 1 Schaden."""
+        "Admiral Ozzel":
+            text: """%DE_HUGESHIPONLY% %DE_IMPERIALONLY%%LINEBREAK%Energie: Du darfst bis zu 3 Schilde von deinem Schiff entfernen. Für jedes entfernte Schild erhältst du 1 Energie."""
         "Emperor Palpatine":
-            text: """%IMPERIALONLY%%LINEBREAK%Once per round, you may change a friendly ship's die result to any other die result.  That die result cannot be modified again."""
+            name: "Imperator Palpatine"
+            text: """%DE_IMPERIALONLY%%LINEBREAK%Ein Mal pro Runde darfst du das Ergebnis eines Würfels, den ein freundliches Schiff geworfen hat, in ein beliebiges anderes Ergebnis ändern. Dieses Ergebnis kann nicht nochmal modifiziert werden."""
         "Bossk":
-            text: """%SCUMONLY%%LINEBREAK%After you perform an attack that does not hit, if you are not stressed, you must receive 1 stress token. Then assign 1 focus token to your ship and acquire a target lock on the defender."""
+            name: "Bossk (Crew)"
+            text: """%DE_SCUMONLY%%LINEBREAK%Nachdem du einen Angriff durchgeführt hast, der nicht geroffen hat, musst du 1 Stressmarker bekommen, wenn du keine Stressmarker hast. Weise dann deinem Schiff 1 Fokusmarker zu und nimm den Verteidiger in die Zielerfassung."""
         "Lightning Reflexes":
-            text: """%SMALLSHIPONLY%%LINEBREAK%After you execute a white or green maneuver on your dial, you may discard this card to rotate your ship 180°.  Then receive 1 stress token after the "Check Pilot Stress" step."""
+            name: "Blitzschnelle Reflexe"
+            text: """%DE_SMALLSHIPONLY%%LINEBREAK%Nachdem du ein weißes oder grünes Manöver von deinem Rad ausgeführt hast, darfst du diese Karte ablegen, um dein Schiff um 180° zu drehen. Dann erhältst du 1 Stressmarker nach dem Schritt "Stress des Piloten überprüfen"."""
         "Twin Laser Turret":
-            text: """Attack: Perform this attack twice (even against a ship outside your firing arc).

Each time this attack hits, the defender suffers 1 damage.  Then cancel all dice results."""
+            name: "Zwillingslasergeschütz"
+            text: """Angriff: Führe diesen Angriff zwei Mal durch (auch gegen ein Schiff außerhalb deines Feuerwinkels). Jedes Mal wenn dieser Angriff triffst, nimmt der Verteidiger 1 Schaden. Dann werden alle Würfelergebnisse negiert."""
         "Plasma Torpedoes":
-            text: """Attack (target lock): Spend your target lock and discard this card to perform this attack.

If this attack hits, after dealing damage, remove 1 shield token from the defender."""
+            name: "Plasma Torpedos"
+            text: """Angriff (Zielerfassung): Gib deinen Zielerfassungsmarker aus und lege diese Karte ab, um diesen Angriff durchzuführen.

Falls dieser Angriff trifft, entferne 1 Schildmarker des Verteidigers, nachdem du ihm Schaden zugefügt hast."""
         "Ion Bombs":
-            text: """When you reveal your maneuver dial, you may discard this card to drop 1 ion bomb token.

This token detonates at the end of the Activation phase.

Ion Bombs Token: When this bomb token detonates, each ship at Range 1 of the token receives 2 ion tokens.  Then discard this token."""
+            name: "Ionenbomben"
+            text: """Sobald du dein Manöverrad aufdeckst, darfst du diese Karte ablegen, um 1 Ionenbombenmarker zu legen.

 Der Marker detoniert am Ende der Aktivierungsphase."""
         "Conner Net":
-            text: """Action: Discard this card to drop 1 Conner Net token.

When a ship's base or maneuver template overlaps this token, this token detonates.

Conner Net Token: When this bomb token detonates, the ship that moved through or overlapped this token suffers 1 damage, receives 2 ion tokens, and skips its "Perform Action" step.  Then discard this token."""
+            name: "Connernetz"
+            text: """Aktion: Lege diese Karte ab, um 1 Connernetzmarker zu legen.

 Der Marker detoniert, sobald sich eine Schiffsbasis oder Manöverschablone mit dem Marker überschneidet."""
         "Bombardier":
-            text: """When dropping a bomb, you may use the (%STRAIGHT% 2) template instead of the (%STRAIGHT% 1) template."""
-        "Cluster Mines":
-            text: """Action: Discard this card to drop 3 cluster mine tokens.

When a ship's base or maneuver template overlaps a cluster mine token, that token detonates.

Cluster Mines Tokens: When one of these bomb tokens detonates, the ship that moved through or overlapped this token rolls 2 attack dice and suffers all damage (%HIT%) rolled.  Then discard this token."""
+            name: "Bombenschütze"
+            text: """Sobald du eine Bombe legst, darfst du die (%STRAIGHT% 2)-Schablone anstatt der (%STRAIGHT% 1)-Schablone verwenden."""
         'Crack Shot':
-            text: '''When attacking a ship inside your firing arc, you may discard this card to cancel 1 of the defender's %EVADE% results.'''
+            name: "Meisterhafter Schuss"
+            text: '''Sobald du ein Schiff innerhalb deines Feuerwinkels angreifst, darfst du diese Karte ablegen um 1 gewürfeltes %EVADE% des Verteidigers zu negieren.'''
         "Advanced Homing Missiles":
-            text: """Attack (target lock): Discard this card to perform this attack.%LINEBREAK%If this attack hits, deal 1 faceup Damage card to the defender.  Then cancel all dice results."""
+            name: "Verstärkte Lenkraketen"
+            text: """Angriff (Zielerfassung): Lege diese Karte ab, um diesen Angriff durchzuführen.%LINEBREAK%Falls dieser Angriff triffst, teile dem Verteidiger 1 offene Schadenskarte zu. Dann werden alle Würfelergebnisse negiert."""
         'Agent Kallus':
-            text: '''%IMPERIALONLY%%LINEBREAK%At the start of the first round, choose 1 enemy small or large ship.  When attacking or defending against that ship, you may change 1 of your %FOCUS% results to a %HIT% or %EVADE% result.'''
+            text: '''%DE_IMPERIALONLY%%LINEBREAK%At the start of the first round, choose 1 enemy small or large ship.  When attacking or defending against that ship, you may change 1 of your %FOCUS% results to a %HIT% or %EVADE% result.'''
         'XX-23 S-Thread Tracers':
             text: """Attack (focus): Discard this card to perform this attack.  If this attack hits, each friendly ship at Range 1-2 of you may acquire a target lock on the defender.  Then cancel all dice results."""
         "Tractor Beam":
             text: """Attack: Attack 1 ship.%LINEBREAK%If this attack hits, the defender receives 1 tractor beam token.  Then cancel all dice results."""
         "Cloaking Device":
-            text: """%SMALLSHIPONLY%%LINEBREAK%Action: Perform a free cloak action.%LINEBREAK%At the end of each round, if you are cloaked, roll 1 attack die.  On a %FOCUS% result, discard this card, then decloak or discard your cloak token."""
+            text: """%DE_SMALLSHIPONLY%%LINEBREAK%Action: Perform a free cloak action.%LINEBREAK%At the end of each round, if you are cloaked, roll 1 attack die.  On a %FOCUS% result, discard this card, then decloak or discard your cloak token."""
         "Shield Technician":
-            text: """%HUGESHIPONLY%%LINEBREAK%When you perform a recover action, instead of spending all of your energy, you can choose any amount of energy to spend."""
+            name: "Schildtechniker"
+            text: """%DE_HUGESHIPONLY%%LINEBREAK%Sobald du die Aktion Aufladen durchführst, kannst du wählen, wie viel Energie du ausgeben möchtest, anstatt alle Energie auszugeben."""
         "Weapons Guidance":
             text: """When attacking, you may spend a focus token to change 1 of your blank results to a %HIT% result."""
         "BB-8":
@@ -904,7 +986,7 @@
         'Cool Hand':
             text: '''When you receive a stress token, you may discard this card to assign 1 focus or evade token to your ship.'''
         'Juke':
-            text: '''%SMALLSHIPONLY%%LINEBREAK%When attacking, if you have an evade token, you may change 1 of the defender's %EVADE% results into a %FOCUS% result.'''
+            text: '''%DE_SMALLSHIPONLY%%LINEBREAK%When attacking, if you have an evade token, you may change 1 of the defender's %EVADE% results into a %FOCUS% result.'''
         'Comm Relay':
             text: '''You cannot have more than 1 evade token.%LINEBREAK%During the End phase, do not remove an unused evade token from your ship.'''

@@ -935,7 +1017,7 @@
             text: """immer wenn du dich enttarnst oder die Aktion Tarnen durchführst, darfst du als freie Aktion ausweichen."""
         "Advanced Cloaking Device":
             name: "Verbesserte Tarnvorrichtung"
-            text: """Nachdem du angegriffen hast, darfst du dich als freie Aktion tarnen."""
+            text: """Nur für TIE-Phantom.%LINEBREAK%Nachdem du angegriffen hast, darfst du dich als freie Aktion tarnen."""
         "Combat Retrofit":
             name: "Umrüstung für den Kampfeinsatz"
             ship: "Medium-Transporter GR-75"
@@ -955,13 +1037,16 @@
             name: "Automatische Schubdüsen"
             text: """Sobald du verteidigst und jenseits von Reichweite 2 oder außerhalb des Feuerwinkels des Angreifers bist, darfst du 1 deiner Leerseiten in ein %EVADE% ändern. Du darfst diese Karte nur ausrüsten, wenn du das %BOOST%-Aktionssymbol hast."""
         "Twin Ion Engine Mk. II":
-            text: """You may treat all bank maneuvers (%BANKLEFT% and %BANKRIGHT%) as green maneuvers."""
+            name: "Zwillings-Ionenantrieb MK. II"
+            text: """Du darfst alle Drehmanöver (%BANKLEFT% oder %BANKRIGHT%) als grüne Manöver behandeln."""
         "Maneuvering Fins":
-            text: """When you reveal a turn maneuver (%TURNLEFT% or %TURNRIGHT%), you may rotate your dial to the corresponding bank maneuver (%BANKLEFT% or %BANKRIGHT%) of the same speed."""
+            name: "Steuertragflächen"
+            text: """Sobald du ein Wendemanöver (%TURNLEFT% oder %TURNRIGHT%) aufdeckst, darfst du das entsprechende Drehmanöver (%BANKLEFT% oder %BANKRIGHT%) mit gleicher Geschwindigkeit auf deinem Manöverrad einstellen."""
         "Ion Projector":
-            text: """%LARGESHIPONLY%%LINEBREAK%After an enemy ship executes a maneuver that causes it to overlap your ship, roll 1 attack die.  On a %HIT% or %CRIT% result, the enemy ship receives 1 ion token."""
+            name: "Ionenprojektor"
+            text: """%DE_LARGESHIPONLY%%LINEBREAK%Nachdem ein feindliches Schiff ein Manöver ausgeführt hat, das zur Überschneidung mit deinem Schiff führte, wirf 1 Angriffswürfel. Bei %HIT% oder %CRIT% bekommt das feindliche Schiff 1 Ionenmarker."""
         'Integrated Astromech':
-            text: '''X-wing only.%LINEBREAK%When you are dealt a Damage card, you may discard 1 of your %ASTROMECH% Upgrade cards to discard that Damage card.'''
+            text: '''Nur für X-Wing.%LINEBREAK%When you are dealt a Damage card, you may discard 1 of your %ASTROMECH% Upgrade cards to discard that Damage card.'''

     title_translations =
@@ -1008,6 +1093,7 @@
         "Dauntless":
             text: """Nur für VT-49 Decimator.%LINEBREAK%Nach dem Ausführen eines Manövers, das zur Überschneidung mit einem anderen Schiff geführt hat, darfst du 1 freie Aktion durchführen. Dann erhältst du 1 Stressmarker."""
         "Virago":
+            ship: "SternenViper"
             text: """Nur für SternenViper.%LINEBREAK%Füge deiner Aufwertungsleiste ein %SYSTEM%- und ein %ILLICIT%-Symbol hinzu.%LINEBREAK%Du kannst diese Karte nicht ausrüsten, wenn dein Pilotenwert "3" oder niedriger ist."""
         '"Heavy Scyk" Interceptor (Cannon)':
             ship: "M3-A Abfangjäger"
@@ -1029,22 +1115,31 @@
         "Andrasta":
             text: """Nur für Firespray-31.%LINEBREAK%Füge deiner Aufwertungsleiste zwei weitere %BOMB%-Symbole hinzu."""
         "TIE/x1":
-            text: """TIE Advanced only.%LINEBREAK%Your upgrade bar gains the %SYSTEM% upgrade icon.%LINEBREAK%If you equip a %SYSTEM% upgrade, its squad point cost is reduced by 4 (to a minimum of 0)."""
+            text: """Nur für TIE Advanced.%LINEBREAK%Füge deiner Aufwertungsleiste ein %SYSTEM%-Symbol hinzu.%LINEBREAK%Wenn du eine %SYSTEM%-Aufwertung ausrüstest, sinken deren Kommandopunkte um 4 (Minimum 0)."""
+        "Hound's Tooth":
+            name: "Reisszahn"
+            text: """Nur für YV-666.%LINEBREAK%Nachdem du zerstörst worden bist, darfst du das Schiff Nashtahwelpe absetzen, bevor du von der Spielfläche entfernt wirst.%LINEBREAK%Es darf diese Runde nicht angreifen."""
         "Ghost":
-            text: """VCX-100 only.%LINEBREAK%Equip the Phantom title card to a friendly Attack Shuttle and dock it to this ship.%LINEBREAK%After you execute a maneuver, you may deploy it from your rear guides."""
+            text: """Nur für VCX-100.%LINEBREAK%Equip the Phantom title card to a friendly Attack Shuttle and dock it to this ship.%LINEBREAK%After you execute a maneuver, you may deploy it from your rear guides."""
         "Phantom":
             text: """While you are docked, the Ghost can perform primary weapon attacks from its special firing arc, and, at the end of the Combat phase, it may perform an additional attack with an equipped %TURRET%. If it performs this attack, it cannot attack again this round."""
         "TIE/v1":
-            text: """TIE Advanced Prototype only.%LINEBREAK%After you acquire a target lock, you may perform a free evade action."""
+            text: """Nur für TIE Advanced Prototype.%LINEBREAK%After you acquire a target lock, you may perform a free evade action."""
         "Mist Hunter":
-            text: """G-1A starfighter only.%LINEBREAK%Your upgrade bar gains the %BARRELROLL% Upgrade icon.%LINEBREAK%You must equip 1 "Tractor Beam" Upgrade card (paying its squad point cost as normal)."""
+            text: """Nur für G-1A starfighter.%LINEBREAK%Your upgrade bar gains the %BARRELROLL% Upgrade icon.%LINEBREAK%You must equip 1 "Tractor Beam" Upgrade card (paying its squad point cost as normal)."""
         "Punishing One":
-            text: """JumpMaster 5000 only.%LINEBREAK%Increase your primary weapon value by 1."""
+            text: """Nur für JumpMaster 5000.%LINEBREAK%Increase your primary weapon value by 1."""
         "Assailer":
-            text: """Raider-class corvette aft section only.%LINEBREAK%When defending, if the targeted section has a reinforce token, you may change 1 %FOCUS% result to a %EVADE% result."""
+            ship: "Korv. der Sturm-Klasse (Heck)"
+            name: "Sturmbringer"
+            text: """Nur für Korvetten der Sturm-Klasse (Heck).%LINEBREAK%Sobald du verteidigst und wenn die als Ziel bestimmten Sektion einen Verstärkungsmarker hat, darfst du 1 %FOCUS% in ein %EVADE% ändern."""
         "Instigator":
-            text: """Raider-class corvette aft section only.%LINEBREAK%After you perform a recover action, recover 1 additional shield."""
+            ship: "Korv. der Sturm-Klasse (Heck)"
+            name: "Hetzer"
+            text: """Nur für Korvetten der Sturm-Klasse (Heck).%LINEBREAK%Nachdem du die Aktion Aufladen durchgeführt hast, lade 1 weiteres Schild wieder auf."""
         "Impetuous":
-            text: """Raider-class corvette aft section only.%LINEBREAK%After you perform an attack that destroys an enemy ship, you may acquire a target lock."""
+            ship: "Korv. der Sturm-Klasse (Heck)"
+            name: "Ungestüm"
+            text: """Nur für Korvetten der Sturm-Klasse (Heck).%LINEBREAK%Nachdem du einen Angriff durchgeführt hast, der ein feindliches Schiff zerstört hat, darfst du ein Schiff in die Zielerfassung nehmen.."""

     exportObj.setupCardData basic_cards, pilot_translations, upgrade_translations, modification_translations, title_translations


use dom.create instead of building the markup via string concatenation

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.coffee
@@ -65,12 +65,10 @@
           # Create a new element with the same type (usually "div") and class as this element.
           # It will contain the new content.

-          newElement = """
-              <#{insertionPoint.element.tagName} class="#{insertionPoint.element.className}"
-                data-container-type="#{FRAGMENT_TYPE}">
-                #{content}
-                </#{insertionPoint.element.tagName}>
-              """
+          newElement = dom.create insertionPoint.element.tagName,
+                                  'class': insertionPoint.element.className, 'data-container-type': FRAGMENT_TYPE,
+                                  content
+

           insertionPoint.insertBefore newElement


fixed api

fixed api

--- routes.coffee
+++ routes.coffee
@@ -34,10 +34,10 @@
       path: '/api/v1/start-app/:app/:version/:name'
       action: ->
         check([@params.app, @params.version, @params.name], [String])
-        def = ApplicationDefs.findOne name: @params.app
-        if def && def.versions[@params.version]
-          console.log "#{def.key}/#{@params.version}", Meteor.settings.project, @params.name, EJSON.stringify(@request.query)
-          Cluster.startApp "#{def.key}/#{@params.version}", Meteor.settings.project, @params.name, EJSON.stringify(@request.query, indent:'').trim()
+        def = ApplicationDefs.findOne project: Meteor.settings.project, name: @params.app, version: @params.version
+        if def
+          console.log def.key, Meteor.settings.project, @params.name, EJSON.stringify(@request.query)
+          Cluster.startApp def.key, Meteor.settings.project, @params.name, EJSON.stringify(@request.query, indent:'').trim()
           @response.writeHead 200, 'Content-Type': 'application/json'
           @response.end "Application #{@params.name} (#{@params.app}:#{@params.version}) is scheduled for execution."
         else


ace: find/replace button styles

ace: find/replace button styles

--- client/app/Applications/Ace.kdapplication/acefindandreplaceview.coffee
+++ client/app/Applications/Ace.kdapplication/acefindandreplaceview.coffee
@@ -18,10 +18,12 @@
       callback     : => @findNext()

     @findNextButton = new KDButtonView
+      cssClass     : "editor-button"
       title        : "Find Next"
       callback     : => @findNext()

     @findPrevButton = new KDButtonView
+      cssClass     : "editor-button"
       title        : "Find Prev"
       callback     : => @findPrev()


Fixed issue with target.toggleClass('is-hovered') if in the meantime the navigation bar is re-rendered and .is-hovered git lost.

--- app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee
+++ app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee
@@ -12,6 +12,7 @@
       container: '.search-holder'
       headline: 'Search'
       text: 'Here you can search for ticket, customers and organizations. Use the wildcard §*§ to find everything. E. g. §smi*§ or §rosent*l§. You also can use ||double quotes|| for searching phrases §"some phrase"§.'
+      actions: []
     }
     {
       container: '.user-menu'
@@ -53,9 +54,6 @@

   constructor: (params) ->

-    # disable active navbar elements
-    $('.main-navigation .active').removeClass('active')
-
     $('#app').append('

')
     params.el = $('#app .js-modal--clue')

@@ -108,12 +106,12 @@
   cleanUp: (callback) ->
     @hideWindow =>
       clue = @clues[@position]
-      container = $(clue.container)
+      container = $("#app #{clue.container}")
       container.removeClass('selected-clue')

       # undo click perform by doing it again
       if clue.actions
-        @perform clue.actions, container
+        @perform clue.actions, container, 'cleanup'

       if callback
         callback()
@@ -130,11 +128,11 @@

   showClue: =>
     clue = @clues[@position]
-    container = $(clue.container)
+    container = $("#app #{clue.container}")
     container.addClass('selected-clue')

     if clue.actions
-      @perform clue.actions, container
+      @perform clue.actions, container, 'show'

     # calculate bounding box after actions
     # to take toggled child nodes into account
@@ -309,7 +307,7 @@

     dimensions

-  perform: (actions, container) ->
+  perform: (actions, container, type) ->
     for action in actions
       if action.indexOf(' ') < 0
         # 'click'
@@ -321,5 +319,14 @@
         target = container.find( action.substr action.indexOf(' ') + 1 )

       switch eventName
-        when 'click' then target.trigger('click')
-        when 'hover' then target.toggleClass('is-hovered')+        when 'click'
+          target.trigger('click')
+        when 'hover'
+
+          # disable active navbar elements
+          $('#app .navigation .is-active').removeClass('is-active')
+
+          if type is 'show'
+            target.addClass('is-hovered')
+          else
+            target.removeClass('is-hovered')


add REDISCLOUD_URL to redis-brain.coffee

--- src/scripts/redis-brain.coffee
+++ src/scripts/redis-brain.coffee
@@ -5,7 +5,7 @@
 #   "redis": "0.8.4"
 #
 # Configuration:
-#   REDISTOGO_URL or BOXEN_REDIS_URL
+#   REDISTOGO_URL or REDISCLOUD_URL or BOXEN_REDIS_URL
 #
 # Commands:
 #   None
@@ -17,7 +17,7 @@
 Redis = require "redis"

 module.exports = (robot) ->
-  info   = Url.parse process.env.REDISTOGO_URL or process.env.BOXEN_REDIS_URL or 'redis://localhost:6379'
+  info   = Url.parse process.env.REDISTOGO_URL or process.env.REDISCLOUD_URL or process.env.BOXEN_REDIS_URL or 'redis://localhost:6379'
   client = Redis.createClient(info.port, info.hostname)

   robot.brain.setAutoSave false


Remove internalCollection

Remove internalCollection

--- vendor/assets/javascripts/brainstem/data-loader.coffee
+++ vendor/assets/javascripts/brainstem/data-loader.coffee
@@ -16,7 +16,6 @@

     # Generate collection references
     @cachedCollection = @storageManager.storage @loadOptions.name
-    @internalCollection = @storageManager.createNewCollection @loadOptions.name, []
     @externalCollection = @loadOptions.collection

   _checkCache: ->
@@ -45,10 +44,10 @@
       return collection

     # If we haven't returned yet, we need to go to the server to load some missing data.
-    modelOrCollection = @internalCollection
+    modelOrCollection = @externalCollection
     modelOrCollection = @loadOptions.model if @loadOptions.only && @loadOptions.model

-    jqXhr = Backbone.sync.call @internalCollection, 'read', modelOrCollection, @_buildSyncOptions()
+    jqXhr = Backbone.sync.call @externalCollection, 'read', modelOrCollection, @_buildSyncOptions()

     if @loadOptions.returnValues
       @loadOptions.returnValues.jqXhr = jqXhr
@@ -119,8 +118,7 @@
     collection.setLoaded true

   _success: (options, data) ->
-    @_updateCollection(@internalCollection, data)
-    @_updateCollection(@externalCollection, @internalCollection)
+    @_updateCollection(@externalCollection, data)
     options.success(@externalCollection) if options.success?

 ####################################


server fixes

server fixes

--- server.coffee
+++ server.coffee
@@ -44,11 +44,11 @@
     socket.gameid = false
     lobby.emit('netrunner', {type: "games", games: games})
   for k, v of games
-    delete games[k] if (v.players.length + v.spectators.length) is 1 and not v.started and (new Date() - v.date) > 1800000
+    delete games[k] if (v.players.length + v.spectators.length) < 2 and (new Date() - v.date) > 3600000

 joinGame = (socket, gameid) ->
   game = games[gameid]
-  if game and game.players.length < 2 and not game.players.some((player) -> player.user.username is socket.request.user.username)
+  if game and game.players.length < 2
     side = if game.players.length is 1 then swapSide(game.players[0].side) else "Corp"
     game.players.push({user: socket.request.user, id: socket.id, side: side})
     socket.join(gameid)


refactored server.coffee to use wrappers

--- server.coffee
+++ server.coffee
@@ -103,6 +103,23 @@
         res.writeHead 200, status: 'ok'
         res.end html

+# wrapper to require current_user
+require_login = (req, res, next) ->
+  if not req.current_user?
+    res.writeHead 401, status: 'login_error'
+    res.end 'not logged in'
+    return
+  else
+    next(req, res)
+# wrapper generator to require current_user, but also direct them to a login page with a nice message
+# message: string or optional
+require_login_nice = (message) ->
+  return (req, res, next) ->
+    if not req.current_user?
+      render_layout "login.jade", {message: message or 'You need to login to do that'}, req, res
+    else
+      next(req, res)
+
 server = utils.Rowter([

   ['/static/:filepath', (req, res) ->
@@ -186,12 +203,8 @@
         res.simpleJSON(200, watching)
   ]

-  ['/r/:id/delete', (req, res) ->
-    console.log "qwe"
-    if not req.current_user?
-      res.writeHead 401, status: 'login_error'
-      res.end 'not logged in'
-      return
+  [wrappers: [require_login],
+   '/r/:id/delete', (req, res) ->
     switch req.method
       when 'POST'
         rid = req.path_data.id
@@ -207,11 +220,8 @@
             res.end html
   ]

-  ['/r/:id/reply', (req, res) ->
-    if not req.current_user?
-      res.writeHead 401, status: 'login_error'
-      res.end 'not logged in'
-      return
+  [wrappers: [require_login],
+   '/r/:id/reply', (req, res) ->
     switch req.method
       when 'GET'
         parent_id = req.path_data.id
@@ -267,7 +277,8 @@
             res.end html
   ]

-  ['/r/:key/recv', (req, res) ->
+  [wrappers: [require_login],
+   '/r/:key/recv', (req, res) ->
     switch req.method
       when 'GET'
         key = req.path_data.key
@@ -282,11 +293,8 @@
           logic.sessions.touch_session(key, req.current_user.username)
   ]

-  ['/r/:id/upvote', (req, res) ->
-    if not req.current_user?
-      res.writeHead 401, status: 'login_error'
-      res.end 'not logged in'
-      return
+  [wrappers: [require_login],
+   '/r/:id/upvote', (req, res) ->
     switch req.method
       when 'POST'
         rid = req.path_data.id
@@ -335,10 +343,8 @@
           res.redirect req.url
   ]

-  ['/submit', (req, res) ->
-    if not req.current_user?
-      render_layout "login.jade", {message: 'You need to log in to submit'}, req, res
-      return
+  [wrappers: [require_login_nice('You need to log in to submit')],
+   '/submit', (req, res) ->
     switch req.method
       when 'GET'
         render_layout "submit.jade", {headerbar_text: 'Submit', type: (req.query_data.type or 'link')}, req, res
@@ -493,12 +499,9 @@
         res.redirect '/'
   ]

-  ['/refer', (req, res) ->
+  [wrappers: [require_login],
+   '/refer', (req, res) ->
     # refer someone to the network
-    if not req.current_user?
-      res.writeHead 401, status: 'login_error'
-      res.end 'not logged in'
-      return
     switch req.method
       when 'GET'
         render_layout "refer.jade", {}, req, res
@@ -582,52 +585,46 @@
                   #pass
   ]

-  [
-    '/__pubsub__', (req, res) ->
-      switch req.method
-        when 'GET'
-          console.log "challenge accepted"
-          res.writeHead 200
-          res.write req.query_data['hub.challenge']
-          res.end()
-        when 'POST'
-          res.writeHead 200
-          require('./logic/diffbot').process_response(req.post_data)
-          res.end()
-  ]
-
-  [
-    '/admin/tracker', (req, res) ->
-      switch req.method
-        when 'GET'
-          mongo.diffbot.find {}, {sort: [['timestamp', -1]], limit: 20}, (err, cursor) ->
-            cursor.toArray (err, diffs) ->
-              if err
-                console.log err
-              render_layout "admin/diffs.jade", {diffs: diffs}, req, res
-  ]
-
-  [
-    '/admin/tracker/subscriptions', (req, res) ->
-      if not req.current_user?
-        res.writeHead 401, status: 'login_error'
-        res.end 'not logged in'
-        return
-      switch req.method
-        when 'GET'
-          logic.diffbot.get_subscriptions (err, subscriptions) ->
-            render_layout "admin/subscriptions.jade", {subscriptions: subscriptions}, req, res
-        when 'POST'
-          try
-            _v.check(req.post_data.url, 'url must be a valid http(s):// url.').isUrl()
-          catch e
-            render_layout "message.jade", {message: ''+e}, req, res
-            return
-          logic.diffbot.subscribe req.post_data.url, req.current_user.username, (err) ->
+  ['/__pubsub__', (req, res) ->
+    switch req.method
+      when 'GET'
+        console.log "challenge accepted"
+        res.writeHead 200
+        res.write req.query_data['hub.challenge']
+        res.end()
+      when 'POST'
+        res.writeHead 200
+        require('./logic/diffbot').process_response(req.post_data)
+        res.end()
+  ]
+
+  ['/admin/tracker', (req, res) ->
+    switch req.method
+      when 'GET'
+        mongo.diffbot.find {}, {sort: [['timestamp', -1]], limit: 20}, (err, cursor) ->
+          cursor.toArray (err, diffs) ->
             if err
-              render_layout "message.jade", {message: ''+err}, req, res
-              return
-            res.redirect "/admin/tracker/subscriptions"
+              console.log err
+            render_layout "admin/diffs.jade", {diffs: diffs}, req, res
+  ]
+
+  [wrappers: [require_login],
+   '/admin/tracker/subscriptions', (req, res) ->
+    switch req.method
+      when 'GET'
+        logic.diffbot.get_subscriptions (err, subscriptions) ->
+          render_layout "admin/subscriptions.jade", {subscriptions: subscriptions}, req, res
+      when 'POST'
+        try
+          _v.check(req.post_data.url, 'url must be a valid http(s):// url.').isUrl()
+        catch e
+          render_layout "message.jade", {message: ''+e}, req, res
+          return
+        logic.diffbot.subscribe req.post_data.url, req.current_user.username, (err) ->
+          if err
+            render_layout "message.jade", {message: ''+err}, req, res
+            return
+          res.redirect "/admin/tracker/subscriptions"
   ]
 ])


KDFramework: lazyload threshold fix

KDFramework: lazyload threshold fix

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -516,7 +516,7 @@

         ratio = (scrollTop + view.getHeight()) / scrollHeight

-        if dynamicThreshold > ratio > lastRatio
+        if dynamicThreshold < ratio > lastRatio
           @emit 'LazyLoadThresholdReached', {ratio}

         lastRatio = ratio


Fixed switches on permissions page

Fixed switches on permissions page

--- client/Dashboard/views/permissionswitch.coffee
+++ client/Dashboard/views/permissionswitch.coffee
@@ -5,3 +5,19 @@

   setDomElement:(cssClass)->
     @domElement = $ "

"
+
+  setOff :->
+    window.kdSwitch = @domElement.children(".koding-on-off")
+
+    kdSwitch.removeClass "on"
+    kdSwitch.addClass "off"
+
+    super
+
+  setOn :->
+    kdSwitch = @domElement.children(".koding-on-off")
+
+    kdSwitch.removeClass "off"
+    kdSwitch.addClass "on"
+
+    super


ComputeController: handleNewMachineRequest: pass limits, usage and plan to modal

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -376,10 +376,19 @@

     plan = @getUserPlan()

-    if plan in ['developer', 'professional', 'super']
-      new ComputePlansModal.Paid { plan }
-    else
-      new ComputePlansModal.Free { plan }
+    @fetchPlans (plans)=>
+
+      @fetchUsage provider: "koding", (err, usage)->
+
+        return  if KD.showError err
+
+        limits  = plans[plan]
+        options = { plan, limits, usage }
+
+        if plan in ['developer', 'professional', 'super']
+          new ComputePlansModal.Paid options
+        else
+          new ComputePlansModal.Free options

   triggerReviveFor:(machineId)->


Apps: Show “” instead undefined

Apps: Show “” instead undefined

--- client/Social/Apps/Views/appdetailsview.coffee
+++ client/Social/Apps/Views/appdetailsview.coffee
@@ -108,6 +108,7 @@
         Screenshots

{{> @slideShow}}

       """
+    desc = @getData().manifest?.description or ""

     """

@@ -118,7 +119,7 @@

{{#(manifest.author)}}

-
{{#(manifest.description)}}

+
#{desc}


use applications kite to download app sources

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -697,6 +697,7 @@
         return

       @kiteController.run
+        kiteName    : "applications"
         method      : "downloadApp"
         withArgs    :
           owner     : manifest.authorNick


fixed silly nested event bindings

fixed silly nested event bindings

--- client/landing/app/Applications/Environments.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/AppView.coffee
@@ -47,18 +47,18 @@
       @utils.defer =>
         @scene.once 'click', -> domainCreateForm.emit 'CloseClicked'

+    vmController = KD.getSingleton 'vmController'
+
+    vmController.on "VMPlansFetchStart", =>
+      machinesContainer.showLoader()
+
+    vmController.on "VMPlansFetchEnd", =>
+      machinesContainer.hideLoader()
+
     # Plus button on machinesContainer uses the vmController
     machinesContainer.on 'PlusButtonClicked', =>
       return unless KD.isLoggedIn()
         new KDNotificationView
           title: "You need to login to create a new machine."

-      vmController = KD.getSingleton 'vmController'
-
-      vmController.on "VMPlansFetchStart", =>
-        machinesContainer.showLoader()
-
-      vmController.on "VMPlansFetchEnd", =>
-        machinesContainer.hideLoader()
-
       vmController.createNewVM()


show notification unless err

show notification unless err

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -698,7 +698,7 @@
     group.modifyMembershipPolicy formData, (err)->
       policy.emit 'MembershipPolicyChangeSaved'  unless err
       KD.showError err
-      new KDNotificationView {title:"Membership policy has been updated."}
+      new KDNotificationView {title:"Membership policy has been updated."}  unless err

   editPermissions:(group)->
     group.getData().fetchPermissions (err, permissionSet)->


KodingClient: added RegisterHostNameAndKey router

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -260,6 +260,26 @@
     createContentHandler       = @bound 'createContentDisplayHandler'
     createStaticContentHandler = @bound 'createStaticContentDisplayHandler'

+    registerHostNameAndKey = ->
+      kite =
+        name: "kodingclient"
+        publicIP: "127.0.0.1"
+        port: "5555"
+
+      k = new NewKite(kite)
+      k.connect()
+
+      k.tell "info", (err, result) =>
+        KD.remote.api.JKodingKey.registerHostnameAndKey {
+            key:result.key
+            hostname:result.hostID
+        }, (err, res)=>
+          KD.notify_ err.message if err
+          KD.notify_ res if res
+          result.cb true
+          @clear()
+
+
     routes =

       '/'      : handleRoot
@@ -353,6 +373,8 @@
             new ReferrerModal
         else
           @handleRoute '/Login'
+
+      '/:name?/RegisterHostKey': registerHostNameAndKey

       '/member/:username': ({params:{username}})->
         @handleRoute "/#{username}", replaceState: yes


hulk default source, blank. Done with cake express-server

--- hulk.coffee
+++ hulk.coffee
@@ -1,4 +1,4 @@
-exports.compile = (source, options)->
+exports.compile = (source='', options)->
 	hogan = require 'hogan.js'
 	compiled = hogan.compile source
 	return ->


fixing a bug where the Lexer assumed that values would always be strings.

--- lexer.coffee
+++ lexer.coffee
@@ -194,7 +194,8 @@
     prev: @prev(2)
     size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
     next_character: @chunk.match(MULTI_DENT)[4]
-    no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and
+    no_newlines: next_character is '.' or
+      (@value() and @value().match and @value().match(NO_NEWLINE) and
       prev and (prev[0] isnt '.') and not @value().match(CODE))
     if size is @indent
       return @suppress_newlines() if no_newlines


Adding support for ICE track changes to calibration UI improvements

--- common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
+++ common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
@@ -471,9 +471,13 @@
       @grading_panel.find(@grading_text_sel).hide()
       @flag_student_container.hide()
       @answer_unknown_container.hide()
-
-      @feedback_area.val("Once you are done with training and are grading real student essays, you will be asked to share feedback with them in addition to grading their rubric.")
       @feedback_area.attr('disabled', true)
+      feedback_text = "Once you are done with training and are grading real student essays, you will be asked to share feedback with them in addition to grading their rubric."
+      if @tracking_changes()
+        @feedback_area.attr('contenteditable', false)
+        @feedback_area.text(feedback_text)
+      else
+        @feedback_area.val(feedback_text)
       @submit_button.show()
       @submit_button.unbind('click')
       @submit_button.click @submit_calibration_essay


Removed calling tooltip() on jQuery ready

--- vendor/assets/javascripts/qrious-ui.js.coffee
+++ vendor/assets/javascripts/qrious-ui.js.coffee
@@ -3,7 +3,3 @@
 #= require angular/angular
 #= require angular-bootstrap/ui-bootstrap-tpls
 #= require_self
-
-$ ()->
-  # Enable Tooltips
-  $("*[data-toggle='tooltip']").tooltip()


only run qpdf for the main output.pdf file

was previously matching any pdf file, which caused it to run for
embedded pdf figures produced during the mklatex run

--- services/clsi/app/coffee/OutputFileOptimiser.coffee
+++ services/clsi/app/coffee/OutputFileOptimiser.coffee
@@ -9,7 +9,7 @@
 	optimiseFile: (src, dst, callback = (error) ->) ->
 		# check output file (src) and see if we can optimise it, storing
 		# the result in the build directory (dst)
-		if src.match(/\.pdf$/)
+		if src.match(/\/output\.pdf$/)
 			OutputFileOptimiser.optimisePDF src, dst, callback
 		else
 			callback (null)


do not load proxy settings if usebackendless

--- generators/app/templates/tasks/server/server.coffee
+++ generators/app/templates/tasks/server/server.coffee
@@ -3,7 +3,7 @@
 browserSync = require 'browser-sync'
 {PROXY_CONFIG, PORT} = require '../../config/server'
 {DIST_DIRECTORY} = require '../constants'
-{open} = require '../options'
+{useBackendless, open} = require '../options'
 {FONTS, IMAGES, SCRIPTS, SOURCEMAPS, STYLES, VIEWS} = require '../extensions'

 extensions = []
@@ -31,7 +31,8 @@
 		'^.*$ /index.html [L]'
 	]

-	modRewriteConfig = PROXY_CONFIG
+	modRewriteConfig = []
+		.concat (unless useBackendless then PROXY_CONFIG else [])
 		.concat html5ModeConfig

 	browserSync


JComputeStack: make delete -> delete$ for internal usage of delete helper

--- workers/social/lib/social/models/stack.coffee
+++ workers/social/lib/social/models/stack.coffee
@@ -264,7 +264,40 @@
     daisy queue

-  delete: permit
+  delete: (owner, callback) ->
+
+    # TODO Implement delete methods.
+    @update { $set: { status: 'Terminating' } }
+
+    JProposedDomain  = require './domain'
+    JMachine = require './computeproviders/machine'
+
+    @domains?.forEach (_id) ->
+      JProposedDomain.one { _id }, (err, domain) ->
+        if not err? and domain?
+          domain.remove (err) ->
+            if err then console.error \
+              "Failed to remove domain: #{domain.domain}", err
+
+    @machines?.forEach (_id) ->
+      JMachine.one { _id }, (err, machine) ->
+        if not err? and machine?
+          machine.remove (err) ->
+            if err then console.error \
+              "Failed to remove machine: #{machine.title}", err
+
+    Relationship.remove {
+      targetName : 'JStackTemplate'
+      targetId   : @baseStackId
+      sourceId   : owner.getId()
+      sourceName : 'JAccount'
+      as         : 'user'
+    }, (err) =>
+
+      @remove callback
+
+
+  delete$: permit

     # TODO Add password check for stack delete
     #
@@ -275,37 +308,9 @@

     success: (client, callback) ->

-      # TODO Implement delete methods.
-      @update { $set: { status: 'Terminating' } }
-
-      JProposedDomain  = require './domain'
-      JMachine = require './computeproviders/machine'
-
       { delegate } = client.connection

-      @domains?.forEach (_id) ->
-        JProposedDomain.one { _id }, (err, domain) ->
-          if not err? and domain?
-            domain.remove (err) ->
-              if err then console.error \
-                "Failed to remove domain: #{domain.domain}", err
-
-      @machines?.forEach (_id) ->
-        JMachine.one { _id }, (err, machine) ->
-          if not err? and machine?
-            machine.remove (err) ->
-              if err then console.error \
-                "Failed to remove machine: #{machine.title}", err
-
-      Relationship.remove {
-        targetName : 'JStackTemplate'
-        targetId   : @baseStackId
-        sourceId   : delegate.getId()
-        sourceName : 'JAccount'
-        as         : 'user'
-      }, (err) =>
-
-        @remove callback
+      @delete owner = delegate, callback

   SUPPORTED_CREDS = (Object.keys PROVIDERS).concat ['userInput', 'custom']


Make imagemin work on the images in dist/img and make sure the task runs last so the images have been copied over by the images:dist task

--- config/plugins/imagemin.coffee
+++ config/plugins/imagemin.coffee
@@ -3,19 +3,19 @@

   files:
     imagemin:
-      cwd: 'generated/img'
-      dest: 'generated/img'
+      cwd: 'dist/img'
+      dest: 'dist/img'
       src: '{,*/}*.{png,jpg,jpeg,gif}'

   config:
     loadNpmTasks: app.loadNpmTasks.concat('grunt-contrib-imagemin')

     prependTasks:
-      dist: app.prependTasks.dist.concat('imagemin')
+      dist: app.appendTasks.dist.concat('imagemin')

     imagemin:
       dist:
         expand: true,
         cwd: '<%= files.imagemin.cwd %>'
         src: '<%= files.imagemin.src %>'
-        dest: '<%= files.imagemin.dest %>'+        dest: '<%= files.imagemin.dest %>'