fix chatroom vertical styling further

fix chatroom vertical styling further

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -68,10 +68,10 @@
         close_input    : rtypes.func

     getInitialState: ->
-        edited_message  : newest_content(@props.message)
-        history_size    : @props.message.get('history').size
-        show_history    : false
-        new_changes     : false
+        edited_message : newest_content(@props.message)
+        history_size   : @props.message.get('history').size
+        show_history   : false
+        new_changes    : false

     componentWillReceiveProps: (newProps) ->
         if @state.history_size != @props.message.get('history').size
@@ -446,16 +446,6 @@
         input          : ''
         preview        : ''

-    chat_input_style :
-        margin       : "0"
-        padding      : "4px 7px 4px 7px"
-        marginTop    : "5px"
-
-    mobile_chat_input_style:
-        margin       : "0"
-        padding      : "4px 7px 4px 7px"
-        marginTop    : "5px"
-
     preview_style:
         background   : '#f5f5f5'
         fontSize     : '14px'
@@ -630,15 +620,7 @@
                 </Tip>
             </Button>

-    render: ->
-        if not @props.messages? or not @props.redux? or not @props.input.length?
-            return <Loading/>
-
-        if @props.input.length > 0 and @props.is_preview and @refs.preview
-            paddingBottom = "#{@_preview_height + 10}px"
-        else
-            paddingBottom = '0px'
-
+    render_desktop: ->
         chat_log_style =
             overflowY    : "auto"
             overflowX    : "hidden"
@@ -646,10 +628,86 @@
             margin       : "0"
             padding      : "0"
             paddingRight : "10px"
-            paddingBottom: paddingBottom
             background   : 'white'

-        mobile_chat_log_style =
+        chat_input_style =
+            margin       : "0"
+            padding      : "4px 7px 4px 7px"
+            marginTop    : "5px"
+            fontSize     : @props.font_size
+
+        <Grid fluid={true} style={maxWidth: '1200px', display:'flex', flexDirection:'column'}>
+            <Row style={marginBottom:'5px'}>
+                <Col xs={2} mdHidden>
+                    <Button className='smc-small-only'
+                            onClick={@show_files}>
+                            <Icon name='toggle-up'/> Files
+                    </Button>
+                </Col>
+                <Col xs={4} md={4} style={padding:'0px'}>
+                    <UsersViewing
+                          file_use_id = {@props.file_use_id}
+                          file_use    = {@props.file_use}
+                          account_id  = {@props.account_id}
+                          user_map    = {@props.user_map} />
+                </Col>
+                <Col xs={6} md={6} className="pull-right" style={padding:'2px', textAlign:'right'}>
+                    <ButtonGroup>
+                        {@render_timetravel_button()}
+                        {@render_video_chat_button()}
+                        {@render_bottom_button()}
+                    </ButtonGroup>
+                </Col>
+            </Row>
+            <Row className='smc-vflex'>
+                <Col className='smc-vflex' md={12} style={padding:'0px 2px 0px 2px'}>
+                    <Well style={chat_log_style} ref='log_container' onScroll={@on_scroll}>
+                        <ChatLog
+                            messages     = {@props.messages}
+                            account_id   = {@props.account_id}
+                            user_map     = {@props.user_map}
+                            project_id   = {@props.project_id}
+                            font_size    = {@props.font_size}
+                            file_path    = {if @props.path? then misc.path_split(@props.path).head}
+                            actions      = {@props.actions}
+                            saved_mesg   = {@props.saved_mesg}
+                            focus_end    = {focus_endpoint}
+                            set_scroll   = {@set_chat_log_state}
+                            show_heads   = true />
+                        {@render_preview_message() if @props.input.length > 0 and @props.is_preview}
+                    </Well>
+                </Col>
+            </Row>
+            <Row>
+                <Col xs={10} md={11} style={padding:'0px 2px 0px 2px'}>
+                    <FormGroup>
+                        <FormControl
+                            autoFocus   = {true}
+                            rows        = 4
+                            componentClass = 'textarea'
+                            ref         = 'input'
+                            onKeyDown   = {@keydown}
+                            value       = {@props.input}
+                            placeholder = {'Type a message...'}
+                            onClick     = {@mark_as_read}
+                            onChange    = {(e)=>@props.actions.set_input(e.target.value)}
+                            onFocus     = {focus_endpoint}
+                            style       = {chat_input_style}
+                        />
+                    </FormGroup>
+                </Col>
+                <Col xs={2} md={1} style={height:'98.6px', padding:'0px 2px 0px 2px', marginBottom: '12px'}>
+                    <Button onClick={@button_on_click} disabled={@props.input==''} bsStyle='info' style={height:'30%', width:'100%', marginTop:'5px'}>Preview</Button>
+                    <Button onClick={@button_send_chat} disabled={@props.input==''} bsStyle='success' style={height:'60%', width:'100%'}>Send</Button>
+                </Col>
+            </Row>
+            <Row>
+                {@render_bottom_tip()}
+            </Row>
+        </Grid>
+
+    render_mobile: ->
+        chat_log_style =
             overflowY    : "auto"
             overflowX    : "hidden"
             maxHeight    : "60vh"
@@ -658,153 +716,91 @@
             padding      : "0"
             background   : 'white'

-        if not IS_MOBILE
-            @chat_input_style.fontSize = @props.font_size
-            <Grid fluid={true} style={maxWidth: '1200px'}>
-                <Row style={marginBottom:'5px'}>
-                    <Col xs={2} mdHidden>
-                        <Button className='smc-small-only'
-                                onClick={@show_files}>
-                                <Icon name='toggle-up'/> Files
-                        </Button>
-                    </Col>
-                    <Col xs={4} md={4} style={padding:'0px'}>
-                        <UsersViewing
-                              file_use_id = {@props.file_use_id}
-                              file_use    = {@props.file_use}
-                              account_id  = {@props.account_id}
-                              user_map    = {@props.user_map} />
-                    </Col>
-                    <Col xs={6} md={6} className="pull-right" style={padding:'2px', textAlign:'right'}>
-                        <ButtonGroup>
-                            {@render_timetravel_button()}
-                            {@render_video_chat_button()}
-                            {@render_bottom_button()}
-                        </ButtonGroup>
-                    </Col>
-                </Row>
-                <Row>
-                    <Col md={12} style={padding:'0px 2px 0px 2px'}>
-                        <Well style={chat_log_style} ref='log_container' onScroll={@on_scroll}>
-                            <ChatLog
-                                messages     = {@props.messages}
-                                account_id   = {@props.account_id}
-                                user_map     = {@props.user_map}
-                                project_id   = {@props.project_id}
-                                font_size    = {@props.font_size}
-                                file_path    = {if @props.path? then misc.path_split(@props.path).head}
-                                actions      = {@props.actions}
-                                saved_mesg   = {@props.saved_mesg}
-                                focus_end    = {focus_endpoint}
-                                set_scroll   = {@set_chat_log_state}
-                                show_heads   = true />
-                            {@render_preview_message() if @props.input.length > 0 and @props.is_preview}
-                        </Well>
-                    </Col>
-                </Row>
-                <Row>
-                    <Col xs={10} md={11} style={padding:'0px 2px 0px 2px'}>
-                        <FormGroup>
-                            <FormControl
-                                autoFocus   = {true}
-                                rows        = 4
-                                componentClass = 'textarea'
-                                ref         = 'input'
-                                onKeyDown   = {@keydown}
-                                value       = {@props.input}
-                                placeholder = {'Type a message...'}
-                                onClick     = {@mark_as_read}
-                                onChange    = {(e)=>@props.actions.set_input(e.target.value)}
-                                onFocus     = {focus_endpoint}
-                                style       = {@chat_input_style}
-                            />
-                        </FormGroup>
-                    </Col>
-                    <Col xs={2} md={1} style={height:'98.6px', padding:'0px 2px 0px 2px', marginBottom: '12px'}>
-                        <Button onClick={@button_on_click} disabled={@props.input==''} bsStyle='info' style={height:'30%', width:'100%', marginTop:'5px'}>Preview</Button>
-                        <Button onClick={@button_send_chat} disabled={@props.input==''} bsStyle='success' style={height:'60%', width:'100%'}>Send</Button>
-                    </Col>
-                </Row>
-                <Row>
-                    {@render_bottom_tip()}
-                </Row>
-            </Grid>
-        else
-            ##########################################
-            # MOBILE HACK
-            ##########################################
-            <Grid fluid={true}>
-                <Row style={marginBottom:'5px'}>
-                    <ButtonGroup>
-                        <Button className='smc-small-only'
-                            onClick={@show_files}>
-                            <Icon name='toggle-up'/> Files
-                        </Button>
-                        <Button onClick={@scroll_to_bottom}>
-                            <Icon name='arrow-down'/> Scroll to Bottom
-                        </Button>
-                    </ButtonGroup>
-                    <UsersViewing
-                          file_use_id = {@props.file_use_id}
-                          file_use    = {@props.file_use}
-                          account_id  = {@props.account_id}
-                          user_map    = {@props.user_map} />
-                </Row>
-                <Row>
-                    <Col md={12} style={padding:'0px 2px 0px 2px'}>
-                        <Well style={mobile_chat_log_style} ref='log_container' onScroll={@on_scroll} >
-                            <ChatLog
-                                messages     = {@props.messages}
-                                account_id   = {@props.account_id}
-                                user_map     = {@props.user_map}
-                                project_id   = {@props.project_id}
-                                font_size    = {@props.font_size}
-                                file_path    = {if @props.path? then misc.path_split(@props.path).head}
-                                actions      = {@props.actions}
-                                focus_end    = {focus_endpoint}
-                                show_heads   = {false} />
-                        </Well>
-                    </Col>
-                </Row>
-                <Row>
-                    <Col xs={10} style={padding:'0px 2px 0px 2px'}>
-                        <FormGroup>
-                            <FormControl
-                                autoFocus   = {isMobile.Android()}
-                                rows        = {2}
-                                type        = 'textarea'
-                                ref         = 'input'
-                                onKeyDown   = {@keydown}
-                                value       = {@props.input}
-                                placeholder = {'Type a message...'}
-                                onClick     = {@mark_as_read}
-                                onChange    = {(e)=>@props.actions.set_input(e.target.value)}
-                                style       = {@mobile_chat_input_style}
-                            />
-                        </FormGroup>
-                    </Col>
-                    <Col xs={2} style={height:'57px', padding:'0px 2px 0px 2px'}>
-                        <Button onClick={@button_send_chat} disabled={@props.input==''} bsStyle='primary' style={height:'90%', width:'100%', marginTop:'5px'}>
-                            <Icon name='chevron-circle-right'/>
-                        </Button>
-                    </Col>
-                </Row>
-            </Grid>
+        mobile_chat_input_style =
+            margin       : "0"
+            padding      : "4px 7px 4px 7px"
+            marginTop    : "5px"
+
+        <Grid fluid={true}>
+            <Row style={marginBottom:'5px'}>
+                <ButtonGroup>
+                    <Button className='smc-small-only'
+                        onClick={@show_files}>
+                        <Icon name='toggle-up'/> Files
+                    </Button>
+                    <Button onClick={@scroll_to_bottom}>
+                        <Icon name='arrow-down'/> Scroll to Bottom
+                    </Button>
+                </ButtonGroup>
+                <UsersViewing
+                      file_use_id = {@props.file_use_id}
+                      file_use    = {@props.file_use}
+                      account_id  = {@props.account_id}
+                      user_map    = {@props.user_map} />
+            </Row>
+            <Row>
+                <Col md={12} style={padding:'0px 2px 0px 2px'}>
+                    <Well style={chat_log_style} ref='log_container' onScroll={@on_scroll} >
+                        <ChatLog
+                            messages     = {@props.messages}
+                            account_id   = {@props.account_id}
+                            user_map     = {@props.user_map}
+                            project_id   = {@props.project_id}
+                            font_size    = {@props.font_size}
+                            file_path    = {if @props.path? then misc.path_split(@props.path).head}
+                            actions      = {@props.actions}
+                            focus_end    = {focus_endpoint}
+                            show_heads   = {false} />
+                    </Well>
+                </Col>
+            </Row>
+            <Row>
+                <Col xs={10} style={padding:'0px 2px 0px 2px'}>
+                    <FormGroup>
+                        <FormControl
+                            autoFocus   = {isMobile.Android()}
+                            rows        = {2}
+                            type        = 'textarea'
+                            ref         = 'input'
+                            onKeyDown   = {@keydown}
+                            value       = {@props.input}
+                            placeholder = {'Type a message...'}
+                            onClick     = {@mark_as_read}
+                            onChange    = {(e)=>@props.actions.set_input(e.target.value)}
+                            style       = {mobile_chat_input_style}
+                        />
+                    </FormGroup>
+                </Col>
+                <Col xs={2} style={height:'57px', padding:'0px 2px 0px 2px'}>
+                    <Button onClick={@button_send_chat} disabled={@props.input==''} bsStyle='primary' style={height:'90%', width:'100%', marginTop:'5px'}>
+                        <Icon name='chevron-circle-right'/>
+                    </Button>
+                </Col>
+            </Row>
+        </Grid>
+
+    render: ->
+        if not @props.messages? or not @props.redux? or not @props.input.length?
+            return <Loading/>
+
+        if true or IS_MOBILE
+            return @render_mobile()
+        else
+            return @render_desktop()
+

 ChatEditorGenerator = (path, redux, project_id) ->
     name = redux_name(project_id, path)
     file_use_id = require('smc-util/schema').client_db.sha1(project_id, path)
     C_ChatRoom = ({actions}) ->
-        <div style={padding:"7px 7px 7px 7px", borderTop: '1px solid rgb(170, 170, 170)'}>
-            <ChatRoom
-                redux       = {redux}
-                path        = {path}
-                name        = {name}
-                actions     = {actions}
-                project_id  = {project_id}
-                file_use_id = {file_use_id}
-                />
-        </div>
+        <ChatRoom
+            redux       = {redux}
+            path        = {path}
+            name        = {name}
+            actions     = {actions}
+            project_id  = {project_id}
+            file_use_id = {file_use_id}
+            />

     C_ChatRoom.propTypes =
         actions : rtypes.object.isRequired


Give user partner access

Give user partner access

--- node/fabricate.coffee
+++ node/fabricate.coffee
@@ -180,6 +180,7 @@
       collector_level: '5'
       default_profile_id: "alessandra"
       likely_to_purchase: 0
+      has_partner_access: true
       get: (attr) -> return @[attr]
       set: (attrs) -> _.extend @, attrs


fixed: display multiple files from filepicker

--- app/assets/javascripts/admin/views/task/task_create_from_nml_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_from_nml_view.coffee
@@ -55,16 +55,11 @@
     # do nothing if nothing selected
     if files

+      filePath = files[0].name
+
       # concat files comma separated
-      if files.length > 1
-
-        filePath = ""
-        for index in [0..file.length]
-          filePath += @files[index].name + ", "
-
-      # display a single file
-      else
-        filePath = files[0].name
+      for index in [1..files.length-1]
+        filePath += ", " + files[index].name

       # update ui
       @ui.fileInfo.val(filePath)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -118,10 +118,7 @@

     toHex: ->
       padString = (hexString) ->
-        if hexString.length == 1
-          pad = "0"
-        else
-          pad = ""
+        if hexString.length == 1 then pad = "0" else pad = ""

         return pad + hexString


Correção validação saldo de conta.

Correção validação saldo de conta.

--- Operador-Deposito/server/lib/validations.coffee
+++ Operador-Deposito/server/lib/validations.coffee
@@ -87,7 +87,7 @@
 			throw new Meteor.Error "Conta não encontrada"

 		saldo = record.saldo
-		if saldo <= valor
+		if saldo < valor
 			throw new Meteor.Error 'Conta não possui saldo suficiente'

 		return saldo


Can't close before finished setting state

--- resources/assets/coffee/react/_components/comment-editor.coffee
+++ resources/assets/coffee/react/_components/comment-editor.coffee
@@ -106,17 +106,19 @@

         onDone = (data) =>
           @setState message: ''
-          @props.close?()
           $.publish 'comment:added', comment: data
       when 'edit'
         url = laroute.route 'comments.update', comment: @props.id
         method = 'PUT'

-        onDone = (data) =>
-          @props.close?()
+        onDone = (data) ->
           $.publish 'comment:updated', comment: data

     @xhr = $.ajax url, {method, data}
-    .done onDone
-    .always =>
+    .done (data) =>
+      onDone(data)
+      @setState posting: false, =>
+        @props.close?()
+    .fail (xhr, status) =>
       @setState posting: false
+      osu.ajaxError(xhr, status)


write a helpful message when an error occurs

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -177,7 +177,8 @@
       return compileResult:compileResult, referencedImportPaths:option.ref

     catch e
-      file.error message:e.message
+      file.error message:e.message + '\n' +
+        @buildErrorMessage e, option, browserifyOptions

     return

@@ -195,6 +196,51 @@
     browserify.transform envify envifyOptions

     return
+
+  buildErrorMessage: (error, option, browserifyOptions) ->
+    # let's look for the cannot find module error's parts
+    regex = /Cannot find module '(.*)' from '(.*)'/
+    match = regex.exec error.toString()
+
+    # if we find them then the second one will exist in match
+    if match?[2]?
+      # first is the name of the module
+      module = match[1]
+      # second is the basedir used, the 'from'
+      basedir = match[2]
+
+      # check if the basedir exists
+      dir = basedir
+      basedirExists = if fs.existsSync dir then 'yes' else 'MISSING'
+
+      # check if the basedir has `node_modules` in it
+      dir = path.join dir, 'node_modules'
+      nodeModulesExists = if fs.existsSync dir then 'yes' else 'MISSING'
+
+      # check if the module is in the node_modules directory
+      dir = path.join dir, module
+      moduleExists = if fs.existsSync dir then 'yes' else 'MISSING'
+
+      # show the above results in the message
+      moduleCheckMessage =
+        """
+
+        Missing module check:
+        >  basedir exists     : #{basedirExists}
+        >  node_modules exists: #{nodeModulesExists}
+        >  module exists      : #{moduleExists}
+
+        """
+
+    # else use an empty string because it's not a 'cannot find module' error
+    else moduleCheckMessage = ''
+
+    # now create the message to return
+    """
+    #{moduleCheckMessage}
+    Browserify options:
+    >  #{JSON.stringify browserifyOptions, null, '>  '}
+    """

   getBundle: (browserify, file) ->


:memo: more docs for Config

:memo: more docs for Config

--- src/config.coffee
+++ src/config.coffee
@@ -112,6 +112,23 @@
 # ```
 #
 # ### Supported Types
+#
+# The `type` keyword can be a string with any one of the following. You can also
+# chain them by specifying multiple in an an array. For example
+#
+# ```coffee
+# config:
+#   someSetting:
+#     type: ['boolean', 'integer']
+#     default: 5
+#
+# # Then
+# atom.config.set('my-package.someSetting', 'true')
+# atom.config.get('my-package.someSetting') # -> true
+#
+# atom.config.set('my-package.someSetting', '12')
+# atom.config.get('my-package.someSetting') # -> 12
+# ```
 #
 # #### string
 #


fixed version check for windows resolves error message with apm update / upgrade

--- src/config.coffee
+++ src/config.coffee
@@ -20,6 +20,8 @@
         child_process.exec 'mdfind "kMDItemCFBundleIdentifier == \'com.github.atom\'"', (error, stdout='', stderr) ->
           appLocation = stdout.split('\n')[0] ? '/Applications/Atom.app'
           callback("#{appLocation}/Contents/Resources/app")
+      if process.platform is 'win32'
+        process.nextTick -> callback('../../../..')
       else
         process.nextTick -> callback('/usr/local/share/atom/resources/app')


Reading BBox from footprint and not from domainset.

--- src/sources/eowcs.coffee
+++ src/sources/eowcs.coffee
@@ -37,17 +37,16 @@
             records = []
             if response.coverageDescriptions? and response.coverageDescriptions.length > 0
                 for coverage in response.coverageDescriptions
-                    bbox = [
-                        coverage.bounds.lower[1],
-                        coverage.bounds.lower[0],
-                        coverage.bounds.upper[1],
-                        coverage.bounds.upper[0]
-                    ]
+                    bbox = [NaN, NaN, NaN, NaN]
                     footprint = []
                     for i in [0...coverage.footprint.length] by 2
-                        footprint.push(
-                            [coverage.footprint[i+1], coverage.footprint[i]]
-                        )
+                        [lon, lat] = [coverage.footprint[i+1], coverage.footprint[i]]
+                        footprint.push([lon, lat])
+                        bbox[0] = if isNaN(bbox[0]) then lon  else Math.min(lon, bbox[0])
+                        bbox[1] = if isNaN(bbox[1]) then lat  else Math.min(lat, bbox[1])
+                        bbox[2] = if isNaN(bbox[2]) then lon  else Math.max(lon, bbox[2])
+                        bbox[3] = if isNaN(bbox[3]) then lat  else Math.max(lat, bbox[3])
+
                     records.push([
                         new Date(coverage.timePeriod[0]),
                         new Date(coverage.timePeriod[1]), {


Adding the clearTask functionality

Adding the clearTask functionality

--- static/js/maproulette.coffee
+++ static/js/maproulette.coffee
@@ -317,7 +317,9 @@
   challenge = currentChallenge.slug
   task_id = currentTask.id
   $.post "/c/#{challenge}/task/#{task_id}", payload, ->
-    setDelay 1 getTask(challenge, near)
+    setDelay 1 ->
+      clearTask()
+      getTask(challenge, near)

 @openIn = (e) ->
   ###


Add property test cases.

Add property test cases.

--- test/e2e/subjectDetailSpec.coffee
+++ test/e2e/subjectDetailSpec.coffee
@@ -2,7 +2,7 @@

 expect = require('./helpers/expect')()

-Page = require('./helpers/page')()
+Page = require './helpers/page'

 class SubjectDetailPage extends Page
   # Finds the profile table WebElements for the given CSS style.
@@ -22,11 +22,15 @@
   #   text value
   profileTablesForCss: (selector, fn) ->
     tables = (panel) ->
-      panel.all(By.tagName('tbody'))
-
+      panel.all(By.tagName('table'))
+
+    headings = (table) ->
+      table.all(By.tagName('th'))
+
     rows = (table) ->
-      table.all(By.tagName('tr'))
-
+      body = table.element(By.tagName('tbody'))
+      body.all(By.tagName('tr'))
+
     columns = (row) ->
       row.all(By.tagName('td'))

@@ -34,16 +38,23 @@
       col.getText().then (text) ->
         fn(text) if text? and fn?
         text
-
+
     mapTables = (panel) ->
+      mapTable = (table) ->
+        mapHeader = ->
+          headings(table).map(text)
+
+        mapBody = ->
+          mapRow = (row) ->
+            columns(row).map(text)
+
+          rows(table).map(mapRow)
+
+        header: mapHeader()
+        body: mapBody()
+
       tables(panel).map(mapTable)

-    mapTable = (table) ->
-      rows(table).map(mapRow)
-
-    mapRow = (row) ->
-      columns(row).map(text)
-
     @select(selector)
       .then(mapTables)

@@ -55,25 +66,7 @@
     @select('.qi-modeling-profile button').then (button) ->
       fn(button) if button? and fn?
       button
-
-  # Finds the imaging profile table WebElements.
-  # See profileTablesForCss.
-  #
-  # @param fn optional function to run on the resolved tables
-  # @returns the imaging profile tables promise
-  imagingProfileTables: (fn) ->
-    click = (button) ->
-      button.click()
-
-    tables = =>
-      @profileTablesForCss('qi-modeling-table', fn)
-
-    # Click the imaging profile format button to show the imaging
-    # profile tables.
-    @imageProfileFormatButton()
-      .then(click)
-      .then(tables)
-
+
   # Finds the imaging profile table panel WebElement.
   #
   # @param fn optional function to run on the resolved panel
@@ -82,216 +75,365 @@
     @select('qi-modeling-table').then (panel) ->
       fn(panel) if panel? and fn?
       panel
-
-  # Finds the clinical profile table WebElements.
+
+  # Finds the imaging profile table WebElements.
   # See profileTablesForCss.
   #
   # @param fn optional function to run on the resolved tables
-  # @returns the clinical profile tables promise
-  clinicalProfileTables: (fn) ->
-    # Note: a version of this with the all function fails, e.g.:
-    #   element(By.css()).all(...)
-    # results in:
-    #   TypeError: Object #<Object> has no method 'all'
-    @profileTablesForCss('qi-clinical-table', fn)
+  # @returns the imaging profile tables promise
+  imagingProfileTables: (fn) ->
+    click = (button) ->
+      button.click()
+
+    tables = =>
+      @profileTablesForCss('qi-modeling-table', fn)
+
+    # Click the imaging profile format button to show the imaging
+    # profile tables.
+    @imageProfileFormatButton()
+      .then(click)
+      .then(tables)

   # @param fn optional function to run on the resolved tables
   modelingChart: (fn) ->
     @select('#qi-modeling-chart').then (chart) ->
       fn(chart) if chart? and fn?
       chart
-
+
   # @param fn optional function to run on the resolved chart
-  # @returns the line chart promise
-  lineChart: (fn) ->
-    this.select('//qi-visit-dateline//nvd3-line-chart').then (chart) ->
+  # @returns the date line chart promise
+  dateLine: (fn) ->
+    @select('//qi-visit-dateline//nvd3-line-chart').then (chart) ->
       fn(chart) if chart? and fn?
       chart

+  # Finds the clinical profile table panel WebElement.
+  #
+  # @param fn optional function to run on the resolved panel
+  # @returns the clinical profile table panel promise
+  clinicalProfileTablePanel: (fn) ->
+    # @param selector the selection criterion
+    # @param bindings the table field {property: binding} bindings
+    # @returns the target table augmented with the component table
+    #   access functions, or null if the table was not found
+    selectTableWithBindings = (selector, bindings) =>
+      # Gets the given WebElement text.
+      text = (elt) -> elt.getText() if elt
+
+      # Searches for a WebElement and resolves to its text.
+      selectText = (selector) => @select(selector).then(text)
+
+      # @param table the target table
+      # @param bindings the table field bindings
+      addTextFields = (table, bindings) =>
+        # @param property the field property
+        # @param _binding the subject property bound to the field
+        addTextField = (property, binding) ->
+          # Associate the field property with its text.
+          table[property] = ->
+            # Note: the assignments below must be made locally in
+            # this anonymous function rather than preceding this
+            # statement in the addTextField function in order to
+            # work around the following Javascript scope/closure
+            # interaction anomaly:
+            # * If the local variables below are bound in the
+            #   parent context, then every property finder resolves
+            #   to the last property binding.
+            sbjBinding = 'subject.' + binding
+            # Make the field element search selector.
+            selector = By.binding(sbjBinding)
+            selectText(selector)
+
+        addTextField(prop, binding) for prop, binding of bindings
+
+      # Find the table.
+      #
+      # Note: A better solution is to perform the field selects below
+      # against the table panel rather than the page. However,
+      # panel.findElement('qi-demographics-table')
+      # results in an error, since the untyped panel object has most, but
+      # not all, of the WebElement function properties. In particular, it
+      # is missing the findElement function. The work-around is to search
+      # in the page context instead.
+      #
+      # TODO - find out why the panel is neither an ElementFinder nor a
+      # WebElement and replace the select below with panel.element or
+      # panel.findElement. Add panel to the clinicalProfileTablePanel
+      # function arguments.
+      @select(selector).then (table) ->
+        # Add the fields.
+        addTextFields(table, bindings) if table
+        # Return the table, or null if the table was not found.
+        table
+
+    # Adds a function property for each component table to the given
+    # profile panel.
+    #
+    # @param panel the clinical profile panel
+    addComponentTables = (panel) ->
+      componentTables =
+        demographics:
+          selector: 'qi-demographics-table'
+          bindings:
+            age: 'birthDate'
+            races: 'races'
+            ethnicity: 'ethnicity'
+
+      addComponentTable = (prop, selector, bindings) ->
+        panel[prop] = -> selectTableWithBindings(selector, bindings)
+
+      for prop, spec of componentTables
+        addComponentTable(prop, spec.selector, spec.bindings)
+
+    # Find the clinical profile table.
+    @select('qi-clinical-table').then (panel) =>
+      if panel?
+        # Add the component table functions.
+        addComponentTables(panel)
+        # Apply the function to the panel.
+        fn(panel) if fn?
+      panel
+
+  # Finds the demographics profile table WebElements.
+  #
+  # @param fn optional function to run on the resolved table
+  # @returns the demographics table promise
+
 describe 'E2E Testing Subject Detail', ->
   page = null
-
-  before ->
-    page = new SubjectDetailPage '/quip/breast/subject/1?project=QIN_Test'
-
-  # The page header test cases.
-  describe 'Header', ->
-    it 'should display the billboard', ->
-      expect(page.billboard, 'The billboard is incorrect').to.eventually.equal('Breast Patient 1')
-
-    it 'should have a home button', ->
-      pat = /.*\/quip\?project=QIN_Test$/
-      expect(page.home(), 'The home URL is incorrect').to.eventually.match(pat)
-
-    it 'should have help text', ->
-      expect(page.help(), 'The help is missing').to.eventually.exist
-
-  # The page main body test cases.
-  describe 'Imaging Profile', ->
-    chart = null
-    table = null
-
+
+  describe 'Breast', ->
     before ->
-      page.modelingChart().then (elt) ->
-        chart = elt
-      table = page.imagingProfileTablePanel().then (elt) ->
-        table = elt
-
-    describe 'Dateline', ->
-      # Note: this is as deep as the chart can be tested,
-      # since the chart SVG content is not visible to the
-      # tester, perhaps because the chart directive dynamically
-      # inserts the SVG into the DOM, and the app directive
-      # then further modifies it.
-      it 'should exist', ->
-        expect(page.lineChart(), 'The dateline is missing').to.eventually.exist
-
-    describe 'Format Button', ->
-      it 'should toggle the chart/table display', ->
-        page.imageProfileFormatButton().then (button) ->
-          # The format button exists.
-          expect(button, 'The imaging profile format button is missing')
-            .to.exist
-          # When the button is clicked, then the imaging profile table
-          # panel is displayed.
-          button.click().then ->
-            table.isDisplayed().then (displayed) ->
-              expect(displayed, 'The modeling table panel is hidden after the' +
-                                ' format button is clicked').to.be.true
-            # The chart is hidden.
-            chart.isDisplayed().then (displayed) ->
-              expect(displayed, 'The modeling chart is displayed after the' +
-                                ' format button is clicked').to.be.false
-            # When the button is clicked again, then the table is hidden and
-            # the chart is displayed.
+      page = new SubjectDetailPage '/quip/breast/subject/1?project=QIN_Test'
+
+    # The page header test cases.
+    describe 'Header', ->
+      it 'should display the billboard', ->
+        expect(page.billboard, 'The billboard is incorrect').to.eventually.equal('Breast Patient 1')
+
+      it 'should have a home button', ->
+        pat = /.*\/quip\?project=QIN_Test$/
+        expect(page.home, 'The home URL is incorrect').to.eventually.match(pat)
+
+      it 'should have help text', ->
+        expect(page.help, 'The help is missing').to.eventually.exist
+
+    # The page main body test cases.
+    describe 'Imaging Profile', ->
+      chart = null
+      table = null
+
+      before ->
+        page.modelingChart().then (elt) ->
+          chart = elt
+        page.imagingProfileTablePanel().then (elt) ->
+          table = elt
+
+      describe 'Dateline', ->
+        # Note: this is as deep as the chart can be tested,
+        # since the chart SVG content is not visible to the
+        # tester, perhaps because the chart directive dynamically
+        # inserts the SVG into the DOM, and the app directive
+        # then further modifies it.
+        it 'should exist', ->
+          expect(page.dateLine(), 'The dateline is missing').to.eventually.exist
+
+      describe 'Format Button', ->
+        it 'should toggle the chart/table display', ->
+          page.imageProfileFormatButton().then (button) ->
+            # The format button exists.
+            expect(button, 'The imaging profile format button is missing')
+              .to.exist
+            # When the button is clicked, then the imaging profile table
+            # panel is displayed.
             button.click().then ->
               table.isDisplayed().then (displayed) ->
-                expect(displayed, 'The modeling table panel is displayed' +
-                                  ' after the format button is clicked twice')
-                  .to.be.false
-
-    describe 'Chart', ->
-      it 'should display the imaging chart', ->
-        # The chart exists.
-        expect(chart, 'The modeling chart is missing').to.eventually.exist
-        # The chart is displayed.
-        chart.isDisplayed().then (displayed) ->
-          expect(displayed, 'The modeling chart is initially hidden')
-            .to.be.true
-
-    describe 'Table', ->
-      it 'should hide the table', ->
-        # The modeling tables panel exists.
-        expect(table, 'The modeling table panel is missing').to.eventually.exist
-        # The panel is hidden.
-        table.isDisplayed().then (displayed) ->
-          expect(displayed, 'The modeling table is initially displayed')
-            .to.be.false
-
-      describe 'Properties', ->
-        tables = null
-
-        before ->
-          page.imagingProfileTables().then (elt) ->
-            tables = elt
-
-        it 'should show the imaging properties when the format button is clicked', ->
-          # There are three imaging profile tables, one for each
-          # PK modeling property.
-          expect(tables.length, 'The imaging profile table count is incorrect')
-            .to.equal(3)
-
-        it 'should have four Breast patient visits', ->
-          for table, tblNdx in tables
-            # There are four Breast patient visits.
-            expect(table.length, "Table #{ tblNdx + 1 } row count is incorrect")
-              .to.equal(4)
-
-        it 'should show the visit dates', ->
-          # The expected visit dates are the first column of the first table.
-          expected = ((_.first(columns) for columns in _.first(tables)))
-          # The date values for every remaining table should be
-          # consistent with those of the first table.
-          for table, tblNdx in _.rest(tables)
-            for columns, rowNdx in table
-              visitDate = _.first(columns)
-              expect(visitDate, "The table #{ tblNdx + 2 } visit date is incorrect")
-                .to.equal(expected[rowNdx])
-
-        it 'should have seven Ktrans table columns', ->
-          # The KTrans table is the first table.
-          table = _.first(tables)
-          for row, rowNdx in table
-            expect(row.length, "The Ktrans table row #{ rowNdx + 1 } column" +
-                              " count is incorrect").to.equal(7)
-
-        it 'should have three non-Ktrans table columns', ->
-          # The KTrans table is the first table.
-          tables = _.rest(tables)
-          for table, tblNdx in tables
-            for row, rowNdx in table
-              expect(row.length, "The table #{ tblNdx + 2 } row #{ rowNdx + 1 }" +
-                                 " column count is incorrect").to.equal(3)
-
-        it 'should not have a blank non-percent change value', ->
-          for rows, tblNdx in tables
-            for columns, rowNdx in rows
+                expect(displayed, 'The modeling table panel is hidden after the' +
+                                  ' format button is clicked').to.be.true
+              # The chart is hidden.
+              chart.isDisplayed().then (displayed) ->
+                expect(displayed, 'The modeling chart is displayed after the' +
+                                  ' format button is clicked').to.be.false
+              # When the button is clicked again, then the table is hidden and
+              # the chart is displayed.
+              button.click().then ->
+                table.isDisplayed().then (displayed) ->
+                  expect(displayed, 'The modeling table panel is displayed' +
+                                    ' after the format button is clicked twice')
+                    .to.be.false
+
+      describe 'Chart', ->
+        it 'should display the imaging chart', ->
+          # The chart exists.
+          expect(chart, 'The modeling chart is missing').to.eventually.exist
+          # The chart is displayed.
+          chart.isDisplayed().then (displayed) ->
+            expect(displayed, 'The modeling chart is initially hidden')
+              .to.be.true
+
+      describe 'Table', ->
+        it 'should hide the table', ->
+          # The modeling tables panel exists.
+          expect(table, 'The modeling table panel is missing').to.eventually.exist
+          # The panel is hidden.
+          table.isDisplayed().then (displayed) ->
+            expect(displayed, 'The modeling table panel is initially displayed')
+              .to.be.false
+
+        describe 'Properties', ->
+          tables = null
+
+          before ->
+            page.imagingProfileTables().then (elt) ->
+              tables = elt
+
+          it 'should show the imaging properties when the format button is clicked', ->
+            # There are three imaging profile tables, one for each
+            # PK modeling property.
+            expect(tables.length, 'The imaging profile table count is incorrect')
+              .to.equal(3)
+
+          it 'should display the column headers', ->
+            for table, tblNdx in tables
+              for heading, hdgNdx in table.header
+                expect(heading, "Table #{ tblNdx + 1 } is missing heading" +
+                                " #{ hdgNdx + 1 }").to.exist.and.not.be.empty
+
+          it 'should have four Breast patient visits', ->
+            for table, tblNdx in tables
+              rows = table.body
+              for row in rows
+                expect(rows.length, "Table #{ tblNdx + 1 } row count is incorrect")
+                  .to.equal(4)
+
+          it 'should show the visit dates', ->
+            # The expected visit dates are the first column of the first table.
+            referenceTable = _.first(tables)
+            expected = ((_.first(columns) for columns in referenceTable.body))
+            # The date values for every remaining table should be
+            # consistent with those of the first table.
+            for table, tblNdx in _.rest(tables)
+              rows = table.body
+              for columns, rowNdx in rows
+                visitDate = _.first(columns)
+                expect(visitDate, "The table #{ tblNdx + 2 } visit date is incorrect")
+                  .to.equal(expected[rowNdx])
+
+          it 'should have seven Ktrans table columns', ->
+            # The KTrans table is the first table.
+            table = _.first(tables)
+            for row, rowNdx in table.body
+              expect(row.length, "The Ktrans table row #{ rowNdx + 1 } column" +
+                                 " count is incorrect").to.equal(7)
+
+          it 'should have three non-Ktrans table columns', ->
+            # The KTrans table is the first table.
+            tables = _.rest(tables)
+            for table, tblNdx in tables
+              for row, rowNdx in table.body
+                expect(row.length, "The table #{ tblNdx + 2 } row #{ rowNdx + 1 }" +
+                                   " column count is incorrect").to.equal(3)
+
+          it 'should not have a blank non-percent change value', ->
+            for table, tblNdx in tables
+              rows = table.body
+              for columns, rowNdx in rows
+                # The odd columns hold non-percent change values.
+                for colNdx in _.range(1, columns.length, 2)
+                  value = columns[colNdx]
+                  expect(value, "Imaging Profile table #{ tblNdx + 1 } body" +
+                                " row #{ rowNdx + 1 } percent change column" +
+                                " #{ colNdx + 1 } is not blank").to.not.be.empty
+
+          it 'should have blank percent values in the first row', ->
+            for table, tblNdx in tables
+              rows = table.body
+              columns = _.first(rows)
+              # The percent change is in the third, fifth and seventh column.
+              # The percent changes of the first row are blank.
               # The odd columns hold non-percent change values.
-              for colNdx in _.range(1, columns.length, 2)
-                value = columns[colNdx]
-                expect(value, "Imaging Profile table #{ tblNdx + 1 } body" +
-                              " row #{ rowNdx + 1 } percent change column" +
-                              " #{ colNdx + 1 } is not blank").to.not.be.empty
-
-        it 'should have blank percent values in the first row', ->
-          for rows, tblNdx in tables
-            columns = _.first(rows)
-            # The percent change is in the third, fifth and seventh column.
-            # The percent changes of the first row are blank.
-            # The odd columns hold non-percent change values.
-            for colNdx in _.range(2, columns.length, 2)
-              value = columns[colNdx]
-              expect(value, "Imaging Profile table #{ tblNdx + 1 } body" +
-                            " row 0 percent change column #{ colNdx + 1 }" +
-                            " is not blank").to.be.empty
-
-        it 'should not have a blank percent value in the remaining rows', ->
-          for rows, tblNdx in tables
-            for columns, rowNdx in _.rest(rows)
               for colNdx in _.range(2, columns.length, 2)
                 value = columns[colNdx]
                 expect(value, "Imaging Profile table #{ tblNdx + 1 } body" +
-                              " row #{ rowNdx + 2 } percent change column" +
-                              " #{ colNdx + 1 } is blank").to.not.be.empty
-
-  describe 'Clinical Profile', ->
+                              " row 0 percent change column #{ colNdx + 1 }" +
+                              " is not blank").to.be.empty
+
+          it 'should not have a blank percent value in the remaining rows', ->
+            for table, tblNdx in tables
+              rows = table.body
+              for columns, rowNdx in _.rest(rows)
+                for colNdx in _.range(2, columns.length, 2)
+                  value = columns[colNdx]
+                  expect(value, "Imaging Profile table #{ tblNdx + 1 } body" +
+                                " row #{ rowNdx + 2 } percent change column" +
+                                " #{ colNdx + 1 } is blank").to.not.be.empty
+
+    describe 'Clinical Profile', ->
+      panel = null
+
+      before ->
+        page.clinicalProfileTablePanel().then (elt) ->
+          panel = elt
+
+      it 'should show the clinical profile table', ->
+          # The clinical tables panel exists.
+          expect(panel, 'The clinical table panel is missing')
+            .to.eventually.exist
+          # The panel is displayed.
+          panel.isDisplayed().then (displayed) ->
+            expect(displayed, 'The clinical table panel is initially displayed')
+              .to.be.true
+
       describe 'Demographics', ->
-        # Demographics is collection-independent.
-        beforeEach ->
-          page = new SubjectDetailPage '/quip/breast/subject/1?project=QIN_Test'
+        table = null
+
+        before ->
+          panel.demographics().then (elt) ->
+            table = elt
+
+        it 'should show the demographics table', ->
+          expect(table, 'The demographics table is missing')
+            .to.eventually.exist
+          table.isDisplayed().then (displayed) ->
+            expect(displayed, 'The demographics table is not displayed')
+              .to.be.true

-        it 'should show the demographics table', ->
-          page.clinicalProfileTables().then (tables) ->
-            # Validate the demographics values.
-
-
+        # Validate the demographics values.
+        it 'should show the age', ->
+          table.age().then (age) ->
+            expect(age, 'The age is missing').to.exist.and.to.not.be.empty
+            expect(age, 'The age is not an integer').to.match(/^\d+$/)
+            expect(parseInt(age), 'The age is not positive')
+              .to.be.greaterThan(0)
+
+        it 'should show the race', ->
+          table.races().then (races) ->
+            expect(races, 'The race is missing').to.exist.and.to.not.be.empty
+
+        it 'should show the ethnicity', ->
+          table.ethnicity().then (ethnicity) ->
+            expect(ethnicity, 'The ethnicity is missing')
+              .to.exist.and.to.not.be.empty
+
       describe 'Outcomes', ->
-        describe 'Breast', ->
-          beforeEach ->
-            page = new SubjectDetailPage '/quip/breast/subject/1?project=QIN_Test'
-
           describe 'Biopsy', ->
             # TODO - Add test cases for the date and every outcome table.
-
+
           describe 'Assessment', ->
             # TODO - Add test cases for the date and every outcome table.
-
-        describe 'Sarcoma', ->
-          beforeEach ->
-            page = new SubjectDetailPage '/quip/sarcoma/subject/1?project=QIN_Test'
-
-          describe 'Biopsy', ->
-            # TODO - Add test cases for the date and every outcome table.
-
-          describe 'Assessment', ->
-            # TODO - Add test cases for the date and every outcome table.
+
+  describe 'Sarcoma', ->
+    beforeEach ->
+      page = new SubjectDetailPage '/quip/sarcoma/subject/1?project=QIN_Test'
+
+    # The Sarcoma-specific page header test cases.
+    describe 'Header', ->
+      it 'should display the billboard', ->
+        expect(page.billboard, 'The billboard is incorrect')
+          .to.eventually.equal('Sarcoma Patient 1')
+
+    describe 'Biopsy', ->
+      # TODO - Add test cases for the date and every outcome table.
+
+    describe 'Assessment', ->
+      # TODO - Add test cases for the date and every outcome table.


relative filename for example script

relative filename for example script

--- examples/node/notebook.coffee
+++ examples/node/notebook.coffee
@@ -1,10 +1,9 @@
 fs = require 'fs'
 lead = require 'lead.js'
-lead.enable_codemirror()
 notebook = lead.require 'notebook'
 React = require 'react'

-script = fs.readFileSync './examples/random_walks.coffee', encoding: 'utf-8'
+script = fs.readFileSync __dirname + '/../random_walks.coffee', encoding: 'utf-8'

 notebook.create_notebook(imports: ['builtins', 'compat'], module_names: ['graph'])
 .done (nb) ->


Activity: prefetched feed for public feed implemented

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -66,8 +66,14 @@

     id = channelId
     {socialapi} = KD.singletons
+    {socialApiChannelId} = KD.getGroup()

-    socialapi.channel.fetchActivities {id, from}, callback
+    if socialApiChannelId is channelId and socialapi.getPrefetchedData('publicFeed').length > 0
+      messages = socialapi.getPrefetchedData 'publicFeed'
+      KD.utils.defer ->  callback null, messages
+      KD.socialApiData.publicFeed = null
+    else
+      socialapi.channel.fetchActivities {id, from}, callback

   getActiveChannel: -> @getView().sidebar.selectedItem.getData()


Activity: fix removed warning header from activity feed

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -71,9 +71,10 @@
   fetchCurrentGroup:(callback)-> callback @currentGroupSlug

   search:(text)->
-    @setWarning text, yes
+    text = Encoder.XSSEncode text
+    @searchText = text
+    @setWarning {text, loading:yes, type:"search"}
     @populateActivity searchText:text
-

   bindLazyLoad:->
     @once 'LazyLoadThresholdReached', @bound "continueLoadingTeasers"
@@ -89,7 +90,11 @@
     @clearPopulateActivityBindings()

     KD.mixpanel "Scrolled down feed"
-    @populateActivity to : @lastFrom
+    options = {to : @lastFrom}
+
+    if @searchText
+      options.searchText = @searchText
+    @populateActivity options

   attachEvents:(controller)->
     activityController = KD.getSingleton('activityController')
@@ -116,7 +121,7 @@

     if query.tagged
       tag = KD.utils.slugify KD.utils.stripTags query.tagged
-      @setWarning tag, yes
+      @setWarning {text:tag, loading:yes}
       options = filterByTag: tag
     # TODO: populateActivity will fire twice if there is a query (FIXME) C.T.
     @ready => @populateActivity options
@@ -177,38 +182,38 @@
       {roles} = KD.config
       group   = groupObj?.slug

+      if not to and (searchText or @searchText)
+        @resetAll()
+        @clearPopulateActivityBindings()
+        @searchText = searchText
+
+      if searchText? or (@searchText and to)
+        options.searchText ?= @searchText
+        @once "searchFeedFetched_#{eventSuffix}", setFeedData
+        @searchActivities options
+        @setWarning {text:searchText, loading:no, type:"search"}
+        return
+
       if not to and (filterByTag or @_wasFilterByTag)
         @resetAll()
         @clearPopulateActivityBindings()
         @_wasFilterByTag = filterByTag

-      if not to and (searchText or @_wasSearchText)
-        @resetAll()
-        @clearPopulateActivityBindings()
-        @_wasSearchText = searchText
-
-      if searchText? or (@_wasSearchText and to)
-        options.searchText ?= @_wasSearchText
-        @once "searchFeedFetched_#{eventSuffix}", setFeedData
-        @searchActivities options
-        @setWarning searchText, no
-
       if filterByTag? or (@_wasFilterByTag and to)
-
         options.slug ?= @_wasFilterByTag
         @once "topicFeedFetched_#{eventSuffix}", setFeedData
         @fetchTopicActivities options
-        @setWarning options.slug
+        @setWarning {text:options.slug}
         view.setTopicTag options.slug
+        return

       else if @getFeedFilter() is "Public"
-
         @once "publicFeedFetched_#{eventSuffix}", setFeedData
         @fetchPublicActivities options
         @setWarning()
+        return

       else
-
         @once "followingFeedFetched_#{eventSuffix}", setFeedData
         @fetchFollowingActivities options
         @setWarning()
@@ -272,19 +277,20 @@
       then @emit "activitiesCouldntBeFetched", err
       else @emit "followingFeedFetched_#{eventSuffix}", activities

-  setWarning:(tag, loading = no)->
+  setWarning:(options = {})->
+    options.type or= "tag"
+    {text, loading, type} = options
     {filterWarning} = @getView()
-    if tag
+    if text
       unless loading
-        filterWarning.showWarning tag
+        filterWarning.showWarning {text, type}
       else
-        filterWarning.warning.setPartial "Filtering activities by #{tag}..."
+        filterWarning.warning.setPartial "Filtering activities by #{text}..."
         filterWarning.show()
     else
       filterWarning.hide()

   setLastTimestamps:(from, to)->
-
     if from
       @lastTo   = to
       @lastFrom = from


Fix React key unicity

Fix React key unicity

--- client/app/components/message_header.coffee
+++ client/app/components/message_header.coffee
@@ -143,12 +143,12 @@
             div className: 'popup', 'aria-hidden': not @state.showDetails,
                 table null,
                     tbody null,
-                        row 'from', @formatUsers(from), 'headers from'
-                        row 'to',   @formatUsers(to[0]), 'headers to', to.length if to.length
-                        row 'dest', @formatUsers(dest) for dest in to[1..] if to.length
-                        row 'cc',   @formatUsers(cc[0]), 'headers cc', cc.length if cc.length
-                        row 'dest', @formatUsers(dest) for dest in cc[1..] if cc.length
-                        row 'reply', @formatUsers(reply), 'headers reply-to' if reply?
+                        row 'from',    @formatUsers(from), 'headers from'
+                        row 'to',      @formatUsers(to[0]), 'headers to', to.length if to.length
+                        row 'destTo',  @formatUsers(dest) for dest in to[1..] if to.length
+                        row 'cc',      @formatUsers(cc[0]), 'headers cc', cc.length if cc.length
+                        row 'destCc',  @formatUsers(dest) for dest in cc[1..] if cc.length
+                        row 'reply',   @formatUsers(reply), 'headers reply-to' if reply?
                         row 'created', @props.message.get('createdAt'), 'headers date'
                         row 'subject', @props.message.get('subject'), 'headers subject'


Improve coverage boilerplate

Improve coverage boilerplate

--- src/test/coverage.coffee
+++ src/test/coverage.coffee
@@ -11,9 +11,11 @@
     spec: undefined
     globals: config.globals
     globalsDebug: config.globalsDebug
+    base: undefined
   }
   (options) ->
     options.spec ?= path.join(options.name, '*.{js,coffee}')
+    packageRoot = util.findPackageRoot()

     suite(options.name, () ->
       setup(() ->
@@ -26,7 +28,7 @@
           if not filePath?
             continue
           if (filePath[0] == '.')
-            global[name] = require(path.join(options.base, filePath))
+            global[name] = require(path.join(packageRoot, filePath))
           else
             global[name] = require(filePath)


Teambuilder: Render hidden powers correctly on import.

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -42,10 +42,9 @@
     )

     @listenTo(@collection, 'add remove', @renderPokemonList)
+    @listenTo(@collection, 'reset', @render)

     @loadTeam()
-
-    @render()

   clickPokemon: (e) =>
     $listItem = $(e.currentTarget)
@@ -60,6 +59,7 @@
       @collection.reset(teamJSON)
     else
       @addNewPokemon()  for i in [1..6]
+      @render()

   addEmptyPokemon: =>
     @collection.add(new Pokemon(teambuilder: true))
@@ -279,8 +279,8 @@
     $modal.on 'click', '.import-team-submit', (e) =>
       teamString = $modal.find('.imported-team').val()
       teamJSON = PokeBattle.parseTeam(teamString)
+      pokemonJSON.teambuilder = true  for pokemonJSON in teamJSON
       @collection.reset(teamJSON)
-      @render()
       $modal.modal('hide')
       return false
     $modal.modal('show')


Don't execute the "focusTab" method

Don't execute the "focusTab" method

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -79,7 +79,7 @@

     @tabView.on 'PaneDidShow', =>
       @updateStatusBar()
-      @focusTab()
+      @focusTab()  unless frontApp.isChatInputFocused()

     @tabView.on 'PaneAdded', (pane) =>


Fix saveAs for multiple VMs

Fix saveAs for multiple VMs

--- client/landing/app/MainApp/fs/fsfile.coffee
+++ client/landing/app/MainApp/fs/fsfile.coffee
@@ -32,15 +32,19 @@

   saveAs:(contents, name, parentPath, callback)->

+    @vmName = FSHelper.getVMNameFromPath parentPath  if parentPath
     newPath = FSHelper.plainPath "#{parentPath}/#{name}"
     @emit "fs.saveAs.started"

-    FSHelper.ensureNonexistentPath "#{newPath}", @vmName, (err, response)=>
+    FSHelper.ensureNonexistentPath "#{newPath}", @vmName, (err, path)=>
       if err
-        callback? err, response
+        callback? err, path
         warn err
       else
-        newFile = FSHelper.createFileFromPath response
+        newFile = FSHelper.createFile
+          type   : 'file'
+          path   : path
+          vmName : @vmName
         newFile.save contents, (err, res)=>
           if err then warn err
           else


minor change

minor change

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -396,10 +396,10 @@

           if err
             @credentialWarning.once 'transitionend', =>
-              if @credentialWarning.getAttribute('class').indexOf(' in') > 0
+              if @credentialWarning.hasClass 'in'
                 @credentialWarning.tooltip.show()

-            if @credentialWarning.getAttribute('class').indexOf(' in') > 0
+            if @credentialWarning.hasClass 'in'
               @credentialWarning.tooltip.show()
             else
               @providersPane.tabHandle.setClass 'notification'


Change deploy:pending output.

Change deploy:pending output.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -17,7 +17,7 @@

     pending = pulsarApi.createJob(app, env, 'deploy:pending')
     pending.on('close', ()->
-      chat.send @data.output
+      chat.send "Pending changes for #{@app} #{@env}: #{@data.stdout}"
       return if(@data.status != 'FINISHED')
       deploy = pulsarApi.createJob(app, env, 'deploy')
       deploy.on('create', () ->
@@ -41,8 +41,8 @@
   robot.respond /deploy pending ([^\s]+) ([^\s]+)$/i, (chat) ->
     return unless robot.isAuthorized(chat)
     job = pulsarApi.createJob(chat.match[1], chat.match[2], 'deploy:pending')
-    job.on('change', (output) ->
-      chat.send output
+    job.on('close', () ->
+      chat.send "Pending changes for #{@app} #{@env}: #{@data.stdout}"
     ).on('error', (error)->
       chat.send "#{@} failed due to #{JSON.stringify(error)}"
     )


Fix for rename/move while offline

Fix for rename/move while offline

--- src/sync.coffee
+++ src/sync.coffee
@@ -224,7 +224,9 @@
             when @moveFrom
                 [from, @moveFrom] = [@moveFrom, null]
                 if from.moveTo is doc._id
-                    side.moveFile doc, from, callback
+                    side.moveFile doc, from, (err) =>
+                        @moveFrom = from if err
+                        callback err
                 else
                     log.error "Invalid move"
                     log.error from
@@ -262,7 +264,9 @@
             when @moveFrom
                 [from, @moveFrom] = [@moveFrom, null]
                 if from.moveTo is doc._id
-                    side.moveFolder doc, from, callback
+                    side.moveFolder doc, from, (err) =>
+                        @moveFrom = from if err
+                        callback err
                 else
                     log.error "Invalid move"
                     log.error from


sync: fix AffiliationsSynchronization typo

--- src/sync.coffee
+++ src/sync.coffee
@@ -110,7 +110,7 @@

     writeResults: (t, results, cb) ->
         async.forEach results, (item, cb2) =>
-            t.setAffiliation @node, item.user, item.affiliations, cb2
+            t.setAffiliation @node, item.user, item.affiliation, cb2
         , cb


Add internal_directRequest

Add internal_directRequest

--- src/teak.coffee
+++ src/teak.coffee
@@ -215,6 +215,23 @@
         )
       )

+  internal_directRequest: (carrotResponse, callback, postMethod) ->
+    if !postMethod? && FB?
+      postMethod = FB.ui
+    if carrotResponse.code == 200
+        postMethod(carrotResponse.fb_data,
+          (fbResponse) =>
+            @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{carrotResponse.request_id}/ids", {platform_id: fbResponse.request})
+            if fbResponse && fbResponse.to
+              for receivingUser in fbResponse.to
+                @ajaxPost("#{@scheme}://parsnip.gocarrot.com/request_send", {platform_id: carrotResponse.request_id, posting_user_id: @udid, user_id: receivingUser})
+
+            callback(carrotResponse, fbResponse) if callback
+        )
+      else
+        callback(carrotResponse) if callback
+
+
   acceptRequest: (requestId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{requestId}/clicks", {clicking_user_id: @udid, sig: "s"},
       (jqXHR) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -103,8 +103,7 @@

   Engine = (I={}) ->
     Object.reverseMerge I,
-      FPS:
-      age: 0
+      FPS: 60
       paused: false
       showFPS: false
       zSort: false


adding description for morphisms

adding description for morphisms

--- scripts/morphism.coffee
+++ scripts/morphism.coffee
@@ -8,7 +8,16 @@
 #   None
 #
 # Commands:
+#   hubot define homomorphism
+#   hubot define isomorphism
+#   hubot define monomorphism
+#   hubot define endomorphism
+#   hubot define automorphism
 #   hubot define catamorphism
+#   hubot define anamorphism
+#   hubot define hylomorphism
+#   hubot define paramorphism
+#   hubot define polymorphism
 #
 # Author:
 #   Mateusz Kopij <sagasu>


adding Chris excuse

adding Chris excuse

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -15,10 +15,25 @@
 #   hubot who will unleash righteous vengeance upon evil
 #   hubot who will be triumphant
 #   hubot what is Chris doing today
+#   hubot Chris excuse
 #
 # Author:
 #   Mateusz Kopij <sagasu>

+chrisExcuse = [
+  "Sachin makes me feel uncomfortable",
+  "It's your smell",
+  "The world is becoming a computer, so am I",
+  "I focus on privacy today, you don't have an access.",
+  "I am protecting the democracies today. Priorities matter.",
+  "I will be working with ethical AI team. Make sure that it parties like it should, otherwise it wouldn't be ethical.",
+  "I am empowering the team to be more self organized, and take decisions on their own.",
+  "That it the price of innovation and be `zuper` creative.",
+  "Microsoft is pushing Azure project I am pushing my project today.",
+  "I will be creating tickets in JIRA -> sorry TFS for DGI drones and Azuresphere",
+  "I will be checking the quality of products on my favorite hub site. The world needs my input.",
+  "Yesterday Sachin showed me his drone, I am still meditating on that."
+]

 offshorepics = [
   "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/OffshoreTeam2.jpg",
@@ -31,6 +46,9 @@

   robot.respond /offshore team/i, (msg)->
     msg.send msg.random offshorepics
+
+  robot.respond /Chris excuse/i, (msg)->
+    msg.send msg.random chrisExcuse

   robot.respond /sachin nsfw/i, (msg)->
     msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/offshore_gone_wrong.png"


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,7 +1,7 @@
 ###*
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object. This method
-relies on both objects having `bounds` methods. GameObject
+relies on both objects having `position` methods. GameObject
 provides this method by default

 <code><pre>


small code refactor

small code refactor

--- src/view.coffee
+++ src/view.coffee
@@ -134,61 +134,63 @@
             c "not implemented #1"
             c result
         new Sirius.Observer(current, clb)
-      else # then it Sirius.Model
-        #TODO refactor this
+      else # then it's Sirius.Model
         children = adapter.all("#{current} *")
-        if children.length == 0
+        count    = children.length
+
+        # before
+        if count == 0
           # then it single element and we need extract data-bind-to, data-bind-from and name
-          data_bind_to   = adapter.get_attr(current, 'data-bind-to') || adapter.get_attr(current, 'name')
-          data_bind_from = adapter.get_attr(current, 'data-bind-from')
-
-          if to && data_bind_to
+          tmp_to   = adapter.get_attr(current, 'data-bind-to') || adapter.get_attr(current, 'name')
+          tmp_from = adapter.get_attr(current, 'data-bind-from')
+
+          if to && tmp_to
             c "You define `to` attribute twice"

-          if from && data_bind_from
-            c "You define `from` attribute twice: #{from}, #{data_bind_from}"
-
-          if !data_bind_to && !to
+          if from && tmp_from
+            c "You define `from` attribute twice"
+
+          if !tmp_to && !to
             c "Error# need pass `to` attribute into `.bind` method or define `data-bind-to` or `name` into html element code"

-          to   = to   ? data_bind_to
-          from = from ? data_bind_from
-
-          # check if attribute present into model class
-          if klass.attributes.indexOf(to) == -1
-            c "Error attribute #{to} not exist in model class #{klass}"
-
-          clb = (result) ->
-            txt = result['text']
-            if txt && !from
-              klass[to](txt)
-            if from == result['attribute']
-              klass[to](txt)
-
-
-          new Sirius.Observer(current, clb)
+          to   = to   ? tmp_to
+          from = from ? tmp_from

         else
           if to || from
             c "Error, `to` or `from` which pass into `bind` method, not taken use `data-bind-to` or `name` and `data-bind-from`"

-          for child in children
-            do(child) ->
-              ddata_bind_to   = adapter.get_attr(child, 'data-bind-to') || adapter.get_attr(child, 'name')
-              ddata_bind_from = adapter.get_attr(child, 'data-bind-from')
-
-              if ddata_bind_to
-                clb = (result) ->
-                  txt = result['text']
-                  if txt && !ddata_bind_from
-                    klass[ddata_bind_to](txt)
-                  if ddata_bind_from == result['attribute']
-                    klass[ddata_bind_to](txt)
-
-
-                new Sirius.Observer(child, clb)
-
-
+        # check if attribute present into model class
+        if klass.attributes.indexOf(to) == -1
+          c "Error attribute #{to} not exist in model class #{klass}"
+
+        # when only one element in collection need wrap his in array
+        children = if count == 0
+          [current]
+        else
+          children
+
+        for child in children
+          do(child) ->
+            data_bind_to = if count == 0
+              to
+            else
+              adapter.get_attr(child, 'data-bind-to') || adapter.get_attr(child, 'name')
+
+            data_bind_from = if count == 0
+              from
+            else
+              adapter.get_attr(child, 'data-bind-from')
+
+            if data_bind_to
+              clb = (result) ->
+                txt = result['text']
+                if txt && !data_bind_from
+                  klass[data_bind_to](txt)
+                if data_bind_from == result['attribute']
+                  klass[data_bind_to](txt)
+
+              new Sirius.Observer(child, clb)

     else
       if Sirius.Utils.is_function(klass)


Add LRSView.eachView to iterate all subviews (direct children), removing duplicate code

--- src/view.coffee
+++ src/view.coffee
@@ -47,11 +47,9 @@
 		super

 	initializeViews: ->
-		for name, view of @views
-			if Array.isArray(view)
-				v.initialize() for v in view
-			else
-				view.initialize()
+		@eachView( (view) ->
+			view.initialize()
+		)

 	_loadTemplate: ->
 		@el = $(LRSView.templates[@template])
@@ -249,11 +247,9 @@
 		@enabled = true
 		@removeClass('disabled') if updateClass
 		if recursive is true
-			for viewName, view of @views
-				if Array.isArray(view)
-					v.enable(recursive, updateClass) for v in view
-				else
-					view.enable(recursive, updateClass)
+			@eachView( (view) ->
+				view.enable(recursive, updateClass)
+			)

 		@

@@ -261,11 +257,10 @@
 		@enabled = false
 		@addClass('disabled') if updateClass
 		if recursive is true
-			for viewName, view of @views
-				if Array.isArray(view)
-					v.disable(recursive, updateClass) for v in view
-				else
-					view.disable(recursive, updateClass)
+			@eachView( (view) ->
+				view.disable(recursive, updateClass)
+			)
+
 		@

 	show: ->
@@ -281,6 +276,13 @@
 		@

 	hideAction: @::hide
+
+	eachView: (func) ->
+		for viewName, view of @views
+			if Array.isArray(view)
+				func(v) for v in view
+			else
+				func(view)

 class lrs.LRSView.views.LRSListView extends lrs.LRSView


:memo: Mark Keymap class as public

:memo: Mark Keymap class as public

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -9,19 +9,23 @@

 Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']

-# Internal: Associates keymaps with actions.
-#
-# Keymaps are defined in a CSON format. A typical keymap looks something like this:
+# Public: Associates keybindings with commands.
+#
+# An instance of this class is always available as the `atom.keymap` global.
+#
+# Keymaps are defined in a CSON/JSON format. A typical keymap looks something
+# like this:
 #
 # ```cson
 # 'body':
-#  'ctrl-l': 'package:do-something'
-#'.someClass':
-#  'enter': 'package:confirm'
+#   'ctrl-l': 'package:do-something'
+# '.someClass':
+#   'enter': 'package:confirm'
 # ```
 #
-# As a key, you define the DOM element you want to work on, using CSS notation. For that
-# key, you define one or more key:value pairs, associating keystrokes with a command to execute.
+# As a key, you define the DOM element you want to work on, using CSS notation.
+# For that key, you define one or more key:value pairs, associating keystrokes
+# with a command to execute.
 module.exports =
 class Keymap
   Emitter.includeInto(this)


matrix

matrix

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -176,9 +176,7 @@
     @name transformPoint
     @methodOf Matrix#
     @see #deltaTransformPoint
-
-    @returns A new point with the transformation applied.
-    @type Point
+    @returns {Point} A new point with the transformation applied.
     ###
     transformPoint: (point) ->
       Point(


Store turn and game state including symbol side.

--- ai.coffee
+++ ai.coffee
@@ -3,6 +3,7 @@
 boards = []
 top_board = []
 subboard_next = []
+turn = 1 # arbitrarily 1 is x, -1 is o
 ais = {}

 zero_array = (size) ->
@@ -40,8 +41,9 @@
 add_move = (x,y) ->
 	subboard_index = get_subboard(x,y)
 	square_index = get_square_on_subboard(x,y)
-	boards[subboard_index][square_index] = true
+	boards[subboard_index][square_index] = turn
 	subboard_next.push square_index
+	turn = turn * -1
 	return

 ##


Updated Server-Logic (push feeds to client)

--- gulp/development/webserver.coffee
+++ gulp/development/webserver.coffee
@@ -1,11 +1,7 @@
-# webserver =  require 'gulp-webserver'
-
 express   = require('express')
-# path      = require('path')
 app       = express()
 http      = require('http').Server(app)
 io        = require('socket.io')(http)
-

 module.exports = (gulp) ->
   gulp.task 'build:webserver', ->
@@ -18,19 +14,49 @@
     http.listen 61337, ->
       console.log "yea listengin!"

-    userAmount = 0
+
+    feeds = [
+      {
+        caption: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
+        image: 'http://localhost:61337/images/dummy_00.png'
+        description: 'Vestibulum id ligula porta felis euismod semper. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.'
+      },{
+        caption: 'Aenean lacinia bibendum nulla sed consectetur.'
+        image: 'http://localhost:61337/images/dummy_01.png'
+        description: 'Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Sed posuere consectetur est at lobortis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.'
+      },{
+        caption: 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum ma.'
+        image: 'http://localhost:61337/images/dummy_02.png'
+        description: 'Aenean lacinia bibendum nulla sed consectetur. Vestibulum id ligula porta felis euismod semper. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Nulla vitae elit libero, a pharetra augue. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nulla vitae elit libero, a pharetra augue.'
+      },{
+        caption: 'Vestibulum id ligula porta felis euismod semper.'
+        image: 'http://localhost:61337/images/dummy_03.png'
+        description: 'Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nulla vitae elit libero, a pharetra augue. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Donec sed odio dui.'
+      },{
+        caption: 'Nullam quis risus eget urna mollis ornare vel eu leo.'
+        image: 'http://localhost:61337/images/dummy_04.png'
+        description: 'Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Etiam porta sem malesuada magna mollis euismod. Donec ullamcorper nulla non metus auctor fringilla. Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit.'
+      },{
+        caption: 'Sed posuere consectetur est at lobortis.'
+        image: 'http://localhost:61337/images/dummy_05.png'
+        description: 'Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Cras mattis consectetur purus sit amet fermentum. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Maecenas faucibus mollis interdum. Sed posuere consectetur est at lobortis.'
+      }
+    ]
     io.on 'connection', (socket) ->
-      ++userAmount
       console.log "User Connected"
+      sent = 0
+      # Update User-Counter
+      io.emit 'feedUpdate', feeds[sent]

-      # Update User-Counter
-      io.emit 'userAmount', userAmount
+      socket.on 'disconnect', ->
+        console.log "User disconnected"
+
+      socket.on 'updateFeed', (msg) ->
+        ++sent
+        io.emit 'feedUpdate', feeds[sent]

-      socket.on 'disconnect', ->
-        --userAmount
-        io.emit 'userAmount', userAmount
-        console.log "User disconnected"

-      socket.on 'yeah', (msg) ->
-        io.emit 'yeah', "User pressed Button with msg #{msg}"
+
+
+


Added feedparser

Added feedparser

--- gulp/development/webserver.coffee
+++ gulp/development/webserver.coffee
@@ -10,21 +10,37 @@
       res.sendFile(__projectdir + '/index.html')

     app.get '/feeds.json', (req, res) ->
+      feedsCollection = []
       FeedParser = require("feedparser")
       request = require("request")
       req = request("http://feeds.wired.com/wired/index")
       feedparser = new FeedParser()
       req.on "error", (error) ->
-      feedsCollection = []

-      feedparser.on "end", (error) ->
+
+      # handle any request errors
+      req.on "response", (res) ->
+        stream = this
+        return @emit("error", new Error("Bad status code"))  unless res.statusCode is 200
+        stream.pipe feedparser
+        return
+
+      feedparser.on "end", ->
+        console.log "end"
+        console.log feedsCollection
         io.emit 'feedUpdate', feedsCollection

+      feedparser.on "error", (error) ->
+
+
+      # always handle errors
       feedparser.on "readable", ->
+        # This is where the action is!
         stream = this
+        meta = @meta # **NOTE** the "meta" is always available in the context of the feedparser instance
         item = undefined
-        feedsCollection.push item while item = stream.read()
-
+        while item = stream.read()
+          feedsCollection.push item

     app.use( express.static(__projectdir) )


KDView: fix warning message about deprecated KDView.appendToDOMBody usage

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -59,7 +59,7 @@
       @utils.defer => @emit "viewAppended"

   @appendToDOMBody = (view) ->
-    console.warn "KDView.viewAppended is deprecated; use #viewAppended instead"
+    console.warn "KDView.appendToDOMBody is deprecated; use #appendToDomBody instead"
     view.appendToDomBody()

 # #


diff_patch_match done.

diff_patch_match done.

--- client/landing/app/Applications/Shell.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Shell.kdapplication/AppController.coffee
@@ -46,6 +46,7 @@
         lastScreen = @screenHistory[@screenHistory.length-1]
         currentScreen = (@dmp.patch_apply diff,lastScreen)[0]
         @screenHistory.push currentScreen
+        @screenHistory.shift() if @screenHistory.length = 50
         @getView().updateScreen(currentScreen)
       @_lastMessageProcessed = i-1


Both sidebar views don't need a user model any more.

--- core/app/backbone/modules/sidebar.coffee
+++ core/app/backbone/modules/sidebar.coffee
@@ -9,18 +9,14 @@
   Sidebar.showForTopicsAndActivateCorrectItem = (topic, user)->
     @setUsernameOnWindowChannels(user)
     currentUser.favourite_topics.fetch()
-    @sidebarView = new TopicSidebarView
-      collection: currentUser.favourite_topics
-      model: user
+    @sidebarView = new TopicSidebarView collection: currentUser.favourite_topics
     FactlinkApp.leftMiddleRegion.show @sidebarView

     @activateTopic topic

   Sidebar.showForChannelsAndActivateCorrectItem = (channels, currentChannel, user) ->
     @setUsernameOnWindowChannels(user)
-    channelCollectionView = new ChannelsView
-      collection: channels
-      model: user
+    channelCollectionView = new ChannelsView collection: channels
     FactlinkApp.leftMiddleRegion.show(channelCollectionView)

     channelCollectionView.setActiveChannel(currentChannel)


ENHANCE: Use number of last group to decide placeholder num

--- src/app/groups/group-selector/group-selector.coffee
+++ src/app/groups/group-selector/group-selector.coffee
@@ -85,7 +85,7 @@
       if _.isEmpty($scope.selectedGroupSet.groups)
         $scope.newGroupNamePlaceholder = "Group 1"
       else if _.last($scope.selectedGroupSet.groups)?.name.match(/\d+$/)?
-        $scope.newGroupNamePlaceholder = "Group #{$scope.selectedGroupSet.groups.length}"
+        $scope.newGroupNamePlaceholder = "Group #{_.last($scope.selectedGroupSet.groups).number + 1}"
       else
         $scope.newGroupNamePlaceholder = "Enter New Group Name..."
       $scope.newGroupName = null


Update expected behavior

Update expected behavior

--- test/src/system/block_formatting_test.coffee
+++ test/src/system/block_formatting_test.coffee
@@ -315,7 +315,7 @@
             document = getDocument()
             assert.equal document.getBlockCount(), 2
             assert.blockAttributes([0, 3], ["heading1"])
-            assert.blockAttributes([3, 4], [])
+            assert.blockAttributes([3, 4], ["heading1"])
             expectDocument("ab\nc\n")

   test "breaking out of middle of heading block with preceding blocks", (expectDocument) ->
@@ -336,7 +336,7 @@
       assert.blockAttributes([0, 1], ["heading1"])
       assert.blockAttributes([2, 3], [])
       assert.blockAttributes([4, 5], ["heading1"])
-      assert.blockAttributes([6, 7], [])
+      assert.blockAttributes([6, 7], ["heading1"])
       expectDocument("a\nb\nc\nd\n")

   test "inserting newline before heading", (done) ->


Minor updates

Minor updates

--- views/components/main/parts/kdock-panel.cjsx
+++ views/components/main/parts/kdock-panel.cjsx
@@ -59,7 +59,7 @@
           kdock.api_item4
           kdock.api_item5
         ]
-        @completeTime = if kdock.api_state is 2 then kdock.api_complete_time else 0
+        @completeTime = kdock.api_complete_time

 getMaterialImage = (idx) ->
   path = join(ROOT, 'assets', 'img', 'material', "0#{idx}.png")
@@ -70,7 +70,7 @@
 KdockPanel = React.createClass
   canNotify: false
   getInitialState: ->
-    docks: new Array(5).fill(0).map () -> new KDockInfo
+    docks: [1..5].map () -> new KDockInfo
   handleResponse: (e) ->
     {method, path, body, postBody} = e.detail
     {$ships} = window


Some more tweaks

Some more tweaks

--- framer/SVGBaseLayer.coffee
+++ framer/SVGBaseLayer.coffee
@@ -10,7 +10,7 @@
 	return value unless sizeProp?
 	layerSize = layer[sizeProp]
 	parentSize = layer.parent[sizeProp]
-	return value unless layerSize > 0 and parentSize > 0
+	return value unless layerSize >= 0 and parentSize > 0
 	return (layerSize / parentSize) * value

 class exports.SVGBaseLayer extends Layer
@@ -87,13 +87,17 @@
 		@_parent = options.parent
 		delete options.parent
 		delete options.element
+		if @_parent instanceof SVGLayer
+			@_stylesAppliedToParent = ["webkitTransform", "webkitTransformOrigin"]
+			for prop in ["x", "y", "z", "scaleX", "scaleY", "scaleZ", "scale", "skewX", "skewY", "skew", "rotationX", "rotationY", "rotationZ", "force2d", "originX", "originY"]
+				options[prop] ?= @_parent[prop]

 		pathProperties = ["fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "name", "opacity"]
 		_.defaults options, @constructor.attributesFromElement(pathProperties, element)
 		if @_element.transform.baseVal.numberOfItems > 0
 			options.x ?= 0
 			options.y ?= 0
-			options.rotation ?= 0
+			options.rotationZ ?= 0
 			indicesToRemove = []
 			pixelMultiplier = Framer?.CurrentContext.pixelMultiplier ? 1
 			for i in [0...@_element.transform.baseVal.numberOfItems]
@@ -106,18 +110,12 @@
 						indicesToRemove.push(i)
 					when 4 #SVG_TRANSFORM_ROTATE
 						# We willingly ignore the translation from this matrix
-						options.rotation += - (Math.atan2(matrix.c, matrix.d) / Math.PI) * 180
+						options.rotationZ += - (Math.atan2(matrix.c, matrix.d) / Math.PI) * 180
 						indicesToRemove.push(i)

 			for index in indicesToRemove.reverse()
 				@_element.transform.baseVal.removeItem(index)
-
 		@calculateSize()
-		if @_parent instanceof SVGLayer
-			@_stylesAppliedToParent = ["webkitTransform"]
-			for prop in ["x", "y", "z", "scaleX", "scaleY", "scaleZ", "scale", "skewX", "skewY", "skew", "rotationX", "rotationY", "rotationZ", "force2d"]
-				options[prop] ?= @_parent[prop]
-
 		super(options)

 		for parent in @ancestors()


Typos in route.cson

Typos in route.cson


bug fix

bug fix


Revet to not loading documents with unsupported protocols

--- src/zombie/window.coffee
+++ src/zombie/window.coffee
@@ -34,6 +34,9 @@
 module.exports = ({ browser, params, encoding, history, method, name, opener, parent, referer, url })->
   name  ||= ""
   url   ||= "about:blank"
+
+  unless /^(about|javascript|http|https|file):/i.test(url)
+    throw new Error("Cannot load resource #{url}, unsupported protocol")

   window = createWindow(HTML)
   global = window.getGlobal()
@@ -362,9 +365,6 @@
   if method == "POST"
     headers =
       "content-type": encoding || "application/x-www-form-urlencoded"
-
-  # Update document location based on the requested URL
-  history.updateLocation(window, url)

   # Let's handle the specifics of each protocol
   { protocol, pathname } = URL.parse(url)


Home: registration form validation improvements

--- client/Main/CommonViews/login/registerform.coffee
+++ client/Main/CommonViews/login/registerform.coffee
@@ -1,6 +1,9 @@
 class RegisterInlineForm extends LoginViewInlineForm

+  EMAIL_VALID    = no
+  USERNAME_VALID = no
   ENTER          = 13
+
   constructor:(options={},data)->
     super options, data

@@ -11,16 +14,20 @@
         testPath      : "register-form-email"
         validate      : @getEmailValidator()
         decorateValidation: no
+        focus         : => @email.icon.unsetTooltip()
         keyup         : (event)   => @submitForm event  if event.which is ENTER

+
+
+    @username?.destroy()
     @username = new LoginInputViewWithLoader
       inputOptions       :
         name             : "username"
         forceCase        : "lowercase"
         placeholder      : "username"
-        # defaultValue     : "gokmen-#{random}"
         testPath         : "register-form-username"
-        keyup            : =>
+        focus            : => @username.icon.unsetTooltip()
+        keyup            : (event) =>

           if (val = @username.input.getValue()).trim() isnt ''
             @domain.updatePartial "#{val}.koding.io"
@@ -28,6 +35,7 @@
             @domain.updatePartial "username.koding.io"

           @submitForm event  if event.which is ENTER
+
         validate         :
           container      : this
           rules          :
@@ -50,12 +58,16 @@

     {buttonTitle} = @getOptions()

+    @button?.destroy()
     @button = new KDButtonView
       title         : buttonTitle or 'Create account'
-      type          : 'submit'
+      type          : 'button'
       style         : 'solid green medium'
       loader        : yes
       callback      : @bound 'submitForm'
+
+
+

     @invitationCode = new LoginInputView
       cssClass      : 'hidden'
@@ -99,13 +111,17 @@
           if err
             if response?.kodingUser
               input.setValidationResult "usernameCheck", "Sorry, \"#{name}\" is already taken!"
+              USERNAME_VALID = no
           else
             if forbidden
               input.setValidationResult "usernameCheck", "Sorry, \"#{name}\" is forbidden to use!"
+              USERNAME_VALID = no
             else if kodingUser
               input.setValidationResult "usernameCheck", "Sorry, \"#{name}\" is already taken!"
+              USERNAME_VALID = no
             else
               input.setValidationResult "usernameCheck", null
+              USERNAME_VALID = yes

   getEmailValidator: ->
@@ -125,9 +141,11 @@
             if err then warn err
             else
               if response
-                input.setValidationResult "available", null
+                input.setValidationResult 'available', null
+                EMAIL_VALID = yes
               else
-                input.setValidationResult "available", "Sorry, \"#{email}\" is already in use!"
+                input.setValidationResult 'available', "Sorry, \"#{email}\" is already in use!"
+                EMAIL_VALID = no
         return
     messages    :
       required  : 'Please enter your email address.'


UserEnvironmentDataProvider: Fix getIDEFromUId.

:facepalm:

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -190,7 +190,7 @@

     return null  unless IDE

-    for i in IDE.instances when i.mountedMachineUId
+    for i in IDE.instances when i.mountedMachineUId is uid
       instance = i
       break


(IE) Fix test for IE8 which has explicit text nodes

--- tests/batman/view/formfor_rendering_test.coffee
+++ tests/batman/view/formfor_rendering_test.coffee
@@ -29,8 +29,9 @@

   node = helpers.render source, context, (node) =>
     $('input', node).val('new name')
-    # FIXME passes in IE8 with childNodes[0] because it doesn't have Text nodes
-    helpers.triggerChange(node[0].childNodes[1])
+    # IE8 inserts explicit text nodes
+    childNode = if node[0].childNodes[1].nodeName != '#text' then node[0].childNodes[1] else node[0].childNodes[0]
+    helpers.triggerChange(childNode)
     delay =>
       equals @User.lastInstance.name, "new name"


remove content collection class

remove content collection class

--- lib/content_collection.coffee
+++ lib/content_collection.coffee
@@ -1,25 +0,0 @@
-fs = require('fs')
-regex = require('../lib/regex')
-Content = require('./content')
-
-class ContentCollection
-  constructor: (files) ->
-    @parent_dir = files[0].parentDir
-    @files = files
-    @load_files()
-
-  load_files: ->
-    results = []
-    @files.forEach (f) =>
-      contents = fs.readFileSync(f.fullPath, 'utf8')
-      order = parseInt(contents.match(regex.order)[0].substring(7))
-      results.push(new Content(
-        order: order
-        name: f.name
-        path: f.fullPath
-        contents: contents
-      ))
-
-    @collection = results.sort (a, b) -> a.order - b.order
-
-module.exports = ContentCollection


Rename to MatchView

Rename to MatchView

--- lib/project/match-view.coffee
+++ lib/project/match-view.coffee
@@ -4,7 +4,7 @@
 removeLeadingWhitespace = (string) -> string.replace(LeadingWhitespace, '')

 module.exports =
-class SearchResultView extends View
+class MatchView extends View
   @content: ({filePath, match}) ->
     range = Range.fromObject(match.range)
     matchStart = range.start.column - match.lineTextOffset


fix a bug 在 nodejs 版本和 atom 的版本更新后，这里不注释的话，使用的时候会出现错误

--- lib/project/scaffolder.coffee
+++ lib/project/scaffolder.coffee
@@ -31,9 +31,9 @@
         reject("请在atom中设置git的路径")

-
-    cp.stdout.on 'data', (data) -> notify out: data.toString()
-    cp.stderr.on 'data', (data) -> notify out: data.toString()
+    # TODO 在 nodejs 版本和 atom 的版本更新后，这里不注释的话，使用的时候会出现错误
+    # cp.stdout.on 'data', (data) -> notify out: data.toString()
+    # cp.stderr.on 'data', (data) -> notify out: data.toString()

 #
 # options:


Remove unused require

Remove unused require

--- lib/snippets-available.coffee
+++ lib/snippets-available.coffee
@@ -1,5 +1,3 @@
-_ = require 'underscore-plus'
-
 {SelectListView, View} = require 'atom'

 module.exports =


minors

minors

--- library/nucleus/zombie.coffee
+++ library/nucleus/zombie.coffee
@@ -79,9 +79,9 @@
     # although there is no guarantee that this handle is initialized!
     @obtain: (kernel, callback) ->
         instantiated = => _.has @, "instance"
-        return @instance if instantiated()
-        internal = "an internal zombie error"
-        assert _.isFunction lazy = @lazy()
+        return @instance if (try instantiated())
+        internal = "got an internal zombie error"
+        assert _.isFunction lazy = try @lazy()
         do -> lazy.call this, kernel, callback
         assert @instance = new this arguments...
         assert instantiated() is yes, internal


added tests for traversal and manip

added tests for traversal and manip

--- test/api.manipulation.coffee
+++ test/api.manipulation.coffee
@@ -20,9 +20,12 @@
 describe '$(...)', ->
   describe '.append', ->

-    it '() : should do nothing'
+    it '() : should do nothing', ->
+      $('#fruits', fruits).append()[0].name.should.equal 'ul'

-    it '(html) : should add element as last child'
+    it '(html) : should add element as last child', ->
+      $('#fruits', fruits).append('
Plum
')
+      $('#fruits', fruits).children(3).hasClass('plum').should.be.ok

     it '($(...)) : should add element as last child'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/array_extensions.coffee
+++ test/array_extensions.coffee
@@ -1,7 +1,7 @@
 module "Array"

 test "#average", ->
-  [1, 3, 5]
+  [1, 3, 5, ]

 test "#compact", ->
   a = [0, 1, undefined, 2, null, 3, '', 4]


could test and fix regular user creation and group invite user creation.

--- workers/social/lib/social/models/user.coffee
+++ workers/social/lib/social/models/user.coffee
@@ -291,23 +291,24 @@
           if data.error then callback yes else callback null

   @addToGroup = (account, selector, callback)->
-    JGroup.one selector, (err, group)=>
+    JGroup.one selector, (err, group)->
+      if err or not group then callback err
+      else
+        group.addMember account, 'member', (err)->
+          if err then callback err
+          else
+            group.updateCounts()
+            callback null
+
+  @addToGroups = (account, invite, callback)->
+    @addToGroup account, slug:'koding', (err)=>
       if err then callback err
-      else
-        group.addMember account, 'member', (err)->
-          group.updateCounts() unless err
+      else if invite.group
+        @addToGroup account, _id:invite.group.id, (err)->
           if err then callback err
-          else callback
-
-  @addToGroups = (account, invite, callback)->
-    queue = [
-      ->
-        @addToGroup account, slug:'koding', -> queue.next()
-      ->
-        if invite.group
-          @addToGroup account, _id:invite.group, -> queue.next()
-    ]
-    daisy queue, callback
+          else callback null
+      else
+        callback null

   @register = secure (client, userFormData, callback)->
     {connection} = client
@@ -347,12 +348,12 @@
             else if not username? or not email?
               return callback createKodingError 'Username and email are required fields'

-            @verifyKodingenPassword {username, password, kodingenUser}, (err)->
+            @verifyKodingenPassword {username, password, kodingenUser}, (err)=>
               if err
                 return callback createKodingError 'Wrong password'
               else
                 nickname = username
-                JSession.one {clientId: client.sessionToken}, (err, session)->
+                JSession.one {clientId: client.sessionToken}, (err, session)=>
                   if err
                     callback err
                   else unless session
@@ -364,7 +365,7 @@
                       usedAsPath: 'username',
                       collectionName: 'jUsers'
                     }
-                    JName.claim username, [slug], 'JUser', (err)->
+                    JName.claim username, [slug], 'JUser', (err)=>
                       if err then callback err
                       else
                         salt = createSalt()
@@ -382,7 +383,7 @@
                             likeActivities : off
                           }
                         }
-                        user.save (err)->
+                        user.save (err)=>
                           if err
                             if err.code is 11000
                               callback createKodingError "Sorry, \"#{email}\" is already in use!"
@@ -396,9 +397,9 @@
                                 lastName
                                 hash
                               }
-                            account.save (err)->
+                            account.save (err)=>
                               if err then callback err
-                              else user.addOwnAccount account, (err)->
+                              else user.addOwnAccount account, (err)=>
                                 if err then callback err
                                 else @addToGroups account, invite, (err)->
                                   if err then callback err


Removed some logging

Removed some logging

--- www/coffee/backbone/collections/areas.coffee
+++ www/coffee/backbone/collections/areas.coffee
@@ -42,7 +42,6 @@
           for localLayer in areaModel.get('mbtiles')
             if localLayer.habitat == fetchedLayer.habitat
               # Copy local storage attributes to fetchedData
-              console.log "adding date #{localLayer.downloadedAt} to #{fetchedLayer}"
               fetchedLayer.downloadedAt = localLayer.downloadedAt
               break