testing interpolation and directives

testing interpolation and directives

--- test/unit/service.spec.coffee
+++ test/unit/service.spec.coffee
@@ -69,10 +69,15 @@
 		provider.db = {}
 	it 'should get messages from current locale', ->
 		expect(service.get('message')).toBe 'one'
+	it 'should get messages from current locale as service keys', ->
+		expect(service.message).toBe 'one'
 	it 'should get nested messages', ->
 		expect(service.get('key.with.dots')).toBe 'key.with.dots'
 		expect(service.get('nested.message')).toBe 'nested message'
 		expect(service.get('nested.nested.message')).toBe 'nested nested message'
+	it 'should get nested messages as service keys', ->
+		expect(service.nested.message).toBe 'nested message'
+		expect(service.nested.nested.message).toBe 'nested nested message'
 	it 'should be able to reference messages using @ sign', ->
 		expect(service.get('references.referencedMessage')).toBe 'This is message from "originalMessage" resource'
 		expect(service.get('references.referencedReferencedMessage')).toBe 'This is message from "originalMessage" resource'
@@ -88,6 +93,96 @@
 		expect(service.get('subs.hello2', 'name', 'name2', 'name3' )).toBe 'Hello, name and name2'
 		expect(service.get('%1 %2 %3 %4 %5 %6 %7 %8 %9 %10', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).toBe '1 2 3 4 5 6 7 8 9 10'

+describe 'l10n directives', ->
+	compile = null
+	scope = null
+	service = null
+	beforeEach ->
+		angular.module('test-directive', ['l10n', 'l10n-tools']).config (l10nProvider) ->
+			l10nProvider.add 'en-US',
+				directive:
+					dirText: 'This line is inserted using l10n-text directive'
+					dirHtml: 'This line is inserted using l10n-html directive. And it has HTML markup'
+					dirTitle: 'This title is inserted using l10n-title directive'
+					dirPlaceholder: 'This placeholder is inserted using l10n-placeholder directive'
+					dirHref: 'http://en.example.com/Localized link'
+					dirValue: 'This line is inserted using l10n-value directive'
+			l10nProvider.add 'uk-UA',
+				directive:
+					dirText: 'UAThis line is inserted using l10n-text directive'
+					dirHtml: 'UAThis line is inserted using l10n-html directive. And it has HTML markup'
+					dirTitle: 'UAThis title is inserted using l10n-title directive'
+					dirPlaceholder: 'UAThis placeholder is inserted using l10n-placeholder directive'
+					dirHref: 'http://ua.example.com/Localized link'
+					dirValue: 'UAThis line is inserted using l10n-value directive'
+
+		module 'test-directive'
+		inject ($compile, $rootScope, l10n) ->
+			l10n.setLocale 'en-US'
+			service = l10n
+			scope = $rootScope
+			compile = $compile
+	setHTML = (html) ->
+		el = angular.element(html)
+		compile(el)(scope)
+		scope.$digest()
+		return el
+
+	it 'should support l10n-text attribute', ->
+		expect(setHTML('

').html()).toBe 'This line is inserted using l10n-text directive'
+	it 'should support l10n-html attribute', ->
+		expect(setHTML('

').html()).toBe 'This line is inserted using l10n-html directive. And it has HTML markup'
+	it 'should support l10n-title attribute', ->
+		expect(setHTML('

').attr('title')).toBe 'This title is inserted using l10n-title directive'
+	it 'should support l10n-placeholder attribute', ->
+		expect(setHTML('

').attr('placeholder')).toBe 'This placeholder is inserted using l10n-placeholder directive'
+	it 'should support l10n-href attribute', ->
+		expect(setHTML('').attr('href')).toBe 'http://en.example.com/Localized link'
+	it 'should support l10n-value attribute', ->
+		expect(setHTML('<input l10n-value="directive.dirValue"/>').attr('value')).toBe 'This line is inserted using l10n-value directive'
+
+	it 'should update directive values when locale is changed', ->
+		service.setLocale 'uk-UA'
+		expect(setHTML('

').html()).toBe 'UAThis line is inserted using l10n-text directive'
+		expect(setHTML('

').html()).toBe 'UAThis line is inserted using l10n-html directive. And it has HTML markup'
+		expect(setHTML('

').attr('title')).toBe 'UAThis title is inserted using l10n-title directive'
+		expect(setHTML('

').attr('placeholder')).toBe 'UAThis placeholder is inserted using l10n-placeholder directive'
+		expect(setHTML('').attr('href')).toBe 'http://ua.example.com/Localized link'
+		expect(setHTML('<input l10n-value="directive.dirValue"/>').attr('value')).toBe 'UAThis line is inserted using l10n-value directive'
+
+describe 'l10n using interpolation', ->
+	compile = null
+	scope = null
+	beforeEach ->
+		angular.module('test-directive', ['l10n', 'l10n-tools']).config (l10nProvider) ->
+			l10nProvider.add 'en-US',
+				interpolation:
+					message: 'This message is inserted through interpolation using object property.'
+					nested:
+						message: 'this is nested message'
+			l10nProvider.add 'uk-UA',
+				interpolation:
+					message: 'UAThis message is inserted through interpolation using object property.'
+					nested:
+						message: 'UAthis is nested message'
+
+		module 'test-directive'
+		inject ($compile, $rootScope, l10n) ->
+			l10n.setLocale 'en-US'
+			scope = $rootScope
+			scope.l10n = l10n
+			compile = $compile
+	setText = (text) ->
+		el = angular.element('
' + text + '
')
+		compile(el)(scope)
+		scope.$digest()
+		return el.html()
+
+	it 'should work as service keys', ->
+		expect(setText('{{ l10n.interpolation.message }}')).toBe 'This message is inserted through interpolation using object property.'
+	it 'should work when .get method is used', ->
+		expect(setText('{{ l10n.get("interpolation.message")}}')).toBe 'This message is inserted through interpolation using object property.'
+
 describe 'provider to service', ->
 	it 'default locale can be changed using l10nProvider.setLocale', ->
 		angular.module('provider2service', ['l10n']).config (l10nProvider) ->


[minor] Remove errant console.log

[minor] Remove errant console.log

--- lib/api.coffee
+++ lib/api.coffee
@@ -72,7 +72,6 @@
       socket.on 'message', (message) ->
         logger.info 'api server ' + process.pid + ' received message: ' + message

-#        console.log socket
         try
           message = JSON.parse message
         catch err


Easy shortcut for one command plugins

Easy shortcut for one command plugins

--- lib/app.coffee
+++ lib/app.coffee
@@ -100,7 +100,11 @@
 		pluginMeta = plugin.getPluginMeta(pluginPath)
 		pluginMain = path.join(pluginPath, pluginMeta.main)
 		pluginCommands = require(pluginMain)
-		_.each(pluginCommands, capitano.command)
+
+		if _.isArray(pluginCommands)
+			_.each(pluginCommands, capitano.command)
+		else
+			capitano.command(pluginCommands)

 	catch error
 		errors.handle(error)


Changed testpie colors

Changed testpie colors


Get rid of jQuery in add_observation script.

--- assets/js/add_observation.coffee
+++ assets/js/add_observation.coffee
@@ -1,5 +1,3 @@
-$ = require("jquery")
-
-$ ->
-    current_jd = +$("#current-jd").text()
-    $("#id_jd").val(current_jd)
+document.addEventListener "DOMContentLoaded", ->
+    current_jd = +document.getElementById("current-jd").textContent
+    document.getElementById("id_jd").value = current_jd


Prevent re-inviting already invited members

--- assets/js/home/membership.coffee
+++ assets/js/home/membership.coffee
@@ -221,7 +221,7 @@
   addMember: (event) =>
     event.preventDefault()
     @validateEmail(event)
-    email = @$("#add_email").val()
+    email = $.trim(@$("#add_email").val())
     role = @$("#add_role").val()
     voting = @$("#add_voting").is(":checked")
     email_td = @$("#add_email").parent()
@@ -232,7 +232,7 @@
     else if _.find(@group.members, (m) => @users[m.user]?.email == email)?
       email_td.addClass("error")
       flash "info", "That user is already a member."
-    else if _.find(@group.invited_members, (m) -> m.email == email)?
+    else if _.find(@group.invited_members, (m) => @users[m.user]?.email == email)?
       email_td.addClass("error")
       flash "info", "That user has already been invited."
     else


Server: Tests: extend url subdomain

Server: Tests: extend url subdomain

--- servers/testhelper/handler/ssotokenloginhelper.coffee
+++ servers/testhelper/handler/ssotokenloginhelper.coffee
@@ -1,4 +1,5 @@
 { generateUrl
+  deepObjectExtend
   generateRandomString
   generateRequestParamsEncodeBody } = require '../index'

@@ -6,8 +7,8 @@
 generateSsoTokenLoginRequestParams = (opts = {}) ->

   params =
-    url     : generateUrl { route : '-/api/ssotoken/login' }
-    body    : { token : generateRandomString() }
+    url : generateUrl deepObjectExtend { route : '-/api/ssotoken/login' }, opts.url
+    qs  : { token : generateRandomString() }

   requestParams = generateRequestParamsEncodeBody params, opts


webapp/buttonbar: inheritence → inheritance -- issue #2640

--- src/smc-webapp/buttonbar.coffee
+++ src/smc-webapp/buttonbar.coffee
@@ -845,7 +845,7 @@

                     print(MyClass(5))
                     """
-        class_inheritence :
+        class_inheritance :
             insert: """
                     class A(object):
                         def __repr__(self):
@@ -1673,7 +1673,7 @@
             ["Lambda", "#lambda", "A Python lambda function"]
             ["Classes"],
             ["Class", "#simple_class", "Define a simple class"],
-            ["Class with inheritence", "#class_inheritence", "A class that inherits from other classes"]
+            ["Class with inheritance", "#class_inheritance", "A class that inherits from other classes"]
         ]]

     add_menu(pybar, py_func)


add back Icon

add back Icon

--- src/smc-webapp/desktop_app.cjsx
+++ src/smc-webapp/desktop_app.cjsx
@@ -24,7 +24,7 @@
 {React, ReactDOM, rclass, redux, rtypes, Redux, redux_fields} = require('./app-framework')

 {Button, Navbar, Nav, NavItem} = require('react-bootstrap')
-{ErrorBoundary, Loading, Space, Tip}   = require('./r_misc')
+{ErrorBoundary, Loading, Space, Tip, Icon}   = require('./r_misc')
 {COLORS} = require('smc-util/theme')
 misc_page = require('./misc_page')


Add name to top of block

Add name to top of block

--- src/smc-webapp/editor_chat.cjsx
+++ src/smc-webapp/editor_chat.cjsx
@@ -110,15 +110,25 @@
     propTypes:
         # example message object
         # {"sender_id":"f117c2f8-8f8d-49cf-a2b7-f3609c48c100","event":"chat","payload":{"content":"l"},"date":"2015-08-26T21:52:51.329Z"}
-        message    : rtypes.object.isRequired  # immutable.js message object
-        account_id : rtypes.string.isRequired
-        user_map   : rtypes.object
-        project_id : rtypes.string    # optional -- improves relative links if given
-        file_path  : rtypes.string    # optional -- (used by renderer; path containing the chat log)
-        font_size  : rtypes.number
+        message        : rtypes.object.isRequired  # immutable.js message object
+        account_id     : rtypes.string.isRequired
+        sender_name    : rtypes.string
+        user_map       : rtypes.object
+        project_id     : rtypes.string    # optional -- improves relative links if given
+        file_path      : rtypes.string    # optional -- (used by renderer; path containing the chat log)
+        font_size      : rtypes.number
+        show_avatar    : rtypes.bool
+        is_prev_sender : rtypes.bool
+        is_next_sender : rtypes.bool

     shouldComponentUpdate: (next) ->
-        return @props.message != next.message or @props.user_map != next.user_map or @props.account_id != next.account_id
+        return @props.message != next.message or
+               @props.user_map != next.user_map or
+               @props.account_id != next.account_id or
+               @props.show_avatar != next.show_avatar or
+               @props.is_prev_sender != next.is_prev_sender or
+               @props.is_next_sender != next.is_next_sender or
+               ((not @props.is_prev_sender) and (@props.sender_name != next.sender_name))

     sender_is_viewer: ->
         @props.account_id == @props.message.get('sender_id')
@@ -128,15 +138,24 @@
             pull = "pull-right small"
         else
             pull = "pull-left small"
-

-            <TimeAgo date={new Date(@props.message.get('date'))} />
+        if @props.show_avatar
+

+                <TimeAgo date={new Date(@props.message.get('date'))} />
+

+
+    show_user_name: ->
+

+            {@props.sender_name}

     avatar_column: ->
         account = @props.user_map?.get(@props.message.get('sender_id'))?.toJS()
-        if account?  # TODO: do something better when we don't know the user (or when sender account_id is bogus)
+        if account? and @props.show_avatar # TODO: do something better when we don't know the user (or when sender account_id is bogus)
+            <Col key={0} xs={1} style={display:"inline-block", verticalAlign:"middle", marginTop:'15px'}>
+                <Avatar account={account} />
+            </Col>
+        else
             <Col key={0} xs={1} style={{display:"inline-block", verticalAlign:"middle"}}>
-                <Avatar account={account} />
             </Col>

     content_column: ->
@@ -154,8 +173,13 @@

         font_size = "#{@props.font_size}px"

+        if @props.show_avatar
+            marginBottom = "20px" # the default value actually..
+        else
+            marginBottom = "5px"
         <Col key={1} xs={8}>
-            <Panel style={wordWrap:"break-word"}>
+            {@show_user_name() if not @props.is_prev_sender and not @sender_is_viewer()}
+            <Panel style={wordWrap:"break-word", marginBottom: marginBottom}>
                 <ListGroup fill>
                     <ListGroupItem style={background:color; fontSize: font_size}>
                         <Markdown value={value}
@@ -170,8 +194,11 @@
     blank_column:  ->
         <Col key={2} xs={3}></Col>

+    #delete_message: ->
+    #    @props.redux.getActions(@props.name).set_input("test")
+
     render: ->
-        cols = [ @avatar_column(), @content_column(), @blank_column()]
+        cols = [@avatar_column(), @content_column(), @blank_column()]
         # mirror right-left for sender's view
         if @sender_is_viewer()
             cols = cols.reverse()
@@ -194,18 +221,40 @@
         return @props.messages != next.messages or @props.user_map != next.user_map or @props.account_id != next.account_id

     list_messages: ->
-        v = {}
-        @props.messages.map (mesg, date) =>
-            v[date] = <Message key={date}
-                        account_id = {@props.account_id}
-                        user_map   = {@props.user_map}
-                        message    = {mesg}
-                        project_id = {@props.project_id}
-                        file_path  = {@props.file_path}
-                        font_size  = {@props.font_size}
-                      />
-        k = misc.keys(v).sort(misc.cmp_Date)
-        return (v[date] for date in k)
+        is_next_message_sender = (index, list, map) ->
+            if index + 1 == list.length
+                return false
+            current_message = map.get(list[index])
+            next_message = map.get(list[index + 1])
+            return current_message.get('sender_id') == next_message.get('sender_id')
+
+        is_prev_message_sender = (index, list, map) ->
+            if index == 0
+                return false
+            current_message = map.get(list[index])
+            prev_message = map.get(list[index - 1])
+            return current_message.get('sender_id') == prev_message.get('sender_id')
+
+        k = @props.messages.keySeq().sort(misc.cmp_Date).toJS()
+        v = []
+        for date, i in k
+            sender_account = @props.user_map.get(@props.messages.get(date).get('sender_id'))
+            sender_name = sender_account.get('first_name') + ' ' + sender_account.get('last_name')
+
+            v.push(<Message key={date}
+                     account_id  = {@props.account_id}
+                     user_map    = {@props.user_map}
+                     message     = {@props.messages.get(date)}
+                     project_id  = {@props.project_id}
+                     file_path   = {@props.file_path}
+                     font_size   = {@props.font_size}
+                     is_prev_sender   = {is_prev_message_sender(i, k, @props.messages)}
+                     is_next_sender   = {is_next_message_sender(i, k, @props.messages)}
+                     show_avatar      = {not is_next_message_sender(i, k, @props.messages)}
+                     sender_name      = {sender_name}
+                    />)
+
+        return v

     render: ->

@@ -219,7 +268,7 @@
         "#{name}" :
             messages : rtypes.immutable
             input    : rtypes.string
-            position : rtypes.string
+            position : rtypes.number
         users :
             user_map : rtypes.immutable
         account :
@@ -359,7 +408,7 @@
                         <Button onClick={@show_timetravel} bsStyle='info' style={float:'right'}>
                             <Icon name='history'/> TimeTravel
                         </Button>
-                        <Button onClick={@scroll_to_bottom} bsStyle='info' style={float:'right'}>
+                        <Button onClick={@scroll_to_bottom} bsStyle='success' style={float:'right'}>
                             <Icon name='arrow-down'/> Scroll to Bottom
                         </Button>
                     </ButtonToolbar>


Default VDI names and descriptions

Default VDI names and descriptions

--- app/modules/new-vm/index.coffee
+++ app/modules/new-vm/index.coffee
@@ -379,6 +379,10 @@

         # Adds the device number based on the index.
         VDI.device = "#{index}"
+
+        # Default VDI name and description
+        VDI.name_label = VDI.name_label || name_label + '_disk' + index
+        VDI.name_description = VDI.name_description || 'Created by XO'

         # TODO: handles invalid values.


Only allow one current classification per workflow

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -2,6 +2,7 @@
 apiClient = require '../../api/client'
 TitleMixin = require '../../lib/title-mixin'
 HandlePropChanges = require '../../lib/handle-prop-changes'
+PromiseToSetState = require '../../lib/promise-to-set-state'
 animatedScrollTo = require 'animated-scrollto'
 Classifier = require '../../classifier'

@@ -10,92 +11,87 @@
 if SKIP_CELLECT
   console?.warn 'Intelligent subject selection disabled'

-classificationsInProgress =
-  byProject: {}
-  byWorkflow: {}
+window.currentWorkflowForProject = {} # Project ID to promised workflow ID, used when no workflow is specified

-upcomingSubjects =
-  byWorkflow: {}
+window.currentClassifications =
+  forWorkflow: {} # Workflow ID to a promise for its current classification resource
+
+window.upcomingSubjects =
+  forWorkflow: {}

 module.exports = React.createClass
   displayName: 'ProjectClassifyPage'

-  mixins: [TitleMixin, HandlePropChanges]
+  mixins: [TitleMixin, HandlePropChanges, PromiseToSetState]

   title: 'Classify'

   getInitialState: ->
-    target: {}
     workflow: null
     subject: null
     classification: null

   propChangeHandlers:
-    project: (project) ->
-      @setTarget {project}
+    project: 'loadClassification'
+    query: 'loadClassification'

-    'query.workflow': (workflowID) ->
-      @setTarget {workflowID}
+  loadClassification: (_, props = @props) ->
+    console.log 'Loading classification!'
+    getWorkflowID = if props.query?.workflow?
+      Promise.resolve props.query.workflow
+    else
+      console.log 'Workflow for project', currentWorkflowForProject[props.project.id]
+      currentWorkflowForProject[props.project.id] ?= @getRandomWorkflowID props.project
+      currentWorkflowForProject[props.project.id]

-  setTarget: (newTarget) ->
-    for key, value of newTarget
-      @state.target[key] = value
-    @loadClassification()
+    @promiseToSetState classification: getWorkflowID.then (workflowID) =>
+      console.log 'Classification for workflow', currentClassifications.forWorkflow[workflowID]
+      currentClassifications.forWorkflow[workflowID] ?= @createNewClassification props.project, workflowID
+      currentClassifications.forWorkflow[workflowID]

-  loadClassification: ->
-    {project, workflowID} = @state.target
-
-    if workflowID?
-      [from, id] = ['byWorkflow', workflowID]
-    else
-      [from, id] = ['byProject', project.id]
-
-    if classificationsInProgress[from][id]?
-      @setState classification: classificationsInProgress[from][id]
-    else
-      @createNewClassification().then (classification) =>
-        classificationsInProgress[from][id] = classification
-        @setState classification: classificationsInProgress[from][id]
-
-  createNewClassification: (project) ->
-    {project, workflowID} = @state.target
-
-    getWorkflow = project.link('workflows').then (workflows) ->
+  getRandomWorkflowID: (project) ->
+    project.link('workflows').then (workflows) ->
       if workflows.length is 0
-        throw new Error "Project has no workflows."
-      else if workflowID?
-        workflow = (workflow for workflow in workflows when workflow.id is workflowID)[0]
-        unless workflow?
-          throw new Error "Could not find workflow #{workflowID}"
-        workflow
+        throw new Error "No workflows for project #{project.id}"
       else
         randomIndex = Math.floor Math.random() * workflows.length
-        workflows[randomIndex]
+        workflows[randomIndex].id
+
+  createNewClassification: (project, workflowID) ->
+    console.log 'createNewClassification()', arguments
+    getWorkflow = project.link('workflows').then (workflows) ->
+      workflow = (workflow for workflow in workflows when workflow.id is workflowID)[0]
+      unless workflow?
+        throw new Error "No workflow #{workflowID} for project #{project.id}"
+      console.log 'Got workflow', workflow
+      workflow

     getSubject = getWorkflow.then (workflow) ->
-      upcomingSubjects.byWorkflow[workflow.id] ?= []
+      upcomingSubjects.forWorkflow[workflow.id] ?= []

-      unless upcomingSubjects.byWorkflow[workflow.id].length is 0
-        subject = upcomingSubjects.byWorkflow[workflow.id].shift()
+      unless upcomingSubjects.forWorkflow[workflow.id].length is 0
+        subject = upcomingSubjects.forWorkflow[workflow.id].shift()

-      if upcomingSubjects.byWorkflow[workflow.id].length is 0
+      if upcomingSubjects.forWorkflow[workflow.id].length is 0
         fetchSubjects = apiClient.type('subjects').get({
           project_id: project.id
           workflow_id: workflow.id
           sort: 'cellect' unless SKIP_CELLECT
         }).then (subjects) ->
-          upcomingSubjects.byWorkflow[workflow.id].push subjects...
+          upcomingSubjects.forWorkflow[workflow.id].push subjects...

         subject ?= fetchSubjects.then ->
-          if upcomingSubjects.byWorkflow[workflow.id].length is 0
+          if upcomingSubjects.forWorkflow[workflow.id].length is 0
             # TODO: If this fails during a random workflow, pick the next workflow.
             throw new Error 'No more subjects'
           else
-            upcomingSubjects.byWorkflow[workflow.id].shift()
+            upcomingSubjects.forWorkflow[workflow.id].shift()

+      console.log 'Got subject', subject
       subject

     Promise.all([getWorkflow, getSubject]).then ([workflow, subject]) ->
+      console.log 'Creating classification'
       classification = apiClient.type('classifications').create
         links:
           project: project.id
@@ -122,6 +118,8 @@
           onLoad={@scrollIntoView}
           onComplete={@handleClassificationCompletion}
           onClickNext={@loadAnotherSubject} />
+      else if @state.rejected.classification?
+        {@state.rejected.classification.toString()}
       else
         Loading classification}

@@ -140,5 +138,7 @@
       console?.log 'Saved classification', @state.classification.id

   loadAnotherSubject: ->
-    classificationsInProgress.byProject[@props.project.id] = null
-    @loadClassification 'byProject', @props.project
+    currentWorkflowForProject[@props.project.id].then (workflowID) =>
+      currentClassifications.forWorkflow[workflowID] = null
+      currentWorkflowForProject[@props.project.id] = null
+    @loadClassification()


Don't render the finished banner unless the project is finished

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -70,6 +70,7 @@
     workflow: null
     subject: null
     classification: null
+    projectIsComplete: false
     demoMode: sessionDemoMode

   propChangeHandlers:
@@ -104,11 +105,13 @@
       currentWorkflowForProject[props.project.id]

   getRandomWorkflowID: (project) ->
-    project.get('workflows', active: true).then (workflows) ->
+    project.get('workflows', active: true).then (workflows) =>
       if workflows.length is 0
         throw new Error "No workflows for project #{project.id}"
         project.uncacheLink 'workflows'
       else
+        projectIsComplete = (true for workflow in workflows when not workflow.finished_at?).length is 0
+        @setState {projectIsComplete}
         randomIndex = Math.floor Math.random() * workflows.length
         # console.log 'Chose random workflow', workflows[randomIndex].id
         workflows[randomIndex].id
@@ -209,7 +212,8 @@

   render: ->
     <div className="classify-page content-container">
-      <FinishedBanner project={@props.project} />
+      {if @state.projectIsComplete
+        <FinishedBanner project={@props.project} />}

       {if @state.classification?
         <Classifier


red font for inline-errors as provided by ink

--- lib/repl.coffee
+++ lib/repl.coffee
@@ -166,7 +166,7 @@
   # Displays some result data inline. tree is a recursive structure expected to
   # be of the shape like the following.
   # ["text for display", {button options}, [childtree1, childtree2, ...]]
-  displayInline: (editor, range, tree)->
+  displayInline: (editor, range, tree, error=false)->
     end = range.end.row

     # Remove the existing view if there is one
@@ -188,8 +188,7 @@
     view = recurseTree(tree)

     # Add new inline view
-    r = new @ink.Result editor, [end, end],
-      content: view
+    r = new @ink.Result editor, [end, end], content: view, error: error

     # Adding the class here lets us apply proto repl specific styles to the display.
     r.view.classList.add 'proto-repl'
@@ -202,11 +201,13 @@
   # of content for inline display.
   makeInlineHandler: (editor, range, valueToTreeFn)->
     (result) =>
+      isError = false
       if result.value
         tree = valueToTreeFn(result.value)
       else
         tree = [result.error]
-      @displayInline(editor, range, tree)
+        isError = true
+      @displayInline(editor, range, tree, isError)

   inlineResultHandler: (result, options)->
     # Alpha support of inline results using Atom Ink.


Small refacto

Small refacto

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -1,75 +1,73 @@
-queuedEvents = {}
-queuedTracks = {}
-initConfig = null
-userSuccessFn = ->
-userFailureFn = ->
+_pool = {}
+_setup = null
+_extend = null
 currentFlavour = null
-_extend = null
+
+# Helpers to manage the calls to functions defined from the start
+# event if they are not available
+createPool = (name)->
+  _pool[name]  = {} unless _pool[name]
+  _p = _pool[name]
+  (evt, params)->
+    _p[evt] ?= []
+    _p[evt].push params
+deletePool = (name)->
+  delete _pool[name]

 preInit = (config, cb, errb)->
-  throw 'Hull.init has already been called' if initConfig
+  throw 'Hull.init has already been called' if _setup

   # Prepare config
   config.namespace = 'hull'
   config.debug = config.debug && { enable: true }
-  initConfig = config
-  userSuccessFn = cb if cb
-  userFailureFn = errb if errb

-  bootstrap() if currentFlavour
+  _setup =
+    config: config
+    userSuccessFn: cb or ->
+    userFailureFn: errb or ->

-# Pools the calls made to Hull.on before init
-preInitOn = (evt, fn)->
-  queuedEvents[evt] ?= []
-  queuedEvents[evt].push fn
-
-# Pools the calls made to Hull.track before init
-preInitTrack = (evtName, params)->
-  queuedTracks[evt] ?= []
-  queuedTracks[evt].push params
+  bootstrap()

 _hull = window.Hull =
-  on:      preInitOn
-  track:   preInitTrack
+  on:      createPool('events')
+  track:   createPool('tracks')
   init:    preInit

 # Wraps the success callback
+# * Extends the global object
 # * Reinjects events in the live app from the pool
 # * Replays the track events
-buildSuccessCb = (flavourSuccessFn, userSuccessFn)->
-  (args...)->
-    extension = flavourSuccessFn(args...)
-    _hull = window.Hull = _extend(_hull, extension)
-    # Prune callback queue
-    for evt, cbArray of queuedEvents
-      for cb of cbArray
-        _hull.events.on evt, cb
-    queuedEvents = []
+successCb = (args...)->
+  extension = currentFlavour.success(args...)
+  _hull = window.Hull = _extend(_hull, extension)
+  # Prune callback queue
+  for evt, cbArray of _pool['events']
+    for cb of cbArray
+      _hull.events.on evt, cb
+  deletePool('events')

-    # Prune init queue
-    userSuccessFn(_hull)
+  # Prune init queue
+  _setup.userSuccessFn(_hull)

-    booted.track(evt, params) for evt, param of queuedTracks
-    queuedTracks = []
+  booted.track(evt, params) for evt, param of _pool['tracks']
+  deletePool('tracks')

-    _hull
+  _hull

 # Wraps the failure callback
 # * Executes the failure behaviour defined by the current flavour
-buildFailureCb = (flavourFailureFn, userFailureFn)->
-  (args...)->
-    flavourFailureFn(args...)
-    userFailureFn(args...)
+failureCb = (args...)->
+  currentFlavour.failure(args...)
+  _setup.userFailureFn(args...)

 bootstrap = ()->
-  success = buildSuccessCb(currentFlavour.success, userSuccessFn)
-  failure = buildFailureCb(currentFlavour.failure, userFailureFn)
-  currentFlavour.condition(initConfig).then(success, failure)
+  return unless _setup and currentFlavour
+  currentFlavour.condition(_setup.config).then(successCb, failureCb)

 require ['flavour', 'underscore', 'lib/utils/version'], (flavour, _, version)->
   _hull.version = version
   _extend = _.extend
   currentFlavour = flavour
-  bootstrap(initConfig) if initConfig
+  bootstrap()


Fix typo in variable name, languageSelect settingsPanel

--- coffee/_Settings.class.coffee
+++ coffee/_Settings.class.coffee
@@ -54,7 +54,7 @@
           @close()
         ,
         'Save': =>
-          window.l10n.changeLang(@LanguageSelect.val())
+          window.l10n.changeLang(@languageSelect.val())
           @close()

   reposition: ->


fixed missing translation for TIE Interceptor and R5 Astromech. Also changed TYP to Typ.

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -49,8 +49,8 @@
         '.translate.sort-cards-by': 'Sortiere Karten per'
         '.xwing-card-browser option[value="name"]': 'Name'
         '.xwing-card-browser option[value="source"]': 'Quelle'
-        '.xwing-card-browser option[value="type-by-points"]': 'TYP (Punkte)'
-        '.xwing-card-browser option[value="type-by-name"]': 'TYP (Name)'
+        '.xwing-card-browser option[value="type-by-points"]': 'Typ (Punkte)'
+        '.xwing-card-browser option[value="type-by-name"]': 'Typ (Name)'
         '.xwing-card-browser .translate.select-a-card': 'Wähle eine Karte aus der Liste links.'
     singular:
         'pilots': 'Pilot'
@@ -138,8 +138,8 @@
                 "Barrel Roll",
                 "Evade",
             ]
-        "TIE Interceptor":
-            name: "TIE Interceptor"
+        "TIE Abfangjäger":
+            name: "TIE Abfangjäger"
             faction: "Galactic Empire"
             attack: 3
             agility: 3
@@ -1069,7 +1069,7 @@
             slot: "Astromech"
             sources: [ "X-Wing Expansion Pack", ]
             points: 1
-            text: """During the End phase, you may choose 1 of your faceup Damage cards with the Ship trait and flip it facedown."""
+            text: """Wähle während der Endphase 1 deiner offnen Schadenskarte mit dem Attribut Schiff und drehe sie um."""
         "Entschlossenheit":
             name: "Entschlossenheit"
             id: 8


Put Advanced SLAM in the correct section.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1037,9 +1037,6 @@
         "Advanced Homing Missiles":
             name: "Misiles Rastreadores Avanzados"
             text: """Ataque (Blanco fijado): Descarta esta carta para efectuar este ataque.%LINEBREAK%Si el ataque impacta, inflinge 1 carta de Daño boca arriba al defensor. Luego se anulan todos los resultados de los dados."""
-        "Advanced SLAM":
-            name: "Motor Sublumínico Avanzado"
-            text: """Después de efectuar una acción de MASA, si no te has solapado con un obstáculo ni con otra nave, puedes llevar a cabo una acctión gratuita."""
         '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.'''
         'XX-23 S-Thread Tracers':
@@ -1130,6 +1127,9 @@
         "Ion Projector":
             name: "Técnico de Escudos"
             text: """%LARGESHIPONLY%%LINEBREAK%Después de que una nave enemiga ejecute una maniobra que la solape con tu nave, tira 1 dado de ataque. Si sacas %HIT% o %CRIT%, la nave enemiga recibe 1 ficha de Iones."""
+        "Advanced SLAM":
+            name: "Motor Sublumínico Avanzado"
+            text: """Después de efectuar una acción de MASA, si no te has solapado con un obstáculo ni con otra nave, puedes llevar a cabo una acctión gratuita."""

     title_translations =
         "Slave I":


change port

change port

--- server/init/index.coffee
+++ server/init/index.coffee
@@ -1,6 +1,6 @@
 app = require '../app'

-app.set 'port', process.env.PORT or 3000
+app.set 'port', process.env.PORT or 3002

 server = app.listen app.get('port'), ->
   console.log "Express server listening on port #{server.address().port}"


remove reference to popout view #oops

remove reference to popout view #oops

--- app/scripts/views/navbar.coffee
+++ app/scripts/views/navbar.coffee
@@ -1,4 +1,4 @@
-define ['whichbus', 'views/popout', 'bootstrap/bootstrap-dropdown'], (WhichBus, PopoutView) ->
+define ['whichbus', 'bootstrap/bootstrap-dropdown'], (WhichBus, PopoutView) ->
 	WhichBus.Views.Navbar = Backbone.View.extend
 		template: 'navbar'


:lipstick: Improve stdout behavior, git path settings

If no exit callback is given the wrapper automatically captures
each stdout stream and returns the complete stdout when complete.

--- lib/git.coffee
+++ lib/git.coffee
@@ -2,17 +2,29 @@
 StatusView = require './views/status-view'

 # Public: Execute a git command.
-#
-# args     - The {Array} containing the arguments to pass.
-# c_stdout - The {Function} to pass the stdout to.
-# c_exit   - The {Function} to pass the exit code to.
+# {Object}
+#   :args    - The {Array} containing the arguments to pass.
+#   :options - The {Object} with options to pass.
+#      :cwd  - Current working directory as {String}.
+#   :stdout  - The {Function} to pass the stdout to.
+#   :exit    - The {Function} to pass the exit code to.
 #
 # Returns: `undefined`
 gitCmd = ({args, options, stdout, stderr, exit}={}) ->
-  command = getGitPath()
+  command = _getGitPath()
   options ?= {}
   options.cwd ?= dir()
   stderr ?= (data) -> new StatusView(type: 'alert', message: data.toString())
+
+  if stdout? and not exit?
+    c_stdout = stdout
+    stdout = (data) ->
+      @save ?= ''
+      @save += data
+    exit = (exit) ->
+      if exit is 0
+        c_stdout @save ?= ''
+        @save = null

   new BufferedProcess
     command: command
@@ -33,15 +45,16 @@
     args: ['diff-files', '--name-status', '-z']
     stdout: (data) ->
       if showUntracked
-        gitUntrackedFiles(c_stdout, _prettify(data))
+        gitUntrackedFiles(stdout, _prettify(data))
       else
-        c_stdout _prettify(data)
+        stdout _prettify(data)
   )

 gitUntrackedFiles = (stdout, dataUnstaged=[]) ->
   gitCmd(
     args: ['ls-files', '-o', '--exclude-standard','-z']
-    stdout: (data) -> stdout dataUnstaged.concat(_prettifyUntracked(data))
+    stdout: (data) ->
+      stdout dataUnstaged.concat(_prettifyUntracked(data))
   )

 gitDiff = (stdout, path) ->
@@ -55,12 +68,16 @@
     args: ['add', '--refresh', '.']
   )

+_getGitPath = ->
+  atom.config.get('git-plus.gitPath') ? 'git'
+
 _prettify = (data) ->
   data = data.split('\0')[...-1]
   files = [] = for mode, i in data by 2
     {mode: mode, path: data[i+1]}

 _prettifyUntracked = (data) ->
+  return [] if not data?
   data = data.split('\0')[...-1]
   files = [] = for file in data
     {mode: '?', path: file}


Pass node name, not component to runtime middleware

--- components/OpenProject.coffee
+++ components/OpenProject.coffee
@@ -101,7 +101,7 @@
         break

       if type is 'runtime'
-        ctx.remote.push currentGraph
+        ctx.remote.push nodeId
         continue

       ctx.graphs.push currentGraph


escaping the project name

escaping the project name

--- src/circle-ci.coffee
+++ src/circle-ci.coffee
@@ -98,7 +98,8 @@
       .headers("Accept": "application/json")
       .get() handleResponse msg, (response) ->
         for project in response
-          clearProjectCache(msg, endpoint, project.reponame)
+          projectname = escape(toProject(project.reponame))
+          clearProjectCache(msg, endpoint, projectname)

 checkToken = (msg) ->
   unless process.env.HUBOT_CIRCLECI_TOKEN?


use real module classes for testing publish / require

--- tests/unit/module.unit.coffee
+++ tests/unit/module.unit.coffee
@@ -10,23 +10,21 @@
   describe '@publish', ->

     it 'adds given module to the static collection of published modules', ->
-      fakeModule = identifier = 'test'
-      Space.Module.publish fakeModule, fakeModule.identifier
-      expect(Space.Module.published[fakeModule.identifier]).to.equal fakeModule
+      module = Space.Module.define 'test'
+      expect(Space.Module.published['test']).to.equal module

     it 'throws an error if two modules try to publish under same name', ->
       publishTwoModulesWithSameName = ->
-        Space.Module.publish {}, 'test'
-        Space.Module.publish {}, 'test'
+        Space.Module.define 'test'
+        Space.Module.define 'test'
       expect(publishTwoModulesWithSameName).to.throw Error

   describe '@require', ->

     it 'returns published module for given identifier', ->
-      fakeModule = identifier = 'test'
-      Space.Module.publish fakeModule, fakeModule.identifier
-      requiredModule = Space.Module.require fakeModule.identifier
-      expect(requiredModule).to.equal fakeModule
+      module = Space.Module.define 'test'
+      requiredModule = Space.Module.require 'test'
+      expect(requiredModule).to.equal module

     it 'throws and error if no module was registered for given identifier', ->
       requireUnkownModule = -> Space.Module.require 'unknown module'


Delete gog-lib.coffee

Delete gog-lib.coffee

--- gog-lib.coffee
+++ gog-lib.coffee
@@ -1,461 +0,0 @@
-class Data
-
-Data::process = (obj) ->
-
-  for i in obj
-    if i.hasOwnProperty('format') and i.format.type == "csv"
-      i.values =  dl.csv(i.values, i.format)
-      lib.GoG_JSON[i.name] = i.values
-    if i.hasOwnProperty('format') and i.format.type == "json"
-      i.values =  dl.json(i.values)
-      lib.GoG_JSON[i.name] = i.values
-    if i.hasOwnProperty('format') and i.format.type == "tsv"
-      i.values =  dl.tsv(i.values, i.format)
-      lib.GoG_JSON[i.name] = i.values
-    else
-      lib.GoG_JSON[i.name] = i.values
-
-class Transform
-
-
-#             {
-#               "lang": "R",    // function language
-#               "function": "fibonnaci", // function name
-#                "properties": {
-#                  "data" : "static",
-#                  "length" : 20,
-#                  "field" : "x",
-#                 "name": "fibonnaci_x"
-#                }
-#          }
-
-Transform::process = (obj) ->
-  for i in obj
-    if i.hasOwnProperty("lang") and i.lang == "R"
-      Transform.prototype["R_" + i.function](i.properties)
-    else
-      Transform.prototype[i.function](i.properties)
-
-
-
-
-        #j {log_x: Math.log(j.x)    ,x:5}
-
-
-
-
-Transform::pow = (obj)->
-  dataTable = lib.GoG_JSON[obj.data]
-  power = obj.power
-  field = obj.field
-  variable = obj.name
-
-  for j in dataTable
-    for key, value of j
-      j[variable] = Math.pow(j[field], power)
-
-
-
-Transform::R_fibonnaci = (obj) ->
-  dataTable = lib.GoG_JSON[obj.data]
-  variable = obj.name
-  field = obj.field
-  length = obj.length
-  library = obj.library
-  data = {n : length, sequence: true}
-
-  ocpu.seturl "http://localhost:5888/ocpu/library/"+library+"/R"
-
-  ocpu.rpc "fibonacci", data , (output) ->
-    m = 0
-    for j in dataTable
-      j[variable] = output[m]
-      m++
-
-  return
-
-
-
-
-
-
-class Scale
-
-
-
-  ##{
-##           "name": "xscale", // scale name
-##            "type": "linear", // scale type
-##            "range": "width", // chart width
-##            "domain": {
-##                "data": "static",
-##                "field": "x"
-##            }
-##        }
-#  {"type" : "linear", "domain" : [1000,4000], "range" : [35,350]}
-  #scale = (obj) ->
-  #    scaleobj = d3.scale[obj.type]()
-  #    scaleobj = scaleobj["domain"](obj.domain)
-  #    scaleobj = scaleobj["range"](obj.range)
-  #    console.log scaleobj(2000)
-
-
-Scale::process = (obj)->
-
-  dataArray = []
-  for i in obj
-    for j in lib.GoG_JSON[i.domain.data]
-      for key,value of j
-        if(key == i.domain.field)
-          dataArray.push(value)
-        else
-          continue
-    domainArray = [d3.min(dataArray), d3.max(dataArray)]
-    rangeArray = []
-    if Array.isArray(i.range)
-      rangeArray = i.range
-    if i.range == "height"
-      minRange = lib.GoG_JSON[i.range] - lib.GoG_JSON.margin
-      rangeArray.push(minRange)
-      maxRange = lib.GoG_JSON.margin
-      rangeArray.push(maxRange)
-    else
-      minRange = lib.GoG_JSON.margin
-      rangeArray.push(minRange)
-      maxRange = lib.GoG_JSON[i.range] - lib.GoG_JSON.margin
-      rangeArray.push(maxRange)
-
-    scaleObj =
-      type : i.type
-      domain : domainArray
-      range : rangeArray
-
-    scaleArray = Scale::scale(scaleObj)
-
-    for c in lib.GoG_JSON[i.domain.data]
-      for key1,value1 of c
-        c[i.name] = scaleArray(c[i.domain.field])
-
-    dataArray = []
-    domainArray = []
-  return
-
-
-
-
-Scale::scale = (obj) ->
-    scaleobj = d3.scale[obj.type]()
-    scaleobj = scaleobj["domain"](obj.domain)
-    scaleobj = scaleobj["range"](obj.range)
-
-#"axes": [
-#//            {
-#//                "type": "x", // x axis
-#//                "data": "static"// scale for axis
-#//                  "field": "x"
-#//         "orient" : "bottom"
-#           "grid" : true
-#//            },
-#//            {
-#//                "type": "y", // y axis
-#//                "scale": "yscale"// scale for axis
-#//            }
-#//        ]
-
-class axes
-
-
-
-
-axes::process = (obj) ->
-  axesArray = []
-  for i in obj
-    axesObj = {}
-    dataArray = []
-    for j in lib.GoG_JSON[i.data]
-      for key,value of j
-        if(key == i.field)
-          dataArray.push(value)
-        else
-          continue
-
-    dataArray.sort(d3.ascending)
-#    dataArray = dataArray.filter( onlyUnique )
-    if i.hasOwnProperty("ticks")
-      axesObj["values"] = new Array(i.ticks)
-    else
-      axesObj["values"] = dataArray
-    axesObj["min"] = d3.min(dataArray)
-    axesObj["max"] = d3.max(dataArray)
-    axesObj["orientation"] = i.orient
-    axesObj["grid"] = i.grid
-    axesObj["margin"] = lib.GoG_JSON["margin"]
-    axesObj["height"] = lib.GoG_JSON["height"]
-    axesObj["width"] = lib.GoG_JSON["width"]
-    axesObj["type"] = i.type
-    axesArray.push(axesObj)
-
-
-  lib.Canvas_JSON["Axes"] = axesArray
-
-
-onlyUnique = ( value, index, self)->
-  return self.indexOf(value) is index
-
-class Geom
-
-#"geom" : [
-#  {
-#    "type" : "Point",
-#    "data" : "static",
-#    "properties" : {
-#      "x" : "xscale",
-#      "y" : "yscale",
-#      "fillColor" : "zscale"
-#    }
-#  }
-#]
-
-Geom::process = (obj) ->
-
-  for i in obj
-    for j in lib.GoG_JSON[i.data]
-      geomObj = {}
-      if i.type == "Point"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          j[i.properties.color]
-        geomObj["fillColor"] = if (i.properties.fillColor is null || i.properties.fillColor is undefined )
-          "#ffffff"
-        else
-          j[i.properties.fillColor]
-        geomObj["linewidth"] = if (i.properties.linewidth is null || i.properties.linewidth is undefined )
-          1
-        else
-          j[i.properties.linewidth]
-        geomObj["radius"] = if (i.properties.radius is null || i.properties.radius is undefined )
-          7
-        else
-          j[i.properties.radius]
-        geomObj["anticlockwise"] = if (i.properties.anticlockwise is null || i.properties.anticlockwise is undefined )
-          false
-        else
-          j[i.properties.anticlockwise]
-        geomObj["startAngle"] = if (i.properties.startAngle is null || i.properties.startAngle is undefined )
-          0
-        else
-          j[i.properties.startAngle]
-        geomObj["endAngle"] = if (i.properties.endAngle is null || i.properties.endAngle is undefined )
-          360
-        else
-          j[i.properties.endAngle]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Picture"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["src"] = j[i.properties.src]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Diamond"
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          j[i.properties.color]
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Text"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["text"] = j[i.properties.text]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-
-
-    for c, index in lib.GoG_JSON[i.data]
-      if index > 0
-        geomObj = {}
-        if i.type == "Line"
-          geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-            "#000000"
-          else
-            lib.GoG_JSON[i.data][index-1][i.properties.color]
-          geomObj["width"] = if (i.properties.width is null || i.properties.width is undefined )
-            1
-          else
-            lib.GoG_JSON[i.data][index-1][i.properties.width]
-
-          geomObj["startX"] = lib.GoG_JSON[i.data][index-1][i.properties.x]
-          geomObj["startY"] = lib.GoG_JSON[i.data][index-1][i.properties.y]
-          geomObj["endX"] = lib.GoG_JSON[i.data][index][i.properties.x]
-          geomObj["endY"] = lib.GoG_JSON[i.data][index][i.properties.y]
-
-          if lib.Canvas_JSON.hasOwnProperty(i.type)
-            lib.Canvas_JSON[i.type].push(geomObj)
-          else
-            lib.Canvas_JSON[i.type] = []
-            lib.Canvas_JSON[i.type].push(geomObj)
-
-
-    pointsArray = []
-    geomObj = {}
-    for j, c in lib.GoG_JSON[i.data]
-      if i.type == "Area"
-        arr = []
-        arr.push(j[i.properties.x])
-        arr.push(j[i.properties.y])
-        pointsArray.push(arr)
-        geomObj["points"] = pointsArray
-        geomObj["fillColor"] = if (i.properties.fillColor is null || i.properties.fillColor is undefined )
-          "rgba(200, 170, 170, 0.8)"
-        else
-          i.properties.fillColor
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          i.properties.color
-        geomObj["margin"] = lib.GoG_JSON["margin"]
-        geomObj["height"] = lib.GoG_JSON["height"]
-        geomObj["width"] = lib.GoG_JSON["width"]
-        if c == lib.GoG_JSON[i.data].length-1
-          if lib.Canvas_JSON.hasOwnProperty(i.type)
-            lib.Canvas_JSON[i.type].push(geomObj)
-          else
-            lib.Canvas_JSON[i.type] = []
-            lib.Canvas_JSON[i.type].push(geomObj)
-
-
-
-  return
-
-class Define
-
-
-
-Define::process = (obj)->
-  for i in obj
-    window.eval i
-##{"type" : "linear", "domain" : [1000,4000], "range" : [35,350]}
-#scale = (obj) ->
-#    scaleobj = d3.scale[obj.type]()
-#    scaleobj = scaleobj["domain"](obj.domain)
-#    scaleobj = scaleobj["range"](obj.range)
-#    console.log scaleobj(2000)
-
-
-
-`//specification = {
-//    "id" : "canvas_1"
-//    "width": 600,
-//    "height": 600,
-//    "margin" : 35,
-//    "background": "transparent",
-//    "data": [
-//        {
-//            "name": "static",
-//            "values": [
-//                {"x": 10, "y": 20},
-//                {"x": 30, "y": 20},
-//                {"x": 40, "y": 10},
-//                {"x": 20, "y": 30},
-//                {"x": 20, "y": 50},
-//                {"x": 5, "y": 60}
-//            ]
-//
-//        }, {
-//            "name": "fromUrl",
-//            "url": "test.csv",
-//            "format": "csv"
-//        }
-//    ],
-//    "transform": [
-//        {
-//            "name": "log_x",    // new variable name
-//            "function": "log", // function name
-//            "data": "static", // data source
-//            "field": "x"     // column name
-//        }
-//    ],
-//    "scales": [
-//        {
-//            "name": "xscale", // scale name
-//            "type": "linear", // scale type
-//            "range": "width", // chart width
-//            "domain": {
-//                "data": "static",
-//                "field": "x"
-//            }
-//        },
-//        {
-//            "name": "yscale", //scale name
-//            "type": "linear", // scale type
-//            "range": "height", // chart height
-//            "domain": {
-//                "data": "static", // data source
-//                "field": "y" // data field
-//            }
-//        }
-//    ],
-//    "axes": [
-//        {
-//            "type": "x", // x axis
-//            "scale": "xscale"// scale for axis
-//        },
-//        {
-//            "type": "y", // y axis
-//            "scale": "yscale"// scale for axis
-//        }
-//    ]
-//
-//}`
-
-`var lib = {}`
-GoG_Parser = (obj) ->
-
-  lib.GoG_JSON = {}
-  lib.Canvas_JSON = {}
-  lib.Canvas_JSON.id = obj.id
-  lib.Canvas_JSON.Background = obj.background
-  lib.GoG_JSON.width = obj.width
-  lib.GoG_JSON.height = obj.height
-  lib.GoG_JSON.margin = obj.margin
-  lib.data = new Data()
-  lib.transform = new Transform()
-  lib.scales = new Scale()
-  lib.axes = new axes()
-  lib.geom = new Geom()
-  lib.define = new Define()
-  for key,value of obj
-    if key == "width" || key =="height" || key == "margin" || key == "background" || key == "id"
-      continue
-    else
-      lib[key]["process"](value)
-
-
-  Canvas_Parse(lib.Canvas_JSON)
-


Expect terminator after do while loop

Expect terminator after do while loop

--- grammar.coffee
+++ grammar.coffee
@@ -118,7 +118,8 @@
       "do",
       @statement_block,
       "while",
-      @_paren_expression)
+      @_paren_expression,
+      terminator())

     try_statement: -> seq(
       "try",


Fixed Draggable on Safari and mobile Safari

webkitMovementX and webkitMovementY are not currently supported in the version of webkit that ships with Safari (OS X 10.9.2) and mobile Safari (iOS 7.1).

Reverted the position delta tracking (as seen in this commit) to the manual calculation instead of relying on the webkitMovement properties.

--- src/ui/draggable.coffee
+++ src/ui/draggable.coffee
@@ -11,102 +11,102 @@
 # Make any view draggable

 class exports.Draggable extends EventEmitter
-
+
 	@VelocityTimeOut = 100
-
+
 	constructor: (@view) ->
-
+
 		@speed = {x:1.0, y:1.0}
-
+
 		@_deltas = []
 		@_isDragging = false
-
+
 		@attach()
-
+
 	attach: -> @view.on  Events.TouchStart, @_touchStart
 	remove: -> @view.off Events.TouchStart, @_touchStart
-
+
 	emit: (eventName, event) ->
 		# We override this to get all events both on the draggable
 		# and the encapsulated view.
 		@view.emit eventName, event
-
+
 		super eventName, event
-
-
+
+
 	calculateVelocity: ->
-
+
 		if @_deltas.length < 2
 			return {x:0, y:0}
-
+
 		curr = @_deltas[-1..-1][0]
 		prev = @_deltas[-2..-2][0]
 		time = curr.t - prev.t
-
+
 		# Bail out if the last move updates where a while ago
 		timeSinceLastMove = (new Date().getTime() - prev.t)

 		if timeSinceLastMove > @VelocityTimeOut
 			return {x:0, y:0}
-
+
 		velocity =
 			x: (curr.x - prev.x) / time
 			y: (curr.y - prev.y) / time
-
+
 		velocity.x = 0 if velocity.x is Infinity
 		velocity.y = 0 if velocity.y is Infinity
-
+
 		velocity
-
+
 	_updatePosition: (event) =>
-
+
 		@emit Events.DragMove, event

 		touchEvent = Events.touchEvent event

-		delta =
-			x: touchEvent.webkitMovementX
-			y: touchEvent.webkitMovementY
-
+		delta =
+			x: touchEvent.clientX - @_start.x
+ -			y: touchEvent.clientY - @_start.y
+
 		# Correct for current drag speed
-		correctedDelta =
+		correctedDelta =
 			x: delta.x * @speed.x
 			y: delta.y * @speed.y
 			t: event.timeStamp
-
-		@view.x += correctedDelta.x
-		@view.y += correctedDelta.y
-
+
+		@view.x = @_start.x + correctedDelta.x - @_offset.x
+ -		@view.y = @_start.y + correctedDelta.y - @_offset.y
+
 		@_deltas.push correctedDelta
-
-	_touchStart: (event) =>
-
+
+	_touchStart: (event) =>
+
 		@view.animateStop()
-
+
 		@_isDragging = true
-
+
 		touchEvent = Events.touchEvent event
-
+
 		@_start =
 			x: touchEvent.clientX
 			y: touchEvent.clientY
-
-		@_offset =
+
+		@_offset =
 			x: touchEvent.clientX - @view.x
 			y: touchEvent.clientY - @view.y
-
+
 		document.addEventListener Events.TouchMove, @_updatePosition
 		document.addEventListener Events.TouchEnd, @_touchEnd
-
+
 		@emit Events.DragStart, event
-
-	_touchEnd: (event) =>
-
+
+	_touchEnd: (event) =>
+
 		@_isDragging = false
-
+
 		document.removeEventListener Events.TouchMove, @_updatePosition
 		document.removeEventListener Events.TouchEnd, @_touchEnd
-
+
 		@emit Events.DragEnd, event
-
+
 		@_deltas = []


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -221,7 +221,7 @@
             xw: target.width/2
             yw: target.height/2
         else
-          error()
+          error ""
           return

       xw = target.xw


Fixed page bug

Fixed page bug

--- resources/assets/scripts/app/directives/paged-tracks-list.coffee
+++ resources/assets/scripts/app/directives/paged-tracks-list.coffee
@@ -54,10 +54,6 @@
             $scope.newClickOverride = (id) ->
                 $scope.clickOverride({id: id})

-            tracks.loadFilters().then(
-                tracks.mainQuery.fromFilterString($state.params.filter)
-            )
-
             typeEnum = switch
                 when $scope.type == 'normal' then tracks.FetchType.NORMAL
                 when $scope.type == 'all' then tracks.FetchType.ALL


Updated info

Updated info

--- t+.coffee
+++ t+.coffee
@@ -13,17 +13,19 @@
 # For general usage, see the `t.js` [docs](https://www.github.com/jasonmoo/t.js).

 # `t+.coffee` additions:
-# ----------------------

 # * Partials & inheritance
 #   * Include: `{{&<name>}}`.
-#   * Extend, replacing named blocks: `{{^parent}}{{#block1}}content{{/block1}}{{#block2}}content 2{{/block2}}`
+#   * Extend, replacing named blocks:
+#
+#           `{{^parent}}{{#block1}}content{{/block1}}{{#block2}}content 2{{/block2}}`
+#
 #     * Extend block `{{^<name>}}` must be first block in template
 #     * Content between blocks in child template will be ignored
 #   * Interpreted templates cached for fast re-render
-# * Simple template management via `t.register(name, stringTemplateOrT)` and `t.load(name)`
-#   * Extensible - simply overwrite `t.register` & `t.load` with your own logic and return an instance of `t`
-#   * Register functions should expect `(name, stringTemplateOrT)`, while loader functions should accept `name`
+# * Simple template management via `t.put(name, stringTemplateOrT)` and `t.load(name)`
+#   * Extensible - simply overwrite `t.put` & `t.load` with your own logic and return an instance of `t`
+#   * Register functions should expect `(name, template)`, while loader functions should accept `name`
 # * Macros
 #   * Inline: `{{+<name>(param1, param2)}}`. If an error occurs, returns `''`.
 #   * Block: `{{+<name>(param1, param2...)}} <default content> {{/<name>}}`. If an error occurs, returns `<default content>`.


increase timeout for long-running acceptance tests

--- services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee
+++ services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee
@@ -14,6 +14,8 @@
 	fs.mkdirSync(fixturePath("tmp"))
 catch err
 	console.log err, fixturePath("tmp"), "unable to create fixture tmp path"
+
+MOCHA_LATEX_TIMEOUT = 60 * 1000

 convertToPng = (pdfPath, pngPath, callback = (error) ->) ->
 	command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}"
@@ -109,6 +111,7 @@
 					@project_id = Client.randomId() + "_" + example_dir

 				it "should generate the correct pdf", (done) ->
+					this.timeout(MOCHA_LATEX_TIMEOUT)
 					Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) =>
 						if error || body?.compile?.status is "failure"
 							console.log "DEBUG: error", error, "body", JSON.stringify(body)
@@ -116,6 +119,7 @@
 						downloadAndComparePdf(@project_id, example_dir, pdf.url, done)

 				it "should generate the correct pdf on the second run as well", (done) ->
+					this.timeout(MOCHA_LATEX_TIMEOUT)
 					Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) =>
 						if error || body?.compile?.status is "failure"
 							console.log "DEBUG: error", error, "body", JSON.stringify(body)


update point: git

update point: git

--- data/points/git.coffee
+++ data/points/git.coffee
@@ -2,6 +2,7 @@
 title: "Git"
 function: [ "vcs" ]
 relevance: [ "github" ]
+homepage: "http://git-scm.com"
 desc: "Git是一个开源的分布式版本控制系统，用以有效、高速的处理从很小到非常大的项目版本管理。"
 reference:
   "Pro Git": "https://github.com/progit/progit/tree/master/zh"


Disable JS minify temporarily on RC

Disable JS minify temporarily on RC

--- config/main.production.coffee
+++ config/main.production.coffee
@@ -85,9 +85,9 @@
   presence      :
     exchange    : 'services-presence'
   client        :
-    pistachios  : yes
+    pistachios  : no
     version     : version
-    minify      : yes
+    minify      : no
     watch       : no
     js          : "./website/js/kd.#{version}.js"
     css         : "./website/css/kd.#{version}.css"


fix(container): additional empty check for editables

--- src/container.coffee
+++ src/container.coffee
@@ -17,7 +17,8 @@
     Observable self, '__updated'

     # For external access for first_class containers
-    self.editables = options.editable
+    self.editables = []
+    self.editables = options.editable if options.editable?

     # Include selectable functions
     if options.selectable? && options.selectable == true


Refactor

Refactor

--- machina.coffee
+++ machina.coffee
@@ -111,6 +111,21 @@
         resource_path = "/#{@options.api_version}" + resource_path if @options.api_version
         resource_path = "/#{@options.url_prefix}" + resource_path if @options.url_prefix

+        multi_response = (res, success_code, error_code) ->
+          (err, result) ->
+            [results, errors] = result
+
+            response = {}
+            response.errors = errors unless errors.isEmpty()
+            response[resource] = results
+
+            if errors.isEmpty()
+              res.send(success_code, response)
+            else if response[resource].every(null)
+              res.send(error_code, response)
+            else
+              res.send(207, response)
+
         resource_GET = (req, res) =>
           @find resource, null, req.machina, (err, results) ->
             if err
@@ -150,19 +165,7 @@
               memo[0].push(null)
               callback(null, memo)

-          async.reduce req.body, [[],[]], createItem, (err, result) ->
-            [results, errors] = result
-
-            response = {}
-            response.errors = errors unless errors.isEmpty()
-            response[resource] = results
-
-            if errors.isEmpty()
-              res.send(201, response)
-            else if response[resource].every((x) -> x == null)
-              res.send(422, response)
-            else
-              res.send(207, response)
+          async.reduce req.body, [[],[]], createItem, multi_response(res, 201, 422)

         resource_OPTIONS = (req, res) =>
           response = Object.clone(config.schema, true)
@@ -300,21 +303,7 @@
               if err
                 res.send(500)
               else
-                async.reduce results.zip(req.body, keys), [[],[]], updateItem, (err, result) ->
-                  [results, errors] = result
-
-                  response = {}
-                  response.errors = errors unless errors.isEmpty()
-                  response[resource] = results
-
-                  if errors.isEmpty()
-                    res.send(200, response)
-                  else if response[resource].every((x) -> x == null)
-                    res.send(422, response)
-                  else
-                    res.send(207, response)
-
-            res.send(200)
+                async.reduce results.zip(req.body, keys), [[],[]], updateItem, multi_response(res, 200, 422)
           else
             res.json(400, {errors: validation_result.errors})

@@ -346,19 +335,7 @@
               memo[0].push(null)
               callback(null, memo)

-          async.reduce keys.zip(req.body), [[], []], updateItem, (err, result) ->
-            [results, errors] = result
-
-            response = {}
-            response.errors = errors unless errors.isEmpty()
-            response[resource] = results
-
-            if errors.isEmpty()
-              res.send(200, response)
-            else if response[resource].every((x) -> x == null)
-              res.send(422, response)
-            else
-              res.send(207, response)
+          async.reduce keys.zip(req.body), [[], []], updateItem, multi_response(res, 200, 422)

         item_DELETE = (req, res) =>
           keys = req.params.lookup.split(",")


Rework game loop

Rework game loop

Don't compute game state and physics independent from each other, this
leads otherwise to weird debugging behavior where the objects move
weigh to far before they can check their position.

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -40,7 +40,6 @@

     now = Date.now()
     @lastPhysicsUpdate = now
-    @lastGameUpdate    = now
     @lastRenderUpdate  = now

     @subscribeEvent 'changeLvl', =>
@@ -87,13 +86,10 @@
     lvl = mediator.getActiveLevel()

     while @lastPhysicsUpdate < timeNow
+      @handleInput()
       lvl.updatePhysics()
+      lvl.update()
       @lastPhysicsUpdate += PHYSICS_LOOP
-
-    while @lastGameUpdate < timeNow
-      @handleInput()
-      lvl.update()
-      @lastGameUpdate += GAME_LOOP

     while @lastRenderUpdate < timeNow
       @draw()


Don't copy falsy values when creating a dropdown

--- dropdown.coffee
+++ dropdown.coffee
@@ -26,7 +26,8 @@
         if Match.test prop, Match.OneOf('x', 'y', 'top', 'left')
           obj[prop] = toIntOr opts[prop], obj[prop]
         else
-          obj[prop] = opts[prop]
+          if opts[prop] and _.isUndefined(opts[prop]) is false
+            obj[prop] = opts[prop]

     return obj


Reset key administration when pausing

Reset key administration when pausing

--- src/components/controls/AnalogKeyboardControls.coffee
+++ src/components/controls/AnalogKeyboardControls.coffee
@@ -118,7 +118,14 @@
         ship.ax += (ACCELLERATE_X * 2)

     @listenTo ship, 'KeyDown', (e) ->
-      Game.togglePause() if e.key is controlMap.pause
+      if e.key is controlMap.pause
+        Game.togglePause()
+        pressed = {
+          up: false,
+          down: false,
+          left: false,
+          right: false
+        }
       return if ship.disableControls
       ship.shoot(true) if e.key is controlMap.fire
       ship.switchWeapon(true) if e.key is controlMap.switchWeapon


Refactor in event handler (new name for events, )

--- source/core/event.coffee
+++ source/core/event.coffee
@@ -22,7 +22,7 @@
     events = events.split(' ')
     calls = @hasOwnProperty('events') and @events or = {}
     for event in events
-      event = @_parseName event
+      event = @_customEventName event
       calls[event] or = []
       calls[event].push callback

@@ -33,7 +33,7 @@
   @param  {function}  The function that is to be no longer executed.
   ###
   unbind: (event, callback) ->
-    event = @_parseName event
+    event = @_customEventName event
     if @hasOwnProperty('events') and @events?[event]
       @events[event].splice @events[event].indexOf(callback), 1

@@ -46,7 +46,7 @@
                       handler.
   ###
   trigger: (event, args...) ->
-    event = @_parseName event
+    event = @_customEventName event
     events = @hasOwnProperty('events') and @events?[event]
     return unless events
     args.push @
@@ -62,12 +62,10 @@
   ###
   bubble: (event, args...) ->
     if @parent?.uid?
-      constructor = if args.length is 1 then @constructor else args[1].constructor
-      callbackName = "bubble#{constructor.name.toClassName()}#{event.toClassName()}"
-
+      callbackName = @_eventCallbackName event, "bubble", args
       args.push @
       # Dispatch event to parent
-      @parent[callbackName]?.apply(@parent, args)
+      @parent[callbackName]?.apply @parent, args
       # Bubble event
       @parent.bubble.apply @parent, [event].concat(args)

@@ -80,15 +78,11 @@
   ###
   tunnel: (event, args...) ->
     if @childrenClass?.length > 0
-      if args.length is 1
-        callbackName = "event_#{@constructor.name}_#{event}"
-      else
-        callbackName = "event_#{args[1].constructor.name}_#{event}"
-
+      callbackName =  @_eventCallbackName event, "tunnel", args
       args.push @
       for child in @childrenClass
         # Dispatch event to child
-        child[callbackName.toLowerCase()]?.apply(child, args)
+        child[callbackName]?.apply child, args
         # Tunnel event
         child.tunnel.apply child, [event].concat(args)

@@ -100,11 +94,20 @@
   @param  {array}     List of events to subscribe.
   ###
   bindList: (instance, events) ->
-    class_lower = instance.constructor.name.toLowerCase()
+    class_base = @_classBase instance.constructor
     for event in events
-      callback_name = "#{class_lower}#{event.toClassName()}"
-      instance.bind event, @[callback_name]
+      callback_name = "#{class_base.toLowerCase()}#{event.toClassName()}"
+      if @[callback_name] then instance.bind event, @[callback_name]

   # Private Methods
-  _parseName: (event) ->
-    ("#{@constructor.type}:#{@constructor.name}:#{event}").toLowerCase()
+  _customEventName: (event) ->
+    class_base = @_classBase @constructor
+    ("#{@constructor.type}:#{class_base}:#{event}").toLowerCase()
+
+  _eventCallbackName: (event, type, args) ->
+    constructor = if args.length is 1 then @constructor else args[1].constructor
+    class_base = @_classBase constructor
+    callbackName = "#{type}#{class_base.toClassName()}#{event.toClassName()}"
+
+  _classBase: (constructor) ->
+    constructor.base or constructor.name


TextInput impl fixed

TextInput impl fixed

--- impl/css/level0/text/input.coffee
+++ impl/css/level0/text/input.coffee
@@ -5,8 +5,6 @@
 module.exports = (impl) ->
 	DATA =
 		isMultiLine: false
-		autoWidth: false
-		autoHeight: false

 	DATA: DATA

@@ -15,28 +13,32 @@
 	create: (data) ->
 		self = @

-		data.elem ?= document.createElement 'textarea'
-		data.elem.setAttribute 'class', 'text'
-		data.textElem = data.elem
-		data.textElemStyle = data.textElem.style
+		impl.Types.Text.create.call @, data
+		{textElem} = data

-		impl.Types.Item.create.call @, data
+		textElem.setAttribute 'contenteditable', 'true'
+		data.textElemStyle.overflow = 'auto'

-		data.elem.addEventListener impl._SIGNALS.pointerWheel, (e) ->
-			if document.activeElement is @
+		data.elemStyle.cursor = 'text'
+
+		data.elem.addEventListener impl._SIGNALS.pointerOnWheel, (e) ->
+			if document.activeElement is textElem
 				e.stopPropagation()
 			return

-		data.elem.addEventListener 'input', ->
-			if not data.isMultiLine and ///\n///.test(@value)
-				@value = @value.replace ///\n///g, ''
-			self.text = @value
+		textElem.addEventListener 'input', ->
+			if not data.isMultiLine and ///\n///.test(textElem.textContent)
+				textElem.textContent = textElem.textContent.replace ///\n///g, ''
+			self.text = textElem.textContent
 			return

-		data.elem.addEventListener 'focus', ->
+		data.elem.addEventListener 'click', ->
+			textElem.focus()
+
+		textElem.addEventListener 'focus', ->
 			self.keys.focus = true

-		data.elem.addEventListener 'blur', ->
+		textElem.addEventListener 'blur', ->
 			self.keys.focus = false

 		impl.setItemWidth.call @, 200
@@ -44,13 +46,6 @@

 		return

-	setText: do (_super = impl.setText) -> (val) ->
-		if @_impl.isMultiLine isnt undefined
-			@_impl.elem.value = val
-		else
-			_super.call @, val
-		return
-
 	setTextInputMultiLine: (val) ->
 		@_impl.isMultiLine = val
 		@text = @text.replace ///\n///g, ''


event directive tweak

event directive tweak

--- src/core/javascripts/controllers/event_list.js.coffee
+++ src/core/javascripts/controllers/event_list.js.coffee
@@ -117,6 +117,11 @@
     deferred = $q.defer()
     current_event = $scope.current_item.event

+    # de-select the event chain if there's one already picked - as it's hiding other events in the same group
+    if $scope.bb.current_item && ($scope.bb.current_item.event_chain_id || $scope.bb.current_item.event_chain)
+      delete $scope.bb.current_item.event_chain
+      delete $scope.bb.current_item.event_chain_id
+
     comp = $scope.bb.company
     params = {item: $scope.bb.current_item, start_date:$scope.start_date.toISODate(), end_date:$scope.end_date.toISODate()}
     params.event_chain_id = $scope.bb.item_defaults.event_chain if $scope.bb.item_defaults.event_chain
@@ -185,15 +190,16 @@
     comp ||= $scope.bb.company

     # de-select the event chain if there's one already picked - as it's hiding other events in the same group
-    if $scope.bb.current_item && $scope.bb.current_item.event_chain
+    if $scope.bb.current_item && ($scope.bb.current_item.event_chain_id || $scope.bb.current_item.event_chain)
       delete $scope.bb.current_item.event_chain
+      delete $scope.bb.current_item.event_chain_id

     params = {item: $scope.bb.current_item, start_date:$scope.start_date.toISODate(), end_date:$scope.end_date.toISODate()}
     params.event_chain_id = $scope.bb.item_defaults.event_chain if $scope.bb.item_defaults.event_chain

     chains = $scope.loadEventChainData(comp)
     $scope.events = {}
-
+
     EventService.query(comp, params).then (events) ->

       events = _.groupBy events, (event) -> event.date.toISODate()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_test/string_extensions.coffee
+++ source/javascripts/_test/string_extensions.coffee
@@ -54,6 +54,8 @@
   equals "0109".toInt(), 109

   equals "F".toInt(16), 15
+
+  equals "-1."

 test "#underscore", ->
   equals "Pro-tip".underscore(), "pro_tip"


Make logging less verbose

Make logging less verbose

--- manager.coffee
+++ manager.coffee
@@ -30,13 +30,11 @@
             readyCallback()
             return

-        console.log "About to start up #{@name}"
         freeport (err, port) =>
-            console.log "Starting up #{@name} on localhost:#{port}"
             @port = port
             processName = "#{@serviceManager.config.dir}/#{@name}"
             @process = childProcess.spawn processName, [@port]
-            console.log "#{@name} is now running on PID #{@process.pid}"
+            console.log "#{@name}: running on port #{port}, PID #{@process.pid}"

             # Redirect stdout and stderr
             @process.stdout.on 'data', (data) =>
@@ -48,7 +46,7 @@

             # handle the server closing down
             @process.on 'close', (code, signal) =>
-                console.log "Child process exited with code #{code}"
+                console.log "#{@name}: stopped, code #{code}, signal #{signal}"
                 @port = null
                 @process = null

@@ -63,7 +61,7 @@
     stop: =>
         # Kills the server process, and all its children (such as those spawned
         # by e.g. Django's autoreloader or a shell script w/o exec).
-        console.log "Stopping #{@name}"
+        console.log "#{@name}: stop requested"
         if @process?
             psTree @process.pid, (err, children) =>
                 for child in children


SidebarMachineList: Fixed refactor variables.

--- client/Social/Activity/sidebar/sidebarmachinelist.coffee
+++ client/Social/Activity/sidebar/sidebarmachinelist.coffee
@@ -15,13 +15,15 @@

   createHeader: ->

+    { title, hasPlusIcon } = @getOptions()
+
     @header = new KDCustomHTMLView
       tagName  : 'h3'
       cssClass : 'sidebar-title'
-      partial  : options.title
+      partial  : title
       click    : => @emit 'ListHeaderClicked'

-    if options.hasPlusIcon
+    if hasPlusIcon
       @header.addSubView new CustomLinkView
         cssClass : 'add-icon buy-vm'
         title    : ' '
@@ -36,7 +38,7 @@

     @machineBoxes = []

-    for machineData in data
+    for machineData in @getData()

       machineBox = new SidebarMachineBox {}, machineData
       @addSubView machineBox


Client: Activity: Updated messageBody component transformChannelHashtagsmethod to check message is preview mode

--- client/activity/lib/components/common/messagebody.coffee
+++ client/activity/lib/components/common/messagebody.coffee
@@ -64,7 +64,7 @@

   transformChannelHashtags: (message) ->

-    return  if message.has('isFake')
+    return  if message.has('isFake') and not message.has('isPreview')
     return  unless message.has('body')

     transformTags message.get('body'), (transformed) =>


app-settings-menu: Added a notification for not previewable files.

--- client/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/app/Applications/Ace.kdapplication/AppView.coffee
@@ -69,9 +69,12 @@

   preview: ->
     publicUrlCheck = /.*\/(.*\.koding.com)\/website\/(.*)/
-    publicPath = @getData().path.replace publicUrlCheck, 'http://$1/$2'
-    return if publicPath is @getData().path
-    KD.getSingleton("appManager").open "Viewer", (appInstance)->
+    publicPath     = @getData().path.replace publicUrlCheck, 'http://$1/$2'
+    appManager     = KD.getSingleton "appManager"
+    if publicPath is @getData().path
+      {nickname} = KD.whoami().profile
+      return appManager.notify "File must be under: /#{nickname}/Sites/#{nickname}.#{location.hostname}/website/"
+    appManager.open "Viewer", (appInstance)->
       appInstance.open publicPath

   compileAndRun: ->


SidebarMachineBox: Fix workspace delete bug.

--- client/app/lib/activity/sidebar/sidebarmachinebox.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinebox.coffee
@@ -141,5 +141,5 @@

     { workspaces } = @getData()

-    for ws, index in workspaces when ws.getId() is wsId
+    for ws, index in workspaces when ws?.getId() is wsId
       workspaces.splice index, 1


StackTemplateListController: Fix coffee lint error

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -115,7 +115,7 @@
     [item] = @getListItems().filter (i) -> i.getData()._id is _id

     if item
-      kd.warn "Stack template is already added to list!"
+      kd.warn 'Stack template is already added to list!'
       return

     @fetch { _id }, (items) =>


Fixing typo

Fixing typo

--- source/quo.output.coffee
+++ source/quo.output.coffee
@@ -92,7 +92,7 @@
   ###
   Replace each element in the set of matched elements with the provided new
   content and return the set of elements that was removed.
-  @method prepend
+  @method replaceWith
   @param  {html} The content to insert (HTML string, DOMelement, array of DOMelements)
   ###
   $$.fn.replaceWith = (value) ->


some fixed coffee

some fixed coffee

--- dev/coffee/script.coffee
+++ dev/coffee/script.coffee
@@ -5,13 +5,14 @@
   if typeof(min) is 'number' and typeof(max) is 'number'
     random = Math.floor(Math.random() * (max - min + 1) + min)
   else
-    random = null
+    random = false
   random

 class Rusnet
-
   @init_view = ->
-    items = $('#notes_wrap h1').addClass 'custom-h1-block'
+    items = $('#notes_wrap h1')
+    $(items).addClass 'custom-h1-block'
+
     menu = $('#menu').find('ul')
     for i in items
       do (i) ->
@@ -21,13 +22,15 @@
         link_wrap = link_wrap.replace(/_{2,}/g, '_').replace(/_$/g, '')
         link_wrap = link_wrap.replace /_$/g, ''
         link_wrap = link_wrap.toLowerCase()
+
         li = document.createElement 'li'
         $(li).attr 'role', 'presentation'

+        count_number = $(i).nextUntil('h1').find('a').length
         count_link = document.createElement 'span'
         $(count_link)
           .attr 'class', 'badge pull-right'
-          .text( $(i).nextUntil('h1').find('a').length )
+          .text count_number

         link_menu = document.createElement 'a'
         $(link_menu)
@@ -44,11 +47,12 @@
           .attr 'class', 'block'
           .text text_wrap

-        $(i).text('')
-        $(i).append(link_anchor)
-
-        $(li).append(link_menu)
-        $(menu).append(li)
+        $(i)
+          .text ''
+          .append link_anchor
+
+        $(li).append link_menu
+        $(menu).append li
         return
     return

@@ -56,10 +60,13 @@
     items = $('#notes_wrap p a')
     for i in items
       do (i) ->
-        $(i).attr('target', '_blank')
-          .text( i.textContent.replace(/(http|https):\/\/(www){0,1}\.{0,1}/, '') )
-          .text( i.textContent.replace(/\/$/, '') )
-        $('<br/>').insertAfter(i)
+        $(i)
+          .attr 'target', '_blank'
+          .text i.textContent.replace(/(http|https):\/\/(www){0,1}\.{0,1}/, '')
+          .text i.textContent.replace(/\/$/, '')
+
+        br = document.createElement 'br'
+        $(br).insertAfter i
         return
     return

@@ -67,27 +74,34 @@
     tags = $('#notes_wrap ul li')
     for i in tags
       do (i) ->
-        link_tag = $('<a>')
-          .attr('href', '#tag-' + i.textContent)
-          .attr('class', 'white')
-          .text(i.textContent)
-        $(i)
-          .attr('class', 'label label-primary')
-          .text('')
-          .append(link_tag)
+        link_tag = document.createElement 'a'
+        $(link_tag)
+          .attr 'href', '#tag-' + i.textContent
+          .attr 'class', 'white'
+          .text i.textContent
+
+        $(i)
+          .attr 'class', 'label label-primary'
+          .text ''
+          .append link_tag
         return
     return

   @wrap_content = ->
-    $items = $('#notes_wrap h1')
-    for i in $items
-      do (i) ->
-        $(i)
-          .on 'click', (event) ->
-            event.preventDefault
-            $(@).next('.wrap').toggleClass 'show', 500
-          .nextUntil('h1')
-          .wrapAll $('<div>').attr('class', 'wrap')
+    items = $('#notes_wrap h1')
+    for i in items
+      do (i) ->
+        div = document.createElement 'div'
+        $(div).attr 'class', 'wrap'
+
+        $(i).on 'click', (event) ->
+          event.preventDefault()
+          $(i).next('.wrap').toggleClass 'show', 500
+          return
+
+        $(i)
+          .nextUntil 'h1'
+          .wrapAll div
         return
     return


Cleanup

Cleanup

--- src/default-network.coffee
+++ src/default-network.coffee
@@ -57,8 +57,8 @@
     getInterface config.index, (error, data) ->
       return callback(error) if error
       result = {}
+      result.defaultGateway = config.defaultGateway
       result.defaultInterface = data
-      result.defaultGateway = config.defaultGateway
       callback(null, result)

 getOnDarwin = (callback) ->


Remove old grid path

Remove old grid path

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -11,11 +11,7 @@
       fill: "#00aa00"
     }

-  config_path = world.call('grid_path')
-  console.log(config_path )
-  grid_path = ""
-  for n in [0..grid_size] by cell_size
-    grid_path += "M#{n},1 V#{grid_size-1} M1,#{n} H#{grid_size-1} "
+  grid_path = world.call('grid_path')
   [
     SVG.rect {
       class: "grid-background"


Fixed typo, allow reversion

Fixed typo, allow reversion

--- tunemachine/routes/api.coffee
+++ tunemachine/routes/api.coffee
@@ -100,10 +100,13 @@
       res.end 'Need name'
     else
       models.Snapshot.findOne
-        id: req.params.snapId
+        _id: req.params.snapId
       , (err, snap) ->
         if err?
           return models.err res, err
+
+        console.log req.params.snapId
+        console.log snap

         if snap?
           songs = snap.Tracks.map (s) -> s.id


Add existence test when handling save errors, e.g. from failure to enable sharing.

--- src/code/providers/document-store-provider.coffee
+++ src/code/providers/document-store-provider.coffee
@@ -231,7 +231,8 @@
           _permissions: 0
         callback null, data.id
       error: ->
-        callback "Unable to save "+metadata.name
+        docName = metadata?.name or 'document'
+        callback "Unable to save #{docName}"

   save: (cloudContent, metadata, callback) ->
     content = cloudContent.getContent()


abstract comparator conversion

abstract comparator conversion

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -21,19 +21,23 @@
   visitComparison: (node) ->
     left = node.left.visit @
     right = node.right.visit @
-    switch node.comparator
-      when '=', '=='
-        left is right
-      when '!=', '<>'
-        left isnt right
-      when '<'
-        left < right
-      when '>'
-        left > right
-      when '<='
-        left <= right
-      when '>='
-        left >= right
+    comparator = getComparator node.comparator
+    comparator left, right
+
+getComparator = (comparator) ->
+  switch comparator
+    when '=', '=='
+      (left, right) -> left is right
+    when '!=', '<>'
+      (left, right) -> left isnt right
+    when '<'
+      (left, right) -> left < right
+    when '>'
+      (left, right) -> left > right
+    when '<='
+      (left, right) -> left <= right
+    when '>='
+      (left, right) -> left >= right

 class Unbound extends FormulaVisitor
   constructor: ->


Source mapping css

Source mapping css

--- bokehjs/gulp/tasks/styles.coffee
+++ bokehjs/gulp/tasks/styles.coffee
@@ -5,6 +5,7 @@
 minifyCSS = require "gulp-minify-css"
 rename = require "gulp-rename"
 runSequence = require "run-sequence"
+sourcemaps = require "gulp-sourcemaps"

 paths = require "../paths"
 utils = require "../utils"
@@ -17,8 +18,12 @@
 gulp.task "styles:minify", ->
   opts = {}
   gulp.src paths.css.sources
+    .pipe rename "bokeh.min.css"
+    .pipe gulp.dest paths.buildDir.css
+    .pipe sourcemaps.init
+      loadMaps: true
     .pipe minifyCSS opts
-    .pipe rename "bokeh.min.css"
+    .pipe sourcemaps.write './'
     .pipe gulp.dest paths.buildDir.css

 gulp.task "styles", (cb) ->


Remove the require.js configuration

Remove the require.js configuration

--- bokehjs/src/coffee/config.coffee
+++ bokehjs/src/coffee/config.coffee
@@ -1,33 +0,0 @@
-require.config
-  map:
-    # '*' means all modules will get 'jquery-private'
-    # for their 'jquery' dependency.
-    '*': { 'jquery': 'jquery-private' }
-
-    # 'jquery-private' wants the real jQuery module
-    # though. If this line was not here, there would
-    # be an unresolvable cyclic dependency.
-    'jquery-private': { 'jquery': 'jquery' }
-  paths:
-    jquery:            "vendor/jquery-1.11.1/jquery"
-    jquery_ui:         "vendor/jquery-ui-1.11.2/js"
-    jquery_mousewheel: "vendor/jquery-mousewheel-3.1.12/jquery.mousewheel"
-    jquery_event_drag: "vendor/jquery-event-2.2/jquery.event.drag"
-    jquery_event_drop: "vendor/jquery-event-2.2/jquery.event.drop"
-    jqrangeslider:     "vendor/jqrangeslider-5.7.0"
-    slick_grid:        "vendor/slick-grid-2.1.0"
-    underscore:        "vendor/underscore-amd/underscore"
-    backbone:          "vendor/backbone-amd/backbone"
-    bootstrap:         "vendor/bootstrap-3.1.1/js"
-    timezone:          "vendor/timezone-0.0.38/src/timezone"
-    sprintf:           "vendor/sprintf-1.0.2/sprintf"
-    rbush:             "vendor/rbush/rbush"
-    gear_utils:        "vendor/gear-utils/gear-utils"
-    kiwi:              "vendor/kiwi/kiwi"
-    jsnlog:            "vendor/jsnlog.js-2.7.5/jsnlog"
-    hammer:            "vendor/hammer.js-2.0.4/hammer"
-    numeral:           "vendor/numeral.js-1.5.3/numeral"
-    handlebars:        "vendor/handlebars-2.0.0/handlebars"
-  shim:
-    kiwi:
-      exports: 'kiwi'


remove log

remove log

--- app/js/controllers/help.coffee
+++ app/js/controllers/help.coffee
@@ -1,4 +1,3 @@
 angular.module("app").controller "HelpController", ($scope, $location, RpcService) ->
   RpcService.request('about').then (response) =>  #TODO replace when CommonAPI is added
       $scope.about=response.result
-      console.log(response.result)


prevent recaptcha double loading

prevent recaptcha double loading

--- app/assets/javascripts/hancock/feedback/form.coffee
+++ app/assets/javascripts/hancock/feedback/form.coffee
@@ -10,21 +10,39 @@
       data = xhr.responseText
       if $(data).find('img').length > 0
         $(data).find('img').load ->
-          $(form_selector).closest(wrapper_selector).stop().css(opacity: 0.01).html(data).css(opacity: "")
+          wrapper = $(form_selector).closest(wrapper_selector).stop().css(opacity: 0.01)
+          if $(data).find(".recaptcha_error").length > 0 or $(data).find(".fields_block").length == 0
+            wrapper.html(data).css(opacity: "")
+            window.hancock_cms.feedback.recaptcha_render()
+          else
+            wrapper.find(".fields_block").replaceWith($(data).find(".fields_block"))
+            wrapper.css(opacity: "")
       else
-        $(form_selector).closest(wrapper_selector).stop().css(opacity: 0.01).html(data).css(opacity: "")
-      window.hancock_cms.feedback.recaptcha_render()
+        wrapper = $(form_selector).closest(wrapper_selector).stop().css(opacity: 0.01)
+        if $(data).find(".recaptcha_error").length > 0 or $(data).find(".fields_block").length == 0
+          wrapper.html(data).css(opacity: "")
+          window.hancock_cms.feedback.recaptcha_render()
+        else
+          wrapper.find(".fields_block").replaceWith($(data).find(".fields_block"))
+          wrapper.css(opacity: "")
+

   else
     $(document).delegate form_selector, "ajax:complete", (event, xhr, status)->
-      $(form_selector).closest(wrapper_selector).html(xhr.responseText)
-      window.hancock_cms.feedback.recaptcha_render()
+      data = xhr.responseText
+      wrapper = $(form_selector).closest(wrapper_selector)
+      if $(data).find(".recaptcha_error").length > 0 or $(data).find(".fields_block").length == 0
+        wrapper.html(data)
+        window.hancock_cms.feedback.recaptcha_render()
+      else
+        wrapper.find(".fields_block").replaceWith($(data).find(".fields_block"))

   $(document).delegate form_selector + " .input", 'click', (e) ->
     e.preventDefault()
-    $(e.currentTarget).removeClass("field_with_errors").find('span.error').hide()
+    $(e.currentTarget).removeClass("field_with_errors").find('.error').hide()
     return false
+

 window.hancock_cms.feedback.set_update_captcha = (link_selector = "#new_hancock_feedback_contact_message .update_captcha_link",


Clean up temp files

Clean up temp files

--- spec/fs-plus-spec.coffee
+++ spec/fs-plus-spec.coffee
@@ -1,6 +1,8 @@
 path = require 'path'
 temp = require 'temp'
 fs = require '../src/fs-plus'
+
+temp.track()

 describe "fs", ->
   fixturesDir = path.join(__dirname, 'fixtures')


Fix merge conflict

Fix merge conflict

--- src/frontend/webRTC.coffee
+++ src/frontend/webRTC.coffee
@@ -21,10 +21,10 @@

 navigator.getUserMedia = navigator.webkitGetUserMedia or navigator.mozGetUserMedia or navigator.getUserMedia

-window.defaults         = {
-    iceServers  : { "iceServers": [{ "url": "stun:stun..org" }] },
-    constraints : { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } }
-    optional    : { optional: [{ RtpDataChannels: true}] }
+window.defaults = {
+    iceServers: { "iceServers": [{ "url": "stun:stun.ekiga.org" }] },
+    constraints: { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } }
+    optional: { optional: [{ RtpDataChannels: true}] }
 }

 Runnable                = () ->
@@ -151,4 +151,9 @@
 if window?
     window.WebRTC   = WebRTC
 if exports?
-    exports.WebRTC  = WebRTC+<<<<<<< HEAD
+    exports.WebRTC  = WebRTC
+=======
+    exports.PeerConnection  = PC
+    exports.Media           = M
+>>>>>>> 44228b5f90fec33b90f84aa23e35c9075c34588b


Kill stray console.log

Kill stray console.log

--- spec/row-map-spec.coffee
+++ spec/row-map-spec.coffee
@@ -102,5 +102,3 @@
       ])

       map.spliceRegions(3, 7, [{bufferRows: 5, screenRows: 5}])
-
-      console.log map.inspect()


Updated test format for 0.3.0 kitkat

Updated test format for 0.3.0 kitkat

--- spec/timbits.spec.coffee
+++ spec/timbits.spec.coffee
@@ -6,36 +6,39 @@

 #start serving timbits

-server = timbits.serve( {home: homeFolder, port: 8785 })
+server = {}

 module.exports =

+	"setup": ->
+		server = timbits.serve( {home: homeFolder, port: 8785 })
+
+	"teardown": ->
+		server.close()
+
 ## Test Case #1 - check we have a valid server running
 	"test that the express server object returned is functioning, note by default the root redirects so hhtp status code should be 302": (beforeExit)->
 		assert.response server,
 			url: "/"
 			method: "GET"
 		,
-			status: 302
+			status: 200
+			headers: "Content-Type": "text/html; charset=utf-8;"
 		, (res) ->
-			assert.ok res, "Expected a response, perhaps something is wrong our status was: #{res.status}"
+			"Test Case #1 - check we have a valid server running\n"

 ## Test Case #2 - Plain timbit request
 	"test the plain timbit request, this is the simpliest request we could try": (beforeExit) ->
-		calls = 0
 		assert.response server,
 			url: "/plain/"
 			method: "GET"
 		,
+			status: 200
 			body: /Hello Anonymous/
-			status: 200
 		, (res) ->
-			++calls
-			assert.ok res, "Expected a response, perhaps something is wrong our status was: #{res.status} and the response object has: #{res}"
+			"Test Case #2 - Plain timbit request\n"

-		beforeExit ->
-			assert.equal 1, calls

 ## Test Case #3 - Plain timbit with querystring
 	"test the plain timbit request with the addition of a querystring": (beforeExit) ->
@@ -45,10 +48,8 @@
 		,
 			body: /cthulhu/
 		, (res) ->
-			++calls
-
-		beforeExit ->
-			assert.equal 1, calls
+			"Test Case #3 - Plain timbit with querystring\n"
+

 ## Test Case #4 - Cherry timbit which uses the eat callback to override or extend the render method
 	"test the cherry timbit, specifically we are vetting that the eat callback is working allowing us to customize the context rendered": (beforeExit) ->
@@ -59,10 +60,8 @@
 			body: /The current server date\/time is/
 			status: 200
 		, (res) ->
-			++calls
+			"Test Case #4 - Cherry timbit which uses the eat callback to override or extend the render method\n"

-		beforeExit ->
-			assert.equal 1, calls

 ## Test Case #5 - Chocolate timbit which utilizes custom views
 	"test the chocolate timbit which allows us to use alternate views if they exist, this is a test of the 'alternate' view which does exist": (beforeExit) ->
@@ -73,11 +72,9 @@
 			body: /coffeescript/
 			status: 200
 		, (res) ->
-			++calls
+			"Test Case #5 - Chocolate timbit which utilizes custom views\n"

-		beforeExit ->
-			assert.equal 1, calls
-
+
 ## Test Case #6 - Chocolate timbit requesting a view that can not be found (500 Error)
 	"test the choclate timbit using an alternate view that does not exist": (beforeExit) ->
 		calls = 0
@@ -87,10 +84,7 @@
 			body: /Error/
 			status: 500
 		, (res) ->
-			++calls
-
-		beforeExit ->
-			assert.equal 1, calls
+			"Test Case #6 - Chocolate timbit requesting a view that can not be found (500 Error)\n"

 ## Test Case #7 - Dutchie timbit which uses the fetch callback to re-route these calls through the chocolate timbit with views
 	"test the dutchie timbit which implements the fetch callback to re-route calls here to the chocolate timbit with views": (beforeExit) ->
@@ -101,10 +95,7 @@
 			body: /Alternate dutchie Timbit View/
 			status: 200
 		, (res) ->
-			++calls
-
-		beforeExit ->
-			assert.equal 1, calls
+			"Test Case #7 - Dutchie timbit which uses the fetch callback to re-route these calls through the chocolate timbit with views\n"

 ## Test Case #8 - Dutchie timbit which has an invalid path that should generate a 404
 	"test the dutchie timbit with an invalid path in the request": (beforeExit) ->
@@ -114,7 +105,4 @@
 		,
 			status: 404
 		, (res) ->
-			++calls
-
-		beforeExit ->
-			assert.equal 1, calls
+			"Test Case #8 - Dutchie timbit which has an invalid path that should generate a 404\n"


Have the close marker be redrawn with the title on each modal display

--- vendor/assets/javascripts/mercury/modal.js.coffee
+++ vendor/assets/javascripts/mercury/modal.js.coffee
@@ -25,7 +25,7 @@
     @element.html("""

-  #{@options.closeButton || jQuery.type(@options.closeButton) == 'undefined' ? '×' : ''}
+

     """)
     @element.append('

')
@@ -181,6 +181,12 @@

   setTitle: ->
     @titleElement.find('span').html(Mercury.I18n(@options.title))
+    @titleElement.find('a').html(
+      if @options.closeButton == true || jQuery.type(@options.closeButton) == 'undefined'
+        '×'
+      else
+        ''
+    )

   reset: ->


support delete document records

support delete document records

--- vue/src/shared/services/attachment_service.coffee
+++ vue/src/shared/services/attachment_service.coffee
@@ -23,3 +23,20 @@
                 helptext: 'group_files_panel.delete_confirmation'
                 submit: 'common.action.delete'
                 flash: 'poll_common_delete_modal.success'
+
+    delete_document:
+      icon: 'mdi-delete'
+      name: 'common.action.delete'
+      canPerform: ->
+        attachment.isA('document') && AbilityService.canAdminister(attachment.group())
+      perform: ->
+        EventBus.$emit 'openModal',
+          component: 'ConfirmModal'
+          props:
+            confirm:
+              submit: attachment.destroy
+              text:
+                title: 'comment_form.attachments.remove_attachment'
+                helptext: 'group_files_panel.delete_confirmation'
+                submit: 'common.action.delete'
+                flash: 'poll_common_delete_modal.success'


Indent and options fix

Indent and options fix

--- templates/coffee.coffee
+++ templates/coffee.coffee
@@ -29,6 +29,7 @@
   buff = []
   s    = (str) -> buff.push(str)

+  options.dependencies ||= []
   options.dependencies =
     for dep in options.dependencies
       if dep.global
@@ -54,8 +55,8 @@
     __module__ = if options.global
       "root.#{options.global}"
     else "__module__"
-    s "   #{__module__} = factory(root)"
-    s "   define -> #{__module__}"
+    s "    #{__module__} = factory(root)"
+    s "    define -> #{__module__}"

   s "  else if typeof module is 'object' && typeof module.exports is 'object'"


Set TimeStore date for before due date spec

Otherwise it'll mysteriously stop working in few months :)

--- test/components/performance/homework-cell.spec.coffee
+++ test/components/performance/homework-cell.spec.coffee
@@ -20,6 +20,9 @@

   describe 'before due date', ->
     beforeEach ->
+      now = new Date()
+      iso_string = 'Fri Jun 11 2015 00:00:00 GMT+0000 (UTC)'
+      TimeActions.setFromString(iso_string, now)
       _.extend @props.task,
         due_at:          '2015-10-14T12:00:00.000Z'
         last_worked_at:  '2015-10-13T12:00:00.000Z'


Fix accessing undefined objects

Fix accessing undefined objects

--- app/views/x3d_page_view.coffee
+++ app/views/x3d_page_view.coffee
@@ -34,8 +34,8 @@
       @currentObject = undefined

   updateObject: (attributes) =>
-    translation = @currentObject.parentElement
-    translation.setAttribute('translation',
+    translation = @currentObject?.parentElement
+    translation?.setAttribute('translation',
     "#{attributes.selectedObject.translation.x} #{attributes.selectedObject.translation.y} #{attributes.selectedObject.translation.z}")

   document.createElementWithCallback = (obj_name, callback, event_type) ->


Fix printing unstack error in binary

Fix printing unstack error in binary

--- lib/autoprefixer/binary.coffee
+++ lib/autoprefixer/binary.coffee
@@ -143,8 +143,9 @@
         @error "autoprefixer: #{ error.message }"
       else
         @error 'autoprefixer: Internal error'
-        @error ''
-        @error error.stack
+        if error.stack?
+          @error ''
+          @error error.stack
     return @endWork() unless prefixed

     if file == '-'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

 ###*
-Transitions to the title state
+Transitions to the title state on user input.

 @name update
 @methodOf GameOver#


Use copy plugin from aloha testing.

Use copy plugin from aloha testing.

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -57,6 +57,7 @@
         'oer/assorted'
         'ghbook/image'
         'oer/note'
+        'oer/copy'
       ]

       # This whole thing is what's needed to:


platformMenuPath -> platformMenu

platformMenuPath -> platformMenu

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -149,8 +149,8 @@
       @sendToBrowserProcess(@template, keystrokesByCommand)

   loadPlatformItems: ->
-    if platformMenuPath
-      @add(menu)
+    if platformMenu?
+      @add(platformMenu)
     else
       menusDirPath = path.join(@resourcePath, 'menus')
       platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json'])