linting r_account

linting r_account

--- salvus/page/r_account.cjsx
+++ salvus/page/r_account.cjsx
@@ -37,7 +37,7 @@
 # Define account actions
 class AccountActions extends Actions
     # NOTE: Can test causing this action by typing this in the Javascript console:
-    #    require('flux').flux.getActions('account').setTo({first_name:"William"})
+    #    require('flux').flux.getActions('account').setTo({first_name:'William'})
     setTo: (settings) ->
         return settings

@@ -85,7 +85,7 @@

 # in a grid:   Title [text input]
 TextSetting = rclass
-    displayName : "Account-TextSetting"
+    displayName : 'Account-TextSetting'

     propTypes :
         label    : rtypes.string.isRequired
@@ -109,7 +109,7 @@
         </LabeledRow>

 EmailAddressSetting = rclass
-    displayName : "Account-EmailAddressSetting"
+    displayName : 'Account-EmailAddressSetting'

     propTypes :
         email_address : rtypes.string
@@ -168,7 +168,7 @@
         switch @state.state
             when 'view'

{@props.email_address}
-                     <Button className="pull-right" onClick={@start_editing}>Change email</Button>
+                     <Button className='pull-right' onClick={@start_editing}>Change email</Button>

             when 'edit', 'saving'
                 <Well>
@@ -200,12 +200,12 @@
             <Saving />

     render : ->
-        <LabeledRow label="Email address">
+        <LabeledRow label='Email address'>
             {@render_value()}
         </LabeledRow>

 PasswordSetting = rclass
-    displayName : "Account-PasswordSetting"
+    displayName : 'Account-PasswordSetting'

     propTypes :
         email_address : rtypes.string
@@ -280,7 +280,7 @@
     render_value : ->
         switch @state.state
             when 'view'
-                <Button className="pull-right" onClick={@change_password}  style={marginTop: '8px'}>
+                <Button className='pull-right' onClick={@change_password}  style={marginTop: '8px'}>
                     Change password
                 </Button>
             when 'edit', 'saving'
@@ -314,7 +314,7 @@
             <Saving />

     render : ->
-        <LabeledRow label="Password">
+        <LabeledRow label='Password'>
             {@render_value()}
         </LabeledRow>

@@ -322,7 +322,7 @@
 # a field here, this one will get overwritten on the prop update.  I think using state would
 # fix that.
 AccountSettings = rclass
-    displayName : "AccountSettings"
+    displayName : 'AccountSettings'

     propTypes :
         first_name    : rtypes.string
@@ -352,7 +352,7 @@
                 {(@render_strategy(strategy) for strategy in STRATEGIES)}
             </ButtonToolbar>

-            NOTE: Linked accounts are
+            NOTE: Linked accounts are
                 currently only used for sign
                 in; in particular, sync is not
                 yet implemented.
@@ -362,14 +362,14 @@
     render : ->
         <Panel header={
 <Icon name='user' /> Account settings
}>
             <TextSetting
-                label    = "First name"
+                label    = 'First name'
                 value    = {@props.first_name}
                 ref      = 'first_name'
                 onChange = {=>@handle_change('first_name')}
                 onBlur   = {=>@save_change('first_name')}
                 />
             <TextSetting
-                label    = "Last name"
+                label    = 'Last name'
                 value    = {@props.last_name}
                 ref      = 'last_name'
                 onChange = {=>@handle_change('last_name')}
@@ -406,7 +406,7 @@
 # TODO: in console.coffee there is also code to set the font size,
 # which our store ignores...
 TerminalSettings = rclass
-    displayName : "Account-TerminalSettings"
+    displayName : 'Account-TerminalSettings'

     handleChange: (obj) ->
         @props.flux.getTable('account').set(terminal: obj)
@@ -415,21 +415,21 @@
         if not @props.terminal?
             return <Loading />
         <Panel header={
 <Icon name='terminal' /> Terminal (settings applied to newly opened terminals)
}>
-            <LabeledRow label="Terminal font size (px)">
+            <LabeledRow label='Terminal font size (px)'>
                 <NumberInput
                     on_change = {(font_size)=>@handleChange(font_size:font_size)}
                     min       = 3
                     max       = 80
                     number    = {@props.terminal.font_size} />
             </LabeledRow>
-            <LabeledRow label="Terminal font family">
+            <LabeledRow label='Terminal font family'>
                 <SelectorInput
                     selected  = {@props.terminal.font}
                     options   = {TERMINAL_FONT_FAMILIES}
                     on_change = {(font)=>@handleChange(font:font)}
                 />
             </LabeledRow>
-            <LabeledRow label="Terminal color scheme">
+            <LabeledRow label='Terminal color scheme'>
                 <SelectorInput
                     selected  = {@props.terminal.color_scheme}
                     options   = {TERMINAL_COLOR_SCHEMES}
@@ -439,30 +439,30 @@
         </Panel>

 EDITOR_SETTINGS_CHECKBOXES =
-    line_wrapping              : "scroll or wrap long lines"
-    line_numbers               : "show line numbers"
-    code_folding               : "fold code using control+Q"
-    smart_indent               : "context sensitive indentation"
-    electric_chars             : "sometimes reindent current line"
-    match_brackets             : "highlight matching brackets near cursor"
-    auto_close_brackets        : "automatically close brackets"
-    match_xml_tags             : "automatically match XML tags"
-    auto_close_xml_tags        : "automatically close XML tags"
-    strip_trailing_whitespace  : "remove whenever file is saved"
-    show_trailing_whitespace   : "show spaces at ends of lines"
-    spaces_instead_of_tabs     : "send 4 spaces when the tab key is pressed"
-    track_revisions            : "record history of changes when editing files"
-    extra_button_bar           : "more editing functions (mainly in Sage worksheets)"
+    line_wrapping             : 'scroll or wrap long lines'
+    line_numbers              : 'show line numbers'
+    code_folding              : 'fold code using control+Q'
+    smart_indent              : 'context sensitive indentation'
+    electric_chars            : 'sometimes reindent current line'
+    match_brackets            : 'highlight matching brackets near cursor'
+    auto_close_brackets       : 'automatically close brackets'
+    match_xml_tags            : 'automatically match XML tags'
+    auto_close_xml_tags       : 'automatically close XML tags'
+    strip_trailing_whitespace : 'remove whenever file is saved'
+    show_trailing_whitespace  : 'show spaces at ends of lines'
+    spaces_instead_of_tabs    : 'send 4 spaces when the tab key is pressed'
+    track_revisions           : 'record history of changes when editing files'
+    extra_button_bar          : 'more editing functions (mainly in Sage worksheets)'

 EditorSettingsCheckboxes = rclass
-    displayName : "Account-EditorSettingsCheckboxes"
+    displayName : 'Account-EditorSettingsCheckboxes'

     propTypes :
         editor_settings : rtypes.object.isRequired
         on_change       : rtypes.func.isRequired

     label_checkbox : (name, desc) ->
-        return misc.capitalize(name.replace(/_/g,' ').replace(/-/g,' ').replace('xml','XML')) + ": " + desc
+        return misc.capitalize(name.replace(/_/g,' ').replace(/-/g,' ').replace('xml','XML')) + ': ' + desc

     render_checkbox : (name, desc) ->
         <Input checked  = {@props.editor_settings[name]}
@@ -479,14 +479,14 @@

 EditorSettingsAutosaveInterval = rclass
-    displayName : "Account-EditorSettingsAutosaveInterval"
+    displayName : 'Account-EditorSettingsAutosaveInterval'

     propTypes :
         autosave  : rtypes.number.isRequired
         on_change : rtypes.func.isRequired

     render : ->
-        <LabeledRow label="Autosave interval (seconds)">
+        <LabeledRow label='Autosave interval (seconds)'>
             <NumberInput
                 on_change = {(n)=>@props.on_change('autosave',n)}
                 min       = 15
@@ -495,45 +495,45 @@
         </LabeledRow>

 EDITOR_COLOR_SCHEMES =
-    'default': 'Default'
-    '3024-day': '3024 day'
-    '3024-night': '3024 night'
-    'ambiance-mobile': 'Ambiance mobile'
-    'ambiance': 'Ambiance'
-    'base16-dark': 'Base 16 dark'
-    'base16-light': 'Base 16 light'
-    'blackboard': 'Blackboard'
-    'cobalt': 'Cobalt'
-    'eclipse': 'Eclipse'
-    'elegant': 'Elegant'
-    'erlang-dark': 'Erlang dark'
-    'lesser-dark': 'Lesser dark'
-    'the-matrix': 'The Matrix'
-    'midnight': 'Midnight'
-    'monokai': 'Monokai'
-    'neat': 'Neat'
-    'night': 'Night'
-    'paraiso-dark': 'Paraiso dark'
-    'paraiso-light': 'Paraiso light'
-    'pastel-on-dark': 'Pastel on dark'
-    'rubyblue': 'Rubyblue'
-    'solarized dark': 'Solarized dark'
-    'solarized light': 'Solarized light'
-    'tomorrow-night-eighties': 'Tomorrow Night - Eighties'
-    'twilight': 'Twilight'
-    'vibrant-ink': 'Vibrant ink'
-    'xq-dark': 'Xq dark'
-    'xq-light': 'Xq light'
+    'default'                 : 'Default'
+    '3024-day'                : '3024 day'
+    '3024-night'              : '3024 night'
+    'ambiance-mobile'         : 'Ambiance mobile'
+    'ambiance'                : 'Ambiance'
+    'base16-dark'             : 'Base 16 dark'
+    'base16-light'            : 'Base 16 light'
+    'blackboard'              : 'Blackboard'
+    'cobalt'                  : 'Cobalt'
+    'eclipse'                 : 'Eclipse'
+    'elegant'                 : 'Elegant'
+    'erlang-dark'             : 'Erlang dark'
+    'lesser-dark'             : 'Lesser dark'
+    'the-matrix'              : 'The Matrix'
+    'midnight'                : 'Midnight'
+    'monokai'                 : 'Monokai'
+    'neat'                    : 'Neat'
+    'night'                   : 'Night'
+    'paraiso-dark'            : 'Paraiso dark'
+    'paraiso-light'           : 'Paraiso light'
+    'pastel-on-dark'          : 'Pastel on dark'
+    'rubyblue'                : 'Rubyblue'
+    'solarized dark'          : 'Solarized dark'
+    'solarized light'         : 'Solarized light'
+    'tomorrow-night-eighties' : 'Tomorrow Night - Eighties'
+    'twilight'                : 'Twilight'
+    'vibrant-ink'             : 'Vibrant ink'
+    'xq-dark'                 : 'Xq dark'
+    'xq-light'                : 'Xq light'

 EditorSettingsColorScheme = rclass
-    displayName : "Account-EditorSettingsColorScheme"
+    displayName : 'Account-EditorSettingsColorScheme'

     propTypes :
         theme     : rtypes.string.isRequired
         on_change : rtypes.func.isRequired

     render : ->
-        <LabeledRow label="Editor color scheme">
+        <LabeledRow label='Editor color scheme'>
             <SelectorInput
                 options   = {EDITOR_COLOR_SCHEMES}
                 selected  = {@props.theme}
@@ -542,13 +542,13 @@
         </LabeledRow>

 EDITOR_BINDINGS =
-    standard : "Standard"
-    sublime  : "Sublime"
-    vim      : "Vim"
-    emacs    : "Emacs"
+    standard : 'Standard'
+    sublime  : 'Sublime'
+    vim      : 'Vim'
+    emacs    : 'Emacs'

 EditorSettingsKeyboardBindings = rclass
-    displayName : "Account-EditorSettingsKeyboardBindings"
+    displayName : 'Account-EditorSettingsKeyboardBindings'

     propTypes :
         bindings  : rtypes.string.isRequired
@@ -564,7 +564,7 @@
         </LabeledRow>

 EditorSettings = rclass
-    displayName : "Account-EditorSettings"
+    displayName : 'Account-EditorSettings'

     on_change : (name, val) ->
         if name == 'autosave'
@@ -587,29 +587,29 @@
         </Panel>

 KEYBOARD_SHORTCUTS =
-    "Next file tab"     : "control+]"
-    "Previous file tab" : "control+["
-    "Smaller text"      : "control+<"
-    "Bigger text"       : "control+>"
-    "Go to line"        : "control+L"
-    "Find"              : "control+F"
-    "Find next"         : "control+G"
-    "Fold/unfold selected code" : "control+Q"
-    "Shift selected text right" : "tab"
-    "Shift selected text left"  : "shift+tab"
-    "Split view in any editor"  : "control+I"
-    "Autoindent selection"      : "control+'"
-    "Multiple cursors"          : "control+click"
-    "Simple autocomplete"       : "control+space"
-    "Sage autocomplete"         : "tab"
-    "Split cell in Sage worksheet": "control+;"
+    'Next file tab'                : 'control+]'
+    'Previous file tab'            : 'control+['
+    'Smaller text'                 : 'control+<'
+    'Bigger text'                  : 'control+>'
+    'Go to line'                   : 'control+L'
+    'Find'                         : 'control+F'
+    'Find next'                    : 'control+G'
+    'Fold/unfold selected code'    : 'control+Q'
+    'Shift selected text right'    : 'tab'
+    'Shift selected text left'     : 'shift+tab'
+    'Split view in any editor'     : 'control+I'
+    'Autoindent selection'         : 'control+'
+    'Multiple cursors'             : 'control+click'
+    'Simple autocomplete'          : 'control+space'
+    'Sage autocomplete'            : 'tab'
+    'Split cell in Sage worksheet' : 'control+;'

 EVALUATE_KEYS =
-    'Shift-Enter' : "shift+enter"
-    'Enter'       : "enter (shift+enter for newline)"
+    'Shift-Enter' : 'shift+enter'
+    'Enter'       : 'enter (shift+enter for newline)'

 KeyboardSettings = rclass
-    displayName : "Account-KeyboardSettings"
+    displayName : 'Account-KeyboardSettings'

     render_keyboard_shortcuts : ->
         for desc, shortcut of KEYBOARD_SHORTCUTS
@@ -638,7 +638,7 @@
         </Panel>

 OtherSettings = rclass
-    displayName : "Account-OtherSettings"
+    displayName : 'Account-OtherSettings'

     on_change : (name, value) ->
         @props.flux.getTable('account').set(other_settings:{"#{name}":value})
@@ -646,11 +646,11 @@
     render_confirm : ->
         if not require('feature').IS_MOBILE
             <Input
-                type     = "checkbox"
+                type     = 'checkbox'
                 checked  = {@props.other_settings.confirm_close}
-                ref      = "confirm_close"
+                ref      = 'confirm_close'
                 onChange = {=>@on_change('confirm_close', @refs.confirm_close.getChecked())}
-                label    = "Confirm: always ask for confirmation before closing the browser window"
+                label    = 'Confirm: always ask for confirmation before closing the browser window'
             />

     render : ->
@@ -659,23 +659,23 @@
         <Panel header={
 <Icon name='gear' /> Other settings
}>
             {@render_confirm()}
             <Input
-                type     = "checkbox"
+                type     = 'checkbox'
                 checked  = {@props.other_settings.mask_files}
-                ref      = "mask_files"
+                ref      = 'mask_files'
                 onChange = {=>@on_change('mask_files', @refs.mask_files.getChecked())}
-                label    = "Mask files: grey-out files in the files viewer that you probably don't want to open"
+                label    = 'Mask files: grey-out files in the files viewer that you probably don't want to open'
             />
-            <LabeledRow label="Default file sort">
+            <LabeledRow label='Default file sort'>
                 <SelectorInput
                     selected  = {@props.other_settings.default_file_sort}
-                    options   = {time:"Sort by time", name:"Sort by name"}
+                    options   = {time:'Sort by time', name:'Sort by name'}
                     on_change = {(value)=>@on_change('default_file_sort', value)}
                 />
             </LabeledRow>
         </Panel>

 AccountCreationToken = rclass
-    displayName : "AccountCreationToken"
+    displayName : 'AccountCreationToken'

     getInitialState : ->
         state : 'view'   # view --> edit --> save --> view
@@ -698,12 +698,12 @@
                     @setState(state:'view', error:'', token:'')

     render_save_button : ->
-        <Button style={marginRight:'1ex'} onClick={@save} bsStyle="success">Save token</Button>
+        <Button style={marginRight:'1ex'} onClick={@save} bsStyle='success'>Save token</Button>

     render_control : ->
         switch @state.state
             when 'view'
-                <Button onClick={@edit} bsStyle="warning">Change token...</Button>
+                <Button onClick={@edit} bsStyle='warning'>Change token...</Button>
             when 'load'
                 <Loading />
             when 'edit', 'save'
@@ -739,7 +739,7 @@

 StripeKeys = rclass
-    displayName : "Account-StripeKeys"
+    displayName : 'Account-StripeKeys'

     getInitialState : ->
         state           : 'view'   # view --> edit --> save --> view
@@ -774,23 +774,23 @@
     render_main :->
         switch @state.state
             when 'view'
-                <Button bsStyle="warning" onClick={@edit}>Change stripe keys...</Button>
+                <Button bsStyle='warning' onClick={@edit}>Change stripe keys...</Button>
             when 'load'

Loading stripe keys...

             when 'save'

Saving stripe keys...

             when 'edit'
                 <Well>
-                    <LabeledRow label="Secret key">
-                        <Input ref="input_secret_key" type="text" value={@state.secret_key}
+                    <LabeledRow label='Secret key'>
+                        <Input ref='input_secret_key' type='text' value={@state.secret_key}
                             onChange={=>@setState(secret_key:@refs.input_secret_key.getValue())} />
                     </LabeledRow>
-                    <LabeledRow label="Publishable key">
-                        <Input ref="input_publishable_key" type="text" value={@state.publishable_key}
+                    <LabeledRow label='Publishable key'>
+                        <Input ref='input_publishable_key' type='text' value={@state.publishable_key}
                             onChange={=>@setState(publishable_key:@refs.input_publishable_key.getValue())} />
                     </LabeledRow>
                     <ButtonToolbar>
-                        <Button bsStyle="success" onClick={@save}>Save stripe keys...</Button>
+                        <Button bsStyle='success' onClick={@save}>Save stripe keys...</Button>
                         <Button onClick={@cancel}>Cancel</Button>
                     </ButtonToolbar>
                 </Well>
@@ -804,10 +804,10 @@
         if not @props.groups? or 'admin' not in @props.groups
             return
         <Panel header={
 <Icon name='users' /> Administrative server settings
}>
-            <LabeledRow label="Account Creation Token">
+            <LabeledRow label='Account Creation Token'>
                 <AccountCreationToken />
             </LabeledRow>
-            <LabeledRow label="Stripe API Keys" style={marginTop:'15px'}>
+            <LabeledRow label='Stripe API Keys' style={marginTop:'15px'}>
                 <StripeKeys />
             </LabeledRow>
         </Panel>
@@ -877,7 +877,7 @@
             return zxcvbn(password, ['sagemath','salvus','sage','sagemathcloud','smc','mathematica','pari'])
     else
         zxcvbn = 'loading'
-        $.getScript "/static/zxcvbn/zxcvbn.js", () =>
+        $.getScript '/static/zxcvbn/zxcvbn.js', () =>
             zxcvbn = window.zxcvbn
     return

@@ -887,7 +887,7 @@
 ###

 AccountName = rclass
-    displayName : "AccountName"
+    displayName : 'AccountName'

     propTypes :
         first_name : rtypes.string
@@ -895,7 +895,7 @@

     render : ->
         if @props.first_name and @props.last_name
-            <Icon name="cog" style={fontSize:"20px"}/> {misc.trunc_middle(@props.first_name + ' ' + @props.last_name, 32)}
+            <Icon name='cog' style={fontSize:'20px'}/> {misc.trunc_middle(@props.first_name + ' ' + @props.last_name, 32)}
         else
             Account

@@ -904,5 +904,5 @@
         <AccountName />
     </FluxComponent>

-React.render top_navbar(), require('top_navbar').top_navbar.pages['account'].button.find(".button-label")[0]
-
+React.render top_navbar(), require('top_navbar').top_navbar.pages['account'].button.find('.button-label')[0]
+


sagews: partial fix for hiding output in both views

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -1497,14 +1497,16 @@
                     marker.clear()

     hide_output: (line) =>
-        mark = @find_output_mark(line)
-        if mark?
-            @elt_at_mark(mark).addClass('sagews-output-hide')
+        for cm in [@codemirror, @codemirror1]
+            mark = @find_output_mark(line, cm)
+            if mark?
+                @elt_at_mark(mark).addClass('sagews-output-hide')

     show_output: (line) =>
-        mark = @find_output_mark(line)
-        if mark?
-            @elt_at_mark(mark).removeClass('sagews-output-hide')
+        for cm in [@codemirror, @codemirror1]
+            mark = @find_output_mark(line, cm)
+            if mark?
+                @elt_at_mark(mark).removeClass('sagews-output-hide')

     execute_code: (opts) ->
         opts = defaults opts,
@@ -1844,10 +1846,11 @@

         return mark

-    find_output_line: (line) =>
+    find_output_line: (line, cm) =>
         # Given a line number in the editor, return the nearest (greater or equal) line number that
         # is an output line, or undefined if there is no output line before the next cell.
-        cm = @focused_codemirror()
+        if not cm?
+            cm = @focused_codemirror()
         if cm.getLine(line)[0] == MARKERS.output
             return line
         line += 1
@@ -1861,11 +1864,13 @@
             line += 1
         return undefined

-    find_output_mark: (line) =>
+    find_output_mark: (line, cm) =>
         # Same as find_output_line, but returns the actual mark (or undefined).
-        n = @find_output_line(line)
+        if not cm?
+            cm = @focused_codemirror()
+        n = @find_output_line(line, cm)
         if n?
-            for mark in @focused_codemirror().findMarksAt({line:n, ch:0})
+            for mark in cm.findMarksAt({line:n, ch:0})
                 if mark.type == MARKERS.output
                     return mark
         return undefined


alternate idear

alternate idear

--- tutor/src/components/course-settings/student-id-field.cjsx
+++ tutor/src/components/course-settings/student-id-field.cjsx
@@ -15,32 +15,36 @@
     isEditing: false
     student_identifier: @props.student.student_identifier

+  componentDidUpdate: (prevProps, prevState) ->
+    if @state.isEditing and not prevState.isEditing
+      el = @refs.input.getDOMNode()
+      el.select()
+      el.focus()
+
   onUpdateId: (ev) ->
     @setState(student_identifier: ev.target.value)

   onEditId: (ev) ->
     ev.preventDefault()
-    return if @state.isBlurring
-    unless @state.isEditing
-      _.defer =>
-        el = @refs.input.getDOMNode()
-        el.select()
-        el.focus()
-    @setState(isEditing: not @state.isEditing, isBlurring: false)
+    @setState(isEditing: not @state.isEditing)

-  onEditBlur: ->
-    if @state.student_identifier isnt @props.student.student_identifier
-      @setState(isSaving: true)
+  onEditBlur: (ev) ->
+    # If blur was triggered by clicking on the editTrigger,
+    # let the onClick of the editTrigger toggle the isEditing state.
+    isEditing = ev.relatedTarget is @refs.editTrigger.getDOMNode()
+    # Only save if changes were made to the student identifier.
+    isSaving = @state.student_identifier isnt @props.student.student_identifier
+
+    stateUpdates = {}
+    stateUpdates.isEditing = isEditing if @state.isEditing isnt isEditing
+    stateUpdates.isSaving = isSaving if @state.isSaving isnt isSaving
+
+    if isSaving
       RosterActions.save(@props.student.id, student_identifier: @state.student_identifier)
       RosterStore.once "saved:#{@props.student.id}", =>
         @setState(isSaving: false) if @isMounted()

-    @setState(isEditing: false, isBlurring: true)
-    # set "isBlurring" so we don't immediatly start editing again
-    # when the blur is because the edit icon was clicked.  After a bit we reset it
-    _.delay =>
-      @setState(isBlurring: false)
-    , 100
+    @setState(stateUpdates) unless _.isEmpty(stateUpdates)

   renderInput: ->
     <input type="text" ref="input"
@@ -60,6 +64,7 @@
       <a href="#"
         tabIndex={ if @state.isEditing then -1 else 0 }
         onClick={@onEditId}
+        ref='editTrigger'
       >
         <Icon
           type={if @state.isSaving then "spinner" else "edit"}


Update docs around loadModel

Update docs around loadModel

--- vendor/assets/javascripts/brainstem/storage-manager.coffee
+++ vendor/assets/javascripts/brainstem/storage-manager.coffee
@@ -49,11 +49,11 @@
   setErrorInterceptor: (interceptor) ->
     @errorInterceptor = interceptor || (handler, modelOrCollection, options, jqXHR, requestParams) -> handler?(jqXHR)

-  # Request a model to be loaded, optionally ensuring that associations be included as well.  A collection is returned immediately and is reset
+  # Request a model to be loaded, optionally ensuring that associations be included as well.  A model is returned immediately and is updated
   # when the load, and any dependent loads, are complete.
-  #     model = manager.loadModel "time_entry"
-  #     model = manager.loadModel "time_entry", fields: ["title", "notes"]
-  #     model = manager.loadModel "time_entry", include: ["project", "task"]
+  #     model = manager.loadModel "time_entry", 2
+  #     model = manager.loadModel "time_entry", 2, fields: ["title", "notes"]
+  #     model = manager.loadModel "time_entry", 2, include: ["project", "task"]
   loadModel: (name, id, options = {}) ->
     return if not id


change git clone manner from ssh to https

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -5,16 +5,16 @@

 SERVICES = [{
 	name: "web"
-	repo: "git@github.com:sharelatex/web-sharelatex.git"
+	repo: "https://github.com/sharelatex/web-sharelatex.git"
 }, {
 	name: "document-updater"
-	repo: "git@github.com:sharelatex/document-updater-sharelatex.git"
+	repo: "https://github.com/sharelatex/document-updater-sharelatex.git"
 }, {
 	name: "clsi"
-	repo: "git@github.com:sharelatex/clsi-sharelatex.git"
+	repo: "https://github.com/sharelatex/clsi-sharelatex.git"
 }, {
 	name: "filestore"
-	repo: "git@github.com:sharelatex/filestore-sharelatex.git"
+	repo: "https://github.com/sharelatex/filestore-sharelatex.git"
 }]


Fix for race-condition on user creation that sometimes would not set user as admin

--- server/lib/accounts.coffee
+++ server/lib/accounts.coffee
@@ -24,9 +24,6 @@
 	user.status = 'offline'
 	user.active = not RocketChat.settings.get 'Accounts_ManuallyApproveNewUsers'

-	# when inserting first user give them admin privileges otherwise make a regular user
-	roleName = if Meteor.users.findOne() then 'user' else 'admin'
-
 	if not user?.name? or user.name is ''
 		if options.profile?.name?
 			user.name = options.profile?.name
@@ -45,14 +42,20 @@
 					verified: true
 				]

-	Meteor.defer ->
-		# need to defer role assignment because underlying alanning:roles requires user
-		# to exist in users collection
-		RocketChat.authz.addUsersToRoles( user._id, roleName)
-		RocketChat.callbacks.run 'afterCreateUser', options, user
-
 	return user

+# Wrap insertUserDoc to allow executing code after Accounts.insertUserDoc is run
+Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc) ->
+	options = arguments[1]
+	user = arguments[2]
+	_id = insertUserDoc(options, user)
+
+	# when inserting first user give them admin privileges otherwise make a regular user
+	roleName = if Meteor.users.findOne() then 'user' else 'admin'
+
+	RocketChat.authz.addUsersToRoles(_id, roleName)
+	RocketChat.callbacks.run 'afterCreateUser', options, user
+	return _id

 Accounts.validateLoginAttempt (login) ->
 	login = RocketChat.callbacks.run 'beforeValidateLogin', login


refactor word-report methods to abstract out functionality

--- server/methods/word.coffee
+++ server/methods/word.coffee
@@ -5,48 +5,41 @@
     expr = angular_expressions.compile(tag)
     return get: expr

+
 # EXPOSURE REPORT -------------------------------------------------------
-exposureWordReport = (tbl_id, filename) ->
-
-    getData = () ->
-        tbl = Tables.findOne(tbl_id)
-        occupationals = ExposureEvidence
-            .find({
-                    tbl_id: tbl_id,
-                    exposureScenario: "Occupational"
-                  }, {sort: {sortIdx: 1}})
-            .fetch()
-        environmentals = ExposureEvidence
-            .find({
-                    tbl_id: tbl_id,
-                    exposureScenario: "Environmental"
-                  }, {sort: {sortIdx: 1}})
-            .fetch()
-
-        for study in occupationals
-            study.reference = Reference.findOne(_id: study.referenceID)
-            share.setExposureWordFields(study)
-
-        for study in environmentals
-            study.reference = Reference.findOne(_id: study.referenceID)
-            share.setExposureWordFields(study)
-
-        d =
-            "monographAgent": tbl.monographAgent
-            "volumeNumber": tbl.volumeNumber
-            "hasTable": true
-            "table": tbl
-            "occupationals": occupationals
-            "environmentals": environmentals
-
-        return d
-
-    data = getData()
-    path = share.getWordTemplatePath(filename)
-    docx = new DocxGen().loadFromFile(path, {async: false, parser: angularParser})
-    docx.setTags(data)
-    docx.applyTags()
-    docx.output({type: "string"})
+exposureWordReport = (tbl_id) ->
+
+    tbl = Tables.findOne(tbl_id)
+    occupationals = ExposureEvidence
+        .find({
+                tbl_id: tbl_id,
+                exposureScenario: "Occupational"
+              }, {sort: {sortIdx: 1}})
+        .fetch()
+    environmentals = ExposureEvidence
+        .find({
+                tbl_id: tbl_id,
+                exposureScenario: "Environmental"
+              }, {sort: {sortIdx: 1}})
+        .fetch()
+
+    for study in occupationals
+        study.reference = Reference.findOne(_id: study.referenceID)
+        share.setExposureWordFields(study)
+
+    for study in environmentals
+        study.reference = Reference.findOne(_id: study.referenceID)
+        share.setExposureWordFields(study)
+
+    d =
+        "monographAgent": tbl.monographAgent
+        "volumeNumber": tbl.volumeNumber
+        "hasTable": true
+        "table": tbl
+        "occupationals": occupationals
+        "environmentals": environmentals
+
+    return d

 # EPI REPORTS ------------------------------------------------------------------
@@ -146,14 +139,14 @@
     docx.applyTags()
     return docx.output({type: "string"})

-epiWordReport = (tbl_id, filename) ->
-    epiSortOrder = ReportTemplate.findOne({filename: filename}).epiSortOrder
+epiWordReport = (tbl_id, epiSortOrder) ->
     switch epiSortOrder
         when "Reference"
-            data = getEpiDataByReference(tbl_id)
+            return getEpiDataByReference(tbl_id)
         when "Organ-site"
-            data = getEpiDataByOrganSite(tbl_id)
-    return createWordReport(filename, data)
+            return getEpiDataByOrganSite(tbl_id)
+        else
+            console.error("unknown epiSortOrder")

 epiWordReportMultiTable = (filename, monographAgent, volumeNumber) ->
     epiSortOrder = ReportTemplate.findOne({filename: filename}).epiSortOrder
@@ -165,37 +158,27 @@
     return createWordReport(filename, data)

 # ANIMAL BIOASSAY REPORT -------------------------------------------------------
-animalWordReport = (tbl_id, filename) ->
-
-    getData = () ->
-        tbl = Tables.findOne(tbl_id)
-        studies = AnimalEvidence
-            .find({tbl_id: tbl_id}, {sort: {sortIdx: 1}})
-            .fetch()
-
-        for study in studies
-            study.reference = Reference.findOne(_id: study.referenceID)
-            share.setAnimalWordFields(study)
-
-        d =
-            "monographAgent": tbl.monographAgent
-            "volumeNumber": tbl.volumeNumber
-            "hasTable": true
-            "table": tbl
-            "studies": studies
-
-        return d
-
-    data = getData()
-    path = share.getWordTemplatePath(filename)
-    docx = new DocxGen().loadFromFile(path, {async: false, parser: angularParser})
-    docx.setTags(data)
-    docx.applyTags()
-    docx.output({type: "string"})
-
+animalWordReport = (tbl_id) ->
+    tbl = Tables.findOne(tbl_id)
+    studies = AnimalEvidence
+        .find({tbl_id: tbl_id}, {sort: {sortIdx: 1}})
+        .fetch()
+
+    for study in studies
+        study.reference = Reference.findOne(_id: study.referenceID)
+        share.setAnimalWordFields(study)
+
+    d =
+        "monographAgent": tbl.monographAgent
+        "volumeNumber": tbl.volumeNumber
+        "hasTable": true
+        "table": tbl
+        "studies": studies
+
+    return d

 # MECHANISTIC REPORT -----------------------------------------------------------
-mechanisticWordReport = (tbl_id, filename) ->
+mechanisticWordReport = (tbl_id) ->

     getReferences = (obj) ->
         refs = Reference.find({_id: {$in: obj.references}}).fetch()
@@ -220,62 +203,57 @@
             getReferences(child)
             getChildren(child)

-    path = share.getWordTemplatePath(filename)
-    docx = new DocxGen().loadFromFile(path, {async: false, parser: angularParser})
-    docx.setTags(data)
-    docx.applyTags()
-    docx.output({type: "string"})
+    return data
+

 # GENOTOXICITY REPORT ----------------------------------------------------------
-genotoxWordReport = (tbl_id, filename) ->
-
-    getData = () ->
-        tbl = Tables.findOne(tbl_id)
-        d =
-            "monographAgent": tbl.monographAgent
-            "volumeNumber": tbl.volumeNumber
-            "hasTable": true
-            "table": tbl
-
-        vals = GenotoxEvidence
-            .find({tbl_id: tbl_id}, {sort: {sortIdx: 1}})
-            .fetch()
-
-        for val in vals
-            val.reference = Reference.findOne(_id: val.referenceID)
-            share.setGenotoxWordFields(val)
-
-        d.nonMammalianInVitro = _.filter(vals, (v) -> return v.dataClass == "Non-mammalian in vitro")
-        d.mammalianInVitro = _.filter(vals, (v) -> return v.dataClass == "Mammalian and human in vitro")
-        d.animalInVivo = _.filter(vals, (v) -> return v.dataClass == "Animal in vivo")
-        d.humanInVivo = _.filter(vals, (v) -> return v.dataClass == "Human in vivo")
-        return d
-
-    data = getData()
-    path = share.getWordTemplatePath(filename)
-    docx = new DocxGen().loadFromFile(path, {async: false, parser: angularParser})
-    docx.setTags(data)
-    docx.applyTags()
-    docx.output({type: "string"})
+genotoxWordReport = (tbl_id) ->
+
+    tbl = Tables.findOne(tbl_id)
+    d =
+        "monographAgent": tbl.monographAgent
+        "volumeNumber": tbl.volumeNumber
+        "hasTable": true
+        "table": tbl
+
+    vals = GenotoxEvidence
+        .find({tbl_id: tbl_id}, {sort: {sortIdx: 1}})
+        .fetch()
+
+    for val in vals
+        val.reference = Reference.findOne(_id: val.referenceID)
+        share.setGenotoxWordFields(val)
+
+    d.nonMammalianInVitro = _.filter(vals, (v) -> return v.dataClass == "Non-mammalian in vitro")
+    d.mammalianInVitro = _.filter(vals, (v) -> return v.dataClass == "Mammalian and human in vitro")
+    d.animalInVivo = _.filter(vals, (v) -> return v.dataClass == "Animal in vivo")
+    d.humanInVivo = _.filter(vals, (v) -> return v.dataClass == "Human in vivo")
+    return d

 Meteor.methods
     downloadWordReport: (tbl_id, filename) ->
         tbl = Tables.findOne(tbl_id)
+        template = ReportTemplate.findOne({filename: filename})
         switch tbl.tblType
             when "Exposure Evidence"
-                fn = exposureWordReport
+                data = exposureWordReport(tbl_id)
             when "Epidemiology Evidence"
-                fn = epiWordReport
+                data = epiWordReport(tbl_id, template.epiSortOrder)
             when "Animal Bioassay Evidence"
-                fn = animalWordReport
+                data = animalWordReport(tbl_id)
             when "Genetic and Related Effects"
-                fn = genotoxWordReport
+                data = genotoxWordReport(tbl_id)
             when "Mechanistic Evidence Summary"
-                fn = mechanisticWordReport
+                data = mechanisticWordReport(tbl_id)
             else
                 return console.error("unknown table type: " + tbl.tblType)
-        fn(tbl_id, filename)
+
+        path = share.getWordTemplatePath(filename)
+        docx = new DocxGen().loadFromFile(path, {async: false, parser: angularParser})
+        docx.setTags(data)
+        docx.applyTags()
+        return docx.output({type: "string"})

     monographAgentEpiReport: (d) ->
         epiWordReportMultiTable(d.templateFN, d.monographagent, d.volumenumber)


Add bitwise operators to grammar.

Add bitwise operators to grammar.

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -121,6 +121,10 @@
   {
     'match': '-eq|-ceq|-ieq|-lt|-gt|-le|-ge|-ne|-notlike|-not|-like|-match|-notmatch|-contains|-notcontains|-in|-notin|-replace'
     'name': 'keyword.operator.comparison.powershell'
+  }
+  {
+    'match': '\\b(?i:(-bAnd|-bOr|-bXor|-bNot|-shl|-shr)\\b'
+    'name': 'keyword.operator.bitwise.powershell'
   }
   {
     'match': '\\+\\=|-\\=|\\*\\=|/\\=|//\\=|%\\=|&\\=|\\|\\=|\\^\\=|>>\\=|<<\\=|\\*\\*\\='


Make permissions and users get bulldosed between tests

--- server/test/helpers.coffee
+++ server/test/helpers.coffee
@@ -15,6 +15,7 @@
 Theme = require('../models/theme').model
 Page = require('../models/page').model
 User = require('../models/user').model
+Permission = require('../models/permission').model

 before( (done) ->
   app.start 3001, (err, server) ->
@@ -39,7 +40,9 @@
     Section,
     Visualisation,
     Theme,
-    Page
+    Page,
+    User,
+    Permission
   ]

   for model in models


Update consolidate task to use variables and new naming scheme

--- grunt/consolidate.coffee
+++ grunt/consolidate.coffee
@@ -1,19 +1,19 @@
 module.exports =
   options:
     engine: 'handlebars'
-  development:
+  debug:
     options:
       local:
         livereload: true,
         mainModule: '<%= package.name %>',
         title: '<%= package.name %> - Development build',
     files:
-      'build/debug/index.html': 'template/index.html'
-  production:
+      '<%= buildDebug %>/index.html': 'template/index.html'
+  release:
     options:
       local:
         livereload: false,
         mainModule: '<%= package.name %>'
         title: '<%= package.name %>'
     files:
-      'build/production/index.html': 'template/index.html'
+      '<%= buildRelease %>/index.html': 'template/index.html'


add ignore in bower.json

add ignore in bower.json

Signed-off-by: Pierre Tardy <bb5a2a16eebb4a48138fca46fede24b3c8d635fe@intel.com>

--- guanlecoja/config.coffee
+++ guanlecoja/config.coffee
@@ -18,6 +18,7 @@
         name: "guanlecoja-ui"
         version: "1.0.5"
         main: ["scripts.js", "styles.css", "fonts/*", "img/*"]
+        ignore: []
         description: "Sets of widgets and integrated bower dependencies useful for dashboard SPAs"
         dependencies: {}


allow julia path relative to atom executable

--- lib/misc/paths.coffee
+++ lib/misc/paths.coffee
@@ -17,17 +17,30 @@
   expandHome: (p) ->
     if p.startsWith '~' then p.replace '~', @home() else p

-  fullPath: (path) ->
+  fullPath: (p) ->
     new Promise (resolve, reject) ->
-      if fs.existsSync(path) then resolve(path)
+      if fs.existsSync(p) then resolve(p)
+
+      current_dir = process.cwd()
+      exepath = path.dirname(process.execPath)
+
+      try
+        process.chdir(exepath)
+        realpath = fs.realpathSync(p)
+        if fs.existsSync(realpath) then resolve(realpath)
+      catch err
+        try
+          process.chdir(current_dir)
+        catch err
+          console.error(err)

       if process.platform is 'win32'
-        if /[a-zA-Z]\:/.test(path)
+        if /[a-zA-Z]\:/.test(p)
           reject("Couldn't resolve path.")
           return

       which = if process.platform is 'win32' then 'where' else 'which'
-      proc = child_process.exec "#{which} \"#{path}\"", (err, stdout, stderr) ->
+      proc = child_process.exec "#{which} \"#{p}\"", (err, stdout, stderr) ->
         return reject(stderr) if err?
         p = stdout.trim()
         return resolve(p) if fs.existsSync(p)


added autoReconnect option

added autoReconnect option

--- lib/promiseFtp.coffee
+++ lib/promiseFtp.coffee
@@ -4,27 +4,26 @@

 FtpClient = require 'ftp'
 Promise = require 'bluebird'
-_ = require 'lodash'
+path = require 'path'

 FtpConnectionError = require './ftpConnectionError'
+FtpReconnectError = require './ftpReconnectError'
+connectionStatuses = require './connectionStatuses'

 simplePassthroughMethods = [
   'ascii'
   'binary'
   'abort'
-  'cwd'
   'delete'
   'status'
   'rename'
-  'logout'
   'listSafe'
   'list'
   'get'
   'put'
   'append'
   'pwd'
-  'cdup'
   'mkdir'
   'rmdir'
   'system'
@@ -37,73 +36,157 @@
 class PromiseFtp

   constructor: () ->
-    @options = null
-    @name = "PromiseFtp"
-    @serverMessage = undefined
-    @connected = false
-    @closedWithError = false
-    @errors = []
+    connectionStatus = connectionStatuses.NOT_YET_CONNECTED
+    client = new FtpClient()
+    ftpOptions = null
+    autoReconnect = null
+    preserveCwd = null
+    intendedCwd = '.'
+    lastError = null
+    closeError = null
+    reconnectError = null
+    unexpectedClose = null
+    autoReconnectPromise = null
+
+    client.on 'error', (err) ->
+      lastError = err
+    client.on 'close', (hadError) ->
+      if hadError
+        closeError = lastError
+      unexpectedClose = (connectionStatus != connectionStatuses.DISCONNECTING && connectionStatus != connectionStatuses.LOGGING_OUT)
+      connectionStatus = connectionStatuses.DISCONNECTED
+      autoReconnectPromise = null

-    @client = new FtpClient()
-    @client.on 'greeting', (msg) =>
-      @serverMessage = msg
-    @client.on 'close', (hadError) =>
-      @connected = false
-      if hadError
-        @closedWithError = true
-    @client.once 'error', (err) =>
-      @errors.push(err)
+    promisifiedClientMethods = {}
+    for name in simplePassthroughMethods
+      promisifiedClientMethods[name] = Promise.promisify(client[name], client)
+      @[name] = do (name) -> (args...) ->
+        checkConnection(name)
+        .then () ->
+          console.log("----------------------- checkConnection.then")
+          promisifiedClientMethods[name](args...)
+
+    checkConnection = (methodName) => Promise.try () =>
+      console.log("----------------------- checkConnection")
+      if unexpectedClose && autoReconnect && !autoReconnectPromise
+        console.log("----------------------- setting autoReconnectPromise")
+        autoReconnectPromise = _connect(connectionStatuses.RECONNECTING)
+      if autoReconnectPromise
+        console.log("----------------------- autoReconnectPromise is truthy")
+        autoReconnectPromise
+        .catch (err) ->
+          console.log("----------------------- autoReconnectPromise.catch")
+          throw new FtpReconnectError(closeError, err, false)
+        .then () ->
+          console.log("----------------------- autoReconnectPromise.then")
+          if preserveCwd
+            console.log("----------------------- preserveCwd")
+            promisifiedClientMethods.cwd(intendedCwd)
+            .catch (err) =>
+              console.log("----------------------- cwd.catch")
+              @destroy()
+              throw new FtpReconnectError(closeError, err, true)
+      else if connectionStatus != connectionStatuses.CONNECTED
+        console.log("----------------------- else not connected")
+        throw new FtpConnectionError("can't perform '#{methodName}' command when connection status is: #{connectionStatus}")

-    promisifiedMethods = {}
+    _connect = (tempStatus) -> new Promise (resolve, reject) ->
+        connectionStatus = tempStatus
+        reconnectError = null
+        serverMessage = null
+        client.once 'greeting', (msg) ->
+          serverMessage = msg
+        onReady = () ->
+          client.removeListener('error', onError)
+          connectionStatus = connectionStatuses.CONNECTED
+          closeError = null
+          unexpectedClose = false
+          resolve(serverMessage)
+        onError = (err) ->
+          client.removeListener('ready', onReady)
+          reject(err)
+        client.once('ready', onReady)
+        client.once('error', onError)
+        client.connect(ftpOptions)
+
+    @connect = (options) ->
+      if connectionStatus != connectionStatuses.NOT_YET_CONNECTED && connectionStatus != connectionStatuses.DISCONNECTED
+        throw new FtpConnectionError("can't connect when connection status is: '#{connectionStatus}'")
+      # copy options object so options can't change without another call to @connect()
+      ftpOptions = {}
+      for key,value of options
+        ftpOptions[key] = value
+      # deep copy options.secureOptions... or at least mostly, ignoring the possibility of mutable secureOptions fields
+      if options.secureOptions
+        ftpOptions.secureOptions = {}
+        for key,value of options.secureOptions
+          ftpOptions.secureOptions[key] = value
+      # the following options are part of PromiseFtp, so they're not understood by the underlying client
+      autoReconnect = !!options.autoReconnect
+      delete ftpOptions.autoReconnect
+      preserveCwd = !!options.preserveCwd
+      delete ftpOptions.preserveCwd
+      # now that everything is set up, we can connect
+      _connect(connectionStatuses.CONNECTING)
+
+    @reconnect = () ->
+      if connectionStatus != connectionStatuses.NOT_YET_CONNECTED && connectionStatus != connectionStatuses.DISCONNECTED
+        throw new FtpConnectionError("can't reconnect when connection status is: '#{connectionStatus}'")
+      _connect(connectionStatuses.RECONNECTING)

-    for name in simplePassthroughMethods
-      promisifiedMethods[name] = Promise.promisify(FtpClient.prototype[name], @client)
-      @[name] = do (name) => (args...) =>
-        if !@connected
-          return Promise.reject(new FtpConnectionError("client not currently connected"))
-        promisifiedMethods[name](args...)
-    promisifiedMethods.site = Promise.promisify(@client.site, @client)
-    @site = (args...) =>
-      if !@connected
-        return Promise.reject(new FtpConnectionError("client not currently connected"))
-      promisifiedMethods.site(args...)
+    @logout = () ->
+      connectionStatus = connectionStatuses.LOGGING_OUT
+      promisifiedClientMethods.logout()
+
+    @end = () -> new Promise (resolve, reject) ->
+      if connectionStatus == connectionStatuses.NOT_YET_CONNECTED || connectionStatus == connectionStatuses.DISCONNECTED
+        reject(new FtpConnectionError("can't end connection when connection status is: #{connectionStatus}"))
+      connectionStatus = connectionStatuses.DISCONNECTING
+      client.once 'close', (hadError) ->
+        resolve(if hadError then lastError||true else false)
+      client.end()
+
+    @destroy = () ->
+      if connectionStatus == connectionStatuses.NOT_YET_CONNECTED || connectionStatus == connectionStatuses.DISCONNECTED
+        wasDisconnected = true
+      else
+        wasDisconnected = false
+        connectionStatus = connectionStatuses.DISCONNECTING
+      client.destroy()
+      wasDisconnected
+
+    @getConnectionStatus = () ->
+      connectionStatus
+
+    reconnectMethodHandlers = {}
+
+    reconnectMethodHandlers.site = (command) ->
+      promisifiedClientMethods.site(command)
       .spread (text, code) ->
         text: text
         code: code

+    reconnectMethodHandlers.cwd = (dir) ->
+      promisifiedClientMethods.cwd(dir)
+      .then (result) ->
+        if dir.charAt(0) == '/'
+          intendedCwd = path.normalize(dir)
+        else
+          intendedCwd = path.join(intendedCwd, dir)
+        result

-  connect: (options) -> new Promise (resolve, reject) =>
-    doneConnecting = false
-    if @connected
-      return reject(new FtpConnectionError("client currently connected to: #{@options.host}"))
-    @options = options
-    @serverMessage = undefined
-    @connected = false
-    @closedWithError = false
-    @errors = []
-    readyListener = () =>
-      doneConnecting = true
-      @connected = true
-      resolve(@serverMessage)
-    @client.once 'ready', readyListener
-    @client.once 'error', (err) =>
-      if !doneConnecting
-        @client.removeListener 'ready', readyListener
-        reject(err)
-    @client.connect @options
-
-  end: () -> new Promise (resolve, reject) =>
-    if !@connected
-      return reject(new FtpConnectionError("client not currently connected"))
-    @client.once 'close', (hadError) ->
-      resolve(hadError)
-    @client.end()
+    reconnectMethodHandlers.cdup = () ->
+      promisifiedClientMethods.cdup()
+      .then (result) ->
+        intendedCwd = path.join(intendedCwd, '..')
+        result

-  destroy: () ->
-    if !@connected
-      return false
-    @client.destroy()
-    return true
+    for name,handler of reconnectMethodHandlers
+      promisifiedClientMethods[name] = Promise.promisify(client[name], client)
+      @[name] = do (name,handler) -> (args...) ->
+        checkConnection(name)
+        .then () ->
+          handler(args...)

 module.exports = PromiseFtp


Fix: Uncaught TypeError: Cannot read property 'destroy' of undefined

--- lib/quick-docs.coffee
+++ lib/quick-docs.coffee
@@ -3,17 +3,17 @@
 } = require 'atom'

 module.exports =
-
+
     activate: (state) ->
       atom.commands.add 'atom-text-editor',
         'quick-docs:toggle': (event) =>
           @toggle()
-
+
       @messenger = null
       @messages = []
       @ranges = []
       @json = require './php.json'
-
+
     deactivate: ->
       @messages.map (msg) -> msg.destroy()

@@ -27,19 +27,21 @@
         console.log 'QuickDocs was toggled!'
         editor = atom.workspace.getActiveTextEditor()
         editor.selectWordsContainingCursors()
-
+
         fun = editor.getSelectedText()
         range = editor.getSelectedBufferRange()
-
-        if fun?.length && @messages[range.toString()+fun] == undefined
+
+        if fun?.length > 0 && @messages[range.toString()+fun] == undefined
+            # Function name is not empty and Message does not already exist
             @get_info(fun, range)
-        else
+        else if @messages[range.toString()+fun] != undefined
+            # Message is already displayed, destroy it
             @messages[range.toString()+fun].destroy()
             @messages[range.toString()+fun] = undefined
-
+
     get_info: (fun, range) ->
-
-        if @json[fun] == undefined
+
+        if @json[fun] == undefined
             @messages[range.toString()+fun] = @messenger.message
                     range: range
                     text: 'Not a PHP function ?'


Make sure there are no raw unicode characters in generated minified javascript

--- bin/render.coffee
+++ bin/render.coffee
@@ -22,6 +22,13 @@
         else
             throw "Value is not an Array nor a String!"
     return acc
+
+stringify_unicode = (str) ->
+    str = str.replace /[\u0100-\uffff]/g, (ch) ->
+            return "\\u" + ('0000' + ch.charCodeAt(0).toString(16)).substr(-4)
+    str = str.replace /[\x00-\x1f\x7f-\xff]/g, (ch) ->
+            return "\\x" + ('00' + ch.charCodeAt(0).toString(16)).substr(-2)
+    return str

 minify = (data, minify_options)->
@@ -88,6 +95,8 @@
     content = for filename in filenames
         render(filename, '', options)
     content.push('\n')
-    process.stdout.write(array_flatten(content).join(''), 'utf8')
+    process.stdout.write(
+        stringify_unicode(array_flatten(content).join('')),
+        'utf8')

-main()+main()


only bind express to localhost

only bind express to localhost

--- lib/run-server.coffee
+++ lib/run-server.coffee
@@ -4,5 +4,5 @@
 app = require './server'

 port = process.env.PORT or 3906
-app.listen port, ->
+app.listen port, 'localhost', ->
   log chalk.magenta "Listenting on port #{port}..."


Added test to check against potential caching issues.

--- tests/views.coffee
+++ tests/views.coffee
@@ -3,18 +3,22 @@

 @tests =
   inline: (t) ->
-    t.expect 1
-    t.wait 3000
-
-    zapp = zappa port++, ->
-      @get '/': ->
-        @render 'index', foo: 'bar', layout: no
+    t.expect 1, 2
+    t.wait 3000
+
+    zapp = zappa port++, ->
+      @get '/bar': ->
+        @render 'index', foo: 'bar', layout: no
+      @get '/foobar': ->
+        @render 'index', foo: 'foobar', layout: no

       @view index: -> h2 "CoffeeKup inline template: #{@foo}"

     c = t.client(zapp.app)
-    c.get '/', (err, res) ->
+    c.get '/bar', (err, res) ->
       t.equal 1, res.body, '
CoffeeKup inline template: bar
'
+    c.get '/foobar', (err, res) ->
+      t.equal 2, res.body, '
CoffeeKup inline template: foobar
'

   'inline + inline layout': (t) ->
     t.expect 1


Update rohbot.login for convenience of multi-factor login

--- Web/js/classes/rohbot-api.coffee
+++ Web/js/classes/rohbot-api.coffee
@@ -51,13 +51,25 @@

 	isConnected: -> @socket?.readyState == WebSocket.OPEN

-	login: (Username, Password, Tokens, Room) -> @_send "auth", { Method: "login", Username, Password, Tokens, Room }
+	login: (options) ->
+		# Username, Password, Tokens, Room
+		options['Method'] = 'login'
+		@_send "auth", options

-	register: (Username, Password) -> @_send "auth", { Method: "register", Username, Password }
+	register: (Username, Password) ->
+		@_send "auth", {
+			Method: "register"
+			Username
+			Password
+		}

-	requestHistory: (AfterDate) -> @_send "chatHistoryRequest", { AfterDate }
+	requestHistory: (AfterDate) ->
+		@_send "chatHistoryRequest", {
+			AfterDate
+		}

-	sendMessage: (Content) -> @_send "sendMessage", { Content }
+	sendMessage: (Content) ->
+		@_send "sendMessage", { Content }

 	_send: (type, data) ->


remove log line

remove log line

--- lib/ui-watcher.coffee
+++ lib/ui-watcher.coffee
@@ -19,7 +19,6 @@

   watchPackages: ->
     for pack in atom.getActivePackages()
-      console.log pack
       @createWatcher(PackageWatcher, pack) if PackageWatcher.supportsPackage(pack)

   createWatcher: (type, object) ->


为压缩文件添加注释说明

为压缩文件添加注释说明

--- lib/utils/util.coffee
+++ lib/utils/util.coffee
@@ -199,21 +199,27 @@
     zip = new JSZip()
     zipPath = pathM.join folderPath,'..',pathM.basename(folderPath)+'.zip'
     flag = "root"
+    # 递归函数 当遇到文件或者文件夹里面没有文件时结束
     compressionZip= (node,filePath) =>
-      console.log filePath
+      # console.log filePath
+      # windows 和 linux 文件路径兼容处理
       stats = fs.statSync(filePath)
+      # 1、将 windows 文件路径的 \ 转为 linux 文件路径的 /
       str1=node.replace(/\\/g,"/")
       # console.log str1
+      # 2、切割路径
       strs=str1.split("/")
-      console.log strs
+      # console.log strs
       tmp = zip
       isroot = false
+      # 3、以 tmp 保存当前 最外层文件夹
       getLast = (filePath) =>
         tmp = tmp.folder(filePath)
       if strs isnt null and strs.length isnt 0
         getLast fileItem for fileItem in strs
         if strs.length is 1 and strs[0] is "."
           isroot = true
+      # 4、保存文件夹或者文件
       if stats.isFile()
         console.log filePath
         fileName = pathM.basename(filePath)
@@ -221,7 +227,7 @@
         # zip.file(fileZipPath,fs.readFileSync(filePath))
         if isroot
           zip.file(fileName,fs.readFileSync(filePath))
-          console.log "==============>>  strs is null or 0"
+          # console.log "==============>>  strs is null or 0"
         else
           tmp.file(fileName,fs.readFileSync(filePath))
         # console.log pathM.basename(fileName)
@@ -239,6 +245,7 @@
         if fileList isnt null and fileList.length isnt 0
           compressionZip folderZipPath,pathM.join filePath,filePathItem for  filePathItem in fileList
     compressionZip ".",folderPath
+    # 5、保存 zip
     content = zip.generate({type:"nodebuffer"})
     fs.writeFileSync(zipPath,content)
     console.log "打包完了"


修改参数字段描述

修改参数字段描述

--- lib/utils/util.coffee
+++ lib/utils/util.coffee
@@ -123,14 +123,14 @@
   writeFile: (file, textContent, cb) ->
     fs.writeFile file, textContent, cb

-  writeJson: (file, obj, cb) ->
-    fs.writeJson file, obj, cb
-
-  readJson: (file,cb) ->
-    fs.readJson file, cb
-
-  readJsonSync: (file) ->
-    fs.readJsonSync file, throws: false
+  writeJson: (filePath, obj, cb) ->
+    fs.writeJson filePath, obj, cb
+
+  readJson: (filePath,cb) ->
+    fs.readJson filePath, cb
+
+  readJsonSync: (filePath) ->
+    fs.readJsonSync filePath, throws: false

   copy: (sourcePath, destinationPath, cb) ->
     fs.copy(sourcePath, destinationPath, cb)


add GUID generator utility

add GUID generator utility

--- _source/lib/lib/utilities.coffee
+++ _source/lib/lib/utilities.coffee
@@ -2,3 +2,12 @@

   base64Encode: (theString) ->
     return new Buffer(theString).toString "base64"
+
+  makeNewGuid: ->
+    s4 = ->
+      return Math.floor((1 + Math.random()) * 0x10000)
+        .toString(16)
+        .substring(1)
+
+    guid = "#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
+    return guid.toUpperCase()


remove unused popover init, now using static

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -263,9 +263,6 @@

 # Template examples

-# Template.bootstrap_magic_preview_popovers.onRendered ->
-#   @$('[data-toggle="popover"]').popover()
-
 Template.bootstrap_magic_preview_tooltips.onRendered ->
   @$('[data-toggle="tooltip"]').tooltip()


:green_heart: Added missing keys in tests

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -14,26 +14,26 @@

   it "tokenizes regexp replace", ->
     {tokens} = grammar.tokenizeLine('s/text/test/')
-    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
-    expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
+    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1

     {tokens} = grammar.tokenizeLine('s_text_test_')
-    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
-    expect(tokens[1]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[3]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[5]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
+    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[1]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[3]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[5]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1

     {tokens} = grammar.tokenizeLine('s/text/test/gxr')
-    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
-    expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
-    expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
-    expect(tokens[6]).toEqual value: "gxr", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]+    expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[3]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"], bufferDelta: 4, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 4
+    expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"], bufferDelta: 1, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 1
+    expect(tokens[6]).toEqual value: "gxr", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"], bufferDelta: 3, hasPairedCharacter: false, isAtomic: undefined, isHardTab: undefined, isSoftWrapIndentation: undefined, screenDelta: 3


Hide load/reload messages

Hide load/reload messages

--- lib/ghci.coffee
+++ lib/ghci.coffee
@@ -77,6 +77,7 @@
           unless @started
             @started = true
             @responseBuffer = []
+            @emitter.emit 'started'
             return

           @responseBuffer =
@@ -121,6 +122,9 @@
   onFinished: (callback) ->
     @emitter.on 'finished', callback

+  onStarted: (callback) ->
+    @emitter.on 'started', callback
+
   onResponse: (callback) ->
     @emitter.on 'response', callback

@@ -141,17 +145,23 @@

   load: (uri) ->
     return unless @isActive()
+    @finished = false
+    @started = false
     @ghci.stdin.write ":load #{hsEscapeString uri}#{EOL}"

   reload: ->
     return unless @isActive()
+    @finished = false
+    @started = false
     @ghci.stdin.write ":reload#{EOL}"

   reloadRepeat: ->
     return unless @isActive()
     return unless @history.back[@history.back.length - 1]?
     @reload()
-    @writeLines([@history.back[@history.back.length - 1]])
+    disp = @onStarted =>
+      @writeLines([@history.back[@history.back.length - 1]])
+      disp.dispose()

   interrupt: ->
     if @ghci?


try another hack

try another hack

--- spec/poly-test/full.coffee
+++ spec/poly-test/full.coffee
@@ -786,7 +786,7 @@

         for type, j in ['with intrinsic condition', 'with linear condition']
           do (type, j) ->
-            expectation = !window.atob || document.body.style.msTouchAction? && 544 || 480
+            expectation = window.atob && document.body.style.msTouchAction? && 544 || 480
             describe type, ->
               it 'Adaptive aspect', (done) ->
                 container = document.createElement('div')


Use a real editor in SideViewSpec.

Use a real editor in SideViewSpec.

--- spec/side-view-spec.coffee
+++ spec/side-view-spec.coffee
@@ -1,34 +1,26 @@
 SideView = require '../lib/side-view'

+Conflict = require '../lib/conflict'
+util = require './util'
+
 describe 'SideView', ->
-
-  side = null
-  view = null
-  line = null
+  [view, ours, theirs] = []

   beforeEach ->
-    line = {}
-    side = {
-      klass: -> 'klass',
-      site: -> 99,
-      description: -> '',
-      lines: -> [line]
-    }
-    view = new SideView(side)
+    editor = util.openPath("single-2way-diff.txt").getEditor()
+    conflict = Conflict.all(editor)[0]
+    [ours, theirs] = [conflict.ours, conflict.theirs]
+    view = new SideView(ours)

   it 'triggers conflict resolution', ->
-    side.resolve = -> null
-    spyOn(side, "resolve")
-
+    spyOn(ours, "resolve")
     view.useMe()
-
-    expect(side.resolve).toHaveBeenCalled()
+    expect(ours.resolve).toHaveBeenCalled()

   describe 'when chosen as the resolution', ->

     beforeEach ->
-      side.wasChosen = -> true
-      line.addClass = -> null
+      ours.resolve()

     it 'adds the "resolved" class'
     it 'deletes the marker line'
@@ -36,7 +28,7 @@
   describe 'when not chosen as the resolution', ->

     beforeEach ->
-      side.wasChosen = -> true
+      theirs.resolve()

     it 'deletes its hunks lines'
     it ''


Add test coverage for interleaved dragenter/leave events

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2585,6 +2585,14 @@
         treeView.onDragEnter(dragEnterEvent)
         expect(alphaDir).toHaveClass('selected')

+        # Remains selected when dragging to a child of the heading entry
+        treeView.onDragEnter(dragEnterEvent)
+        treeView.onDragLeave(dragEnterEvent)
+        expect(alphaDir).toHaveClass('selected')
+
+        treeView.onDragLeave(dragEnterEvent)
+        expect(alphaDir).not.toHaveClass('selected')
+
     describe "when dropping a FileView onto a DirectoryView's header", ->
       it "should move the file to the hovered directory", ->
         # Dragging delta.txt onto alphaDir


Solve minor wording issue in test

Solve minor wording issue in test

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2543,7 +2543,7 @@
       beforeEach ->
         atom.config.set('tree-view.squashDirectoryNames', true)

-      it "it does not squash root directories", ->
+      it "does not squash root directories", ->
         rootDir = fs.absolute(temp.mkdirSync('tree-view'))
         zetaDir = path.join(rootDir, "zeta")
         fs.makeTreeSync(zetaDir)


Pending failing test

Pending failing test

--- spec/vim-state-spec.coffee
+++ spec/vim-state-spec.coffee
@@ -173,7 +173,7 @@
       expect(editorView).not.toHaveClass 'insert-mode'
       expect(editorView).not.toHaveClass 'visual-mode'

-    fit "allows undoing an entire batch of typing", ->
+    xit "allows undoing an entire batch of typing", ->
       type('a')
       type('b')
       type('c')


onUpdate

onUpdate

--- lib/init.coffee
+++ lib/init.coffee
@@ -16,8 +16,12 @@

 child_process = require('child_process')

+onUpdate = () -> 0
 plugin = child_process.fork(__dirname + '/plugin')
-bar = (data)-> console.log("AAAAA", data)
+bar = (data)-> {
+    console.log("result: ", data)
+    onUpdate(data)
+}

 plugin.on('message', bar)
 # plugin.send({type: 'whatever'})
@@ -107,6 +111,8 @@

     el.innerHTML = ''

+    # assign to global variable
+    onUpdate = update
     #plugin.on('message', update)
     plugin.send({type: 'analyze', path: editor.buffer.file.path})


restore updates

restore updates

--- lib/init.coffee
+++ lib/init.coffee
@@ -266,7 +266,7 @@
         contents: new Buffer(editor.getBuffer().getText())
     })
     plugin.invalidate(f)
-    #plugin.process(f).subscribe(update1)
+    plugin.process(f).subscribe(update1)

 doc.getElementById('lupa-refresh').addEventListener('click', refresh)


More recent npm doesn't delete node_modules dirs that aren't packages

--- spec/clean-spec.coffee
+++ spec/clean-spec.coffee
@@ -48,6 +48,11 @@
   it 'uninstalls any packages not referenced in the package.json', ->
     removedPath = path.join(moduleDirectory, 'node_modules', 'will-be-removed')
     fs.makeTreeSync(removedPath)
+    fs.writeFileSync(
+      path.join(removedPath, 'package.json'),
+      '{"name": "will-be-removed", "version": "1.0.0", "dependencies": {}}',
+      'utf8'
+    )

     callback = jasmine.createSpy('callback')
     apm.run(['clean'], callback)


fixed some whitespace

fixed some whitespace

--- spec/model_spec.coffee
+++ spec/model_spec.coffee
@@ -4,9 +4,7 @@

 describe 'Model', ->
-
     describe 'a subclass called User', ->
-
         it '#metadata should setup the model data', ->
             delete User.__metadata


Remove additional console log

Remove additional console log

--- lib/main.coffee
+++ lib/main.coffee
@@ -94,7 +94,6 @@
             atom.config.get('atom-latex.build_after_save') and \
             editor.buffer.file?.path and \
             path.extname(editor.buffer.file?.path) == '.tex'
-          console.log editor.buffer.file?.path
           @latex.builder.build()

     @latex.logPanel.showText icon: 'check', 'Activated.', 5000, true


warning about the BROWSER_TYPE.lang_fi and .lang_en thingy, it will be removed for the next release

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -25,6 +25,7 @@
   ipad: false
   android: false
   mobile: false
+  # WARN: These are soon deprecated, better system is being worked on in foundation/locale_settings
   lang_en: false
   lang_fi: false

@@ -919,11 +920,13 @@
     _browserType.ipad = _iPad
     _browserType.android = !!~_ua.indexOf('Android')
     _browserType.mobile = !!~_av.indexOf('Mobile')
+
+    # WARN: this language hack is going to be deprecated once there's a proper system around:
     if !!~_lang.indexOf('fi')
       _browserType.lang_fi = true
     else
       _browserType.lang_en = true
-
+
     @_domWaiter()
     null


fix find projectPath in osx

fix find projectPath in osx

--- lib/main.coffee
+++ lib/main.coffee
@@ -3,9 +3,12 @@
 path = require 'path'
 fs = require 'fs'

-
-projectPath = path.resolve(atom.config.resourcePath, '../../project')
-
+# projectPath
+if process.platform == 'darwin'
+  resolvePath = '../../../project'
+else
+  resolvePath = '../../project'
+projectPath = path.resolve(atom.config.resourcePath, resolvePath)
 if fs.lstatSync(projectPath).isSymbolicLink()
   projectPath = fs.readlinkSync(projectPath)
   if process.platform == 'win32'
@@ -112,7 +115,7 @@
       # pkg.onDidCompleteConflictResolution ()->
       #   console.log('onDidCompleteConflictResolution')
       #   return
-      # console.log(atom.packages.getActivePackage('merge-conflicts'))
+      # console.log(atom.packages.getActivePackage('merge-conflicts'))
         # git.gitCmd args: ['rebase', '--continue']
         # .then () =>
         #   @generator.deployGhpages()
@@ -123,7 +126,7 @@
       #   # git.gitCmd args: ['rebase', '--continue']
       #   # .then () =>
       #   #   @generator.deployGhpages()
-      # subs.dispose()
+      # subs.dispose()

 #      httpServer.runChildProcess
 #        root: atom.config.get('docapp.outPath')


Remove Grim.includeDeprecatedAPIs short-circuits

--- lib/main.coffee
+++ lib/main.coffee
@@ -7,19 +7,12 @@
   DeprecationCopView ?= require './deprecation-cop-view'
   new DeprecationCopView(state)

-if Grim.includeDeprecatedAPIs
-  atom.deserializers.add
-    name: 'DeprecationCopView'
-    deserialize: createView
-
 module.exports =
   deprecationCopView: null
   deprecationCopStatusBarView: null
   commandSubscription: null

   activate: ->
-    return unless Grim.includeDeprecatedAPIs
-
     atom.workspace.addOpener (uriToOpen) =>
       return unless uriToOpen is viewUri
       @deprecationCopView = createView(uri: uriToOpen)
@@ -37,8 +30,6 @@
     @commandSubscription = null

   consumeStatusBar: (statusBar) ->
-    return unless Grim.includeDeprecatedAPIs
-
     DeprecationCopStatusBarView = require './deprecation-cop-status-bar-view'
     @deprecationCopStatusBarView ?= new DeprecationCopStatusBarView()
     statusBar.addRightTile(item: @deprecationCopStatusBarView, priority: 150)


fix error catching by registering error listener on original bundle stream

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -12,7 +12,7 @@
 fs = Npm.require 'fs'

 processFile = (step) ->
-
+
   # check for extension as filename
   checkFilename step

@@ -44,12 +44,13 @@
   # extract the source map content from the generated file to give to Meteor
   # explicitly by piping bundle thru `exorcist`
   mapFileName = step.fullInputPath+'.map'
-  bundle = bundle.pipe exorcist mapFileName, step.pathForSourceMap
+  exorcisedBundle = bundle.pipe exorcist mapFileName, step.pathForSourceMap
+  exorcisedBundle.originalBundle = bundle

   try # try-catch for browserify errors

     # get browserify result from either the cache or processing
-    string = getResult step, bundle, browserifyOptions?.cache
+    string = getResult step, exorcisedBundle, browserifyOptions?.cache

     # read the generated source map from the file
     sourceMap = fs.readFileSync mapFileName, encoding:'utf8'
@@ -272,4 +273,8 @@
   bundle.once 'end', -> cb undefined, string  # undefined = error

   # when there's an error, give it to the callback
-  bundle.once 'error', (error) -> cb error
+  # NOTE:
+  #  after piping bundle into exorcist transform the once('error') doesn't
+  #  work. fixed it by storing original bundle as a property and registering
+  #  the event callback on that instead.
+  bundle.originalBundle.once 'error', (error) -> cb error


:bug: Fix some typing errors

:bug: Fix some typing errors

--- lib/plus.coffee
+++ lib/plus.coffee
@@ -26,12 +26,12 @@
       return unless atom.workspace.getActiveTextEditor()
       @lint()
     @Subscriptions.add atom.workspace.observeTextEditors (editor)=>
-      return if @InProgress
       return unless editor.getPath()
       editor.onDidSave(@lint.bind(@))
       @Subscriptions.add editor.onDidChangeCursorPosition ({newBufferPosition})=>
         @View.updateBubble(newBufferPosition)
   lint:->
+    return if @InProgress
     @InProgress = true

     ActiveEditor = atom.workspace.getActiveTextEditor()
@@ -59,6 +59,7 @@
       @Messages = Messages
       @render()
     , =>
+      console.error.call console, arguments
       @InProgress = false
   render:->
     if not @Messages.length


sidebar: make section headers work like more link

--- client/Social/Activity/sidebar/activitysideview.coffee
+++ client/Social/Activity/sidebar/activitysideview.coffee
@@ -36,7 +36,9 @@
       tagName  : 'h3'
       cssClass : 'sidebar-title'
       partial  : @getOption 'title'
-      # click   : @bound 'reload'
+      click    : (event) =>
+        KD.utils.stopDOMEvent event
+        KD.singletons.router.handleRoute searchLink

     @listController.on 'ListIsEmptied', @lazyBound 'setClass', 'empty'


activity: rename route class name

activity: rename route class name

--- client/activity/lib/routes/singleprivatechannel.coffee
+++ client/activity/lib/routes/singleprivatechannel.coffee
@@ -16,7 +16,7 @@
 SingleMessageRoute = require './singlemessage'

-module.exports = class SinglePrivateMessageRoute
+module.exports = class SinglePrivateChannelRoute

   constructor: ->


add default value for suggested fields

add default value for suggested fields

--- client/controllers/incidentReports/incidentForm.coffee
+++ client/controllers/incidentReports/incidentForm.coffee
@@ -16,6 +16,7 @@
 Template.incidentForm.onCreated ->
   @incidentStatus = new ReactiveVar('')
   @incidentType = new ReactiveVar('')
+  @suggestedFields = new ReactiveVar([])

   @incidentData =
     species: 'Human'
@@ -100,9 +101,8 @@
     Template.instance().incidentType.get().slice(0, -1)

   suggestedField: (fieldName)->
-    if Template.instance().suggestedFields
-      if fieldName in Template.instance().suggestedFields.get()
-        "suggested"
+    if fieldName in Template.instance().suggestedFields.get()
+      "suggested"

 Template.incidentForm.events
   'change input[name=daterangepicker_start]': (event, instance) ->


Fix mising export

Fix mising export

This partially reverts commit 3a2734ac2b2cf39687feed9ece7a4c478869e6db.

--- lib/site.coffee
+++ lib/site.coffee
@@ -11,6 +11,7 @@
     parse: yaml.parse.bind(yaml)
     stringify: yaml.dump.bind(yaml)

+module.exports =
   newInfo: (infoStr) ->
     info =
       layout: 'greyhound'


Removed console.log

Removed console.log

--- server/lib/crawler/helpers.coffee
+++ server/lib/crawler/helpers.coffee
@@ -48,7 +48,6 @@
     {body} = activity

   tagMap = {}
-  console.log "activity.tags: ", activity.tags
   activity.tags?.forEach (tag) -> tagMap[tag.getId()] = tag

   return body.replace /\|(.+?)\|/g, (match, tokenString) ->


Fixed typo.

Fixed typo.

--- server/lib/crawler/helpers.coffee
+++ server/lib/crawler/helpers.coffee
@@ -1,4 +1,4 @@
- forceTwoDigits = (val) ->
+forceTwoDigits = (val) ->
   if val < 10
     return "0#{val}"
   return val


This condition check is no longer necessary

--- lib/view.coffee
+++ lib/view.coffee
@@ -1,5 +1,4 @@
 Base = require './base'
-_ = require 'underscore-plus'

 class ViewModel
   char: null
@@ -11,19 +10,11 @@
     @vimState.onDidFailToCompose => @view.remove()

   confirm: (view) ->
-    if _.isFunction(@operation.setInput)
-    # if @operation.isMotion() or @operation.isOperator() or @operation.isInsertRegister()
-      @operation.setInput(new Input(@view.value))
-    else
-      @vimState.operationStack.push(new Input(@view.value))
+    @operation.setInput(new Input(@view.value))

   cancel: (view) ->
     if @vimState.operationStack.isOperatorPending()
-      if _.isFunction(@operation.setInput)
-      # if @operation.isMotion() or @operation.isOperator() or @operation.isInsertRegister()
-        @operation.setInput(new Input(''))
-      else
-        @vimState.operationStack.push(new Input(''))
+      @operation.setInput(new Input(''))

 class Input extends Base
   @extend()


:new: Add updateTiles function

:new: Add updateTiles function

--- lib/view.coffee
+++ lib/view.coffee
@@ -19,6 +19,9 @@
     @Root.id = 'linter-panel'

   remove: ->
+    @Messages = []
+    @CountFile = 0
+    @CountProject = 0
     @Bubble?.destroy()
     @removeDecorations()
     @Root.innerHTML = ''
@@ -58,12 +61,15 @@
     while not Value.done
       @Messages = @Messages.concat Value.value
       @CountFile += Value.value.length
+      @CountProject += Value.value.length
       Value = Values.next()
     if not @Messages.length
       @Linter.ViewPanel.hide() if @Linter.ViewPanel.isVisible()
       @remove()
+      @updateTiles()
     else
       @update()
+      @updateTiles()
       @Linter.ViewPanel.show() unless @Linter.ViewPanel.isVisible()
   update: ->
     @Bubble?.destroy()
@@ -88,6 +94,22 @@
       )

     @updateBubble(TextEditor.getCursors()[0].getBufferPosition())
+
+  updateTiles: ->
+    @BarCurrent.Child.textContent = @CountFile.toString()
+    @BarProject.Child.textContent = @CountProject.toString()
+    if @Messages.length
+      @BarStatus.Root.classList.remove 'linter-success'
+      @BarStatus.Root.classList.add 'linter-error'
+      @BarStatus.Child.classList.remove 'icon-check'
+      @BarStatus.Child.classList.add 'icon-x'
+      @BarStatus.Child.textContent = if @Messages.length is 1 then '1 Error' else @Messages.length + ' Errors'
+    else
+      @BarStatus.Root.classList.remove 'linter-error'
+      @BarStatus.Root.classList.add 'linter-success'
+      @BarStatus.Child.classList.remove 'icon-x'
+      @BarStatus.Child.classList.add 'icon-check'
+      @BarStatus.Child.textContent = 'No Errors'

   initTiles: ->
     @BarCurrent = Views.currentFile(this)


Add unrefined() method to refinement library; gets unrefined object

--- lib_spec.coffee
+++ lib_spec.coffee
@@ -47,5 +47,6 @@
       it "adds the default refinement to objects that have no refinements", ->
         expect(@xp.refined()).toEqual "default"

+    it "provides a way to get the unrefined object", ->
+      expect(@ap.unrefined()).toEqual @a

-


Calculator Fixes: [#3738002]

Calculator Fixes: [#3738002]

Multiple operator button presses will replace the previous operator in
the calculation

--- calculator_spec.coffee
+++ calculator_spec.coffee
@@ -15,14 +15,19 @@
     @handle.press_buttons("8")
     expect(@handle.output_content()).toEqual("8")

+  describe "multiple sequential operator button presses", ->
+    it "automatically clears when another number starts getting entered after a calculation", ->
+      @handle.press_buttons("2 + - * / ^ 1 0 =")
+      expect(@handle.output_content()).toEqual("1024")
+
   describe "clearing after calculation", ->
     it "automatically clears when another number starts getting entered after a calculation", ->
-      @handle.press_buttons("2 ^ 2 = 1")
-      expect(@handle.output_content()).toEqual("1")
+      @handle.press_buttons("2 ^ 2 = 1 2 3")
+      expect(@handle.output_content()).toEqual("123")

     it "preserves numbers for future calculations", ->
-      @handle.press_buttons("2 ^ 2 = + 1 =")
-      expect(@handle.output_content()).toEqual("5")
+      @handle.press_buttons("2 ^ 2 = + 1 3 5 =")
+      expect(@handle.output_content()).toEqual("139")

   it "will display decimal numbers correctly", ->
     @handle.press_buttons("1 . 0 1")


move default value of lastVersion into function body

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -14,10 +14,18 @@
 		if length == 0
 			return callback()

-		MongoManager.popLastCompressedUpdate doc_id, (error, lastCompressedUpdate, lastVersion = lastCompressedUpdate?.v) ->
-			# lastCompressedUpdate may be null if we are forcing the start
-			# of a new compressed update, in which case we have the
-			# lastVersion to check consistency (defaults to lastCompressedUpdate.v)
+		MongoManager.popLastCompressedUpdate doc_id, (error, lastCompressedUpdate, lastVersion) ->
+			# lastCompressedUpdate is the most recent update in Mongo.
+			#
+			# The popLastCompressedUpdate method may pass it back as 'null'
+			# to force the start of a new compressed update, even when there
+			# was a previous compressed update in Mongo.  In this case it
+			# passes back the lastVersion from the update to check
+			# consistency.
+
+			# when lastVersion is not provided, default to lastCompressedUpdate.v
+			lastVersion ?= lastCompressedUpdate?.v
+
 			return callback(error) if error?

 			# Ensure that raw updates start where lastVersion left off


Remove unused formatSelection function.

Remove unused formatSelection function.

--- app/javascripts/intensity.coffee
+++ app/javascripts/intensity.coffee
@@ -1,7 +1,7 @@
 define ['angular', 'chart'], (ng) ->
-  intensity = ng.module 'qiprofile.intensity', ['qiprofile.chart']
+  intensity = ng.module 'qiprofile.intensity', []

-  intensity.factory 'Intensity', ['Chart', (Chart) ->
+  intensity.factory 'Intensity', ->
     # Highlights the bolus arrival tick mark. The bolus arrival is
     # only highlighted if it occurs after the first series.
     #
@@ -10,7 +10,7 @@
     highlightBolusArrival = (session, chart) ->
       # If the bolus arrival index is zero, then it does not have
       # have a tick mark, so bail.
-      if not session.bolus_arrival_index
+      if not session.bolusArrivalIndex
         return
       # Select the SVG element.
       svg = d3.select(chart.container)
@@ -20,29 +20,29 @@
       # g elements with the tick class. There is a tick mark for
       # all but the first and last series. Therefore, the tick
       # offset is one less than the bolus arrival index.
-      offset = session.bolus_arrival_index - 1
+      offset = session.bolusArrivalIndex - 1
       # The D3 CSS3 nth-of-type selector argument of zero returns
-      # null. Work around this possible D3 bug by using the
-      # first-of-type selector argument in that case.
+      # null. The work-around for this possible D3 bug by using
+      # the first-of-type selector argument in that case.
       if offset
-        bolusTickSltr = "g.tick:nth-of-type(#{ offset })"
+        bolusTickSlct = "g.tick:nth-of-type(#{ offset })"
       else
-        bolusTickSltr = "g.tick:first-of-type"
+        bolusTickSlct = "g.tick:first-of-type"
       # The bolus tick element.
-      bolusTick = xAxis.select(bolusTickSltr)
+      bolusTick = xAxis.select(bolusTickSlct)
       # The bolus tick child line element.
       bolusTickLine = bolusTick.select('line')
       highlightNode = bolusTickLine.node().cloneNode()
       highlight = d3.select(highlightNode)
       # Set the bolus CSS class.
       highlight.classed('qi-bolus-arrival', true)
-      # Insert the highlight SVG element after the tick line.
-      # The highlight will display centered over the tick line.
-      # D3 insert differs from jQuery insert. The D3 arguments are
-      # a function which returns a DOM element and the selector
-      # before which the node is inserted. In our case, the
-      # node to insert is the highlight node and the before
-      # selector is the bolus tick selector.
+      # Insert the highlight SVG element after the tick line. The
+      # highlight will display centered over the tick line. Note
+      # that D3 insert differs from jQuery insert. The D3 arguments
+      # are a function which returns a DOM element and the selector
+      # before which the node is inserted. In our case, the node to
+      # insert is the highlight node and the before selector is the
+      # bolus tick selector.
       highlightNodeFunc = -> highlightNode
       bolusTick.insert(highlightNodeFunc, 'line')
       # Add the legend.
@@ -110,9 +110,9 @@
       registration_data_series = (registration, index) ->
         # The registration image select button colors.
         COLORS = ['LightGreen', 'LightYellow', 'LightCyan', 'LemonChiffon']
-
+
         key = registration.title
-
+
         # TODO - Wrap the key in a registration parameters pop-up hyperlink.
         # Return the data series format object.
         key: key
@@ -131,7 +131,7 @@
         # series key.
         reg.data_series = registration_data_series(reg, i)
       reg_data = ((reg.data_series for reg in session.registrations))
-
+
       # Pad the image selection buttons based on the number of series.

       # Return the chart configuration.
@@ -140,55 +140,3 @@
       yFormat: yFormat
       highlightBolusArrival: (chart) ->
         highlightBolusArrival(session, chart)
-
-    # Formats the intensity selection block for the given session.
-    # Padding is added to each button sufficient to center the
-    # button under the intensity chart x-axis tick marks.
-    #
-    # @param element the selection Angular jQueryLite element
-    formatSelection: (element) ->
-      formatRow = (row) ->
-        # The width of the entire row.
-        rowWidth = row[0].clientWidth
-        # The selection key label and buttons.
-        children = row.children()
-        # The width of the first key element holding the label,
-        # e.g. 'Scan'.
-        keyWidth = children[0].clientWidth
-        # The button width without padding.
-        buttonWidth = children[1].clientWidth
-        # The number of buttons.
-        buttonCnt = children.length - 1
-        # The width available to pad the buttons.
-        available = rowWidth - keyWidth - (buttonCnt * buttonWidth)
-        # The button left and right pad amount. Since the left-most
-        # and right-most buttons are only padded on one side, count
-        # those two buttons as one for the purpose of computing the
-        # pad amount.
-        pad = available / (2 * (buttonCnt - 1))
-        # The buttons include all but the first child.
-        buttons = (ng.element(children[i]) for i in [1..buttonCnt])
-
-
-        # FIXME - this has no effect. Perhaps the margin precision
-        # is too large, since setting it to a precision > 3 in the
-        # style sheet fails. But setting margin here to an int
-        # has no effect either.
-        #
-        # TODO - remove the css margin and get this to work.
-        #
-        # # Add a right pad for all but the last button.
-        # for btn in buttons[0...buttonCnt-1]
-        #   btn.css('margin-right', pad)
-        # # Add a left pad for all but the first button.
-        # for btn in buttons[1...buttonCnt]
-        #   btn.css('margin-left', pad)
-
-      # Format each row.
-      for childDom in element.children()
-        child = ng.element(childDom)
-        if child.hasClass('row')
-          formatRow(child)
-        else
-          this.formatSelection(child)
-  ]


add large page size for user_roles

add large page size for user_roles

For projects with more than the default number of users that have roles on the project. We should probably just look at adding paging onto this page long term.

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -140,7 +140,7 @@
     saving: []

   fetchAllRoles: ->
-    Promise.all([@props.project.get('project_roles'), talkClient.type('roles').get(section: @talkSection(), page_size: 100)])
+    Promise.all([@props.project.get('project_roles', page_size: 100), talkClient.type('roles').get(section: @talkSection(), page_size: 100)])
       .then ([panoptesRoles, talkRoles]) ->
         for roleSet in panoptesRoles when roleSet.links.owner.type == 'users'
           roleSet['talk_roles'] = talkRoles.filter((role) -> role.links.user == roleSet.links.owner.id)


:new: Add some tests for message-registry

--- spec/message-registry-spec.coffee
+++ spec/message-registry-spec.coffee
@@ -1,9 +1,12 @@
 describe 'message-registry', ->
+  messageRegistry = null
   MessageRegistry = require('../lib/message-registry')
   EditorLinter = require('../lib/editor-linter')
   LinterRegistry = require('../lib/linter-registry')
-  nextAnimationFrame = ->
-    return new Promise (resolve) -> requestAnimationFrame(resolve)
+  objectSize = (obj) ->
+    size = 0
+    size++ for value of obj
+    return size
   getLinterRegistry = ->
     linterRegistry = new LinterRegistry
     editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
@@ -11,7 +14,7 @@
       grammarScopes: ['*']
       lintOnFly: false
       modifiesBuffer: false
-      scope: 'file'
+      scope: 'project'
       lint: -> return [{type: "Error", text: "Something"}]
     }
     linterRegistry.addLinter(linter)
@@ -20,51 +23,54 @@
   beforeEach ->
     waitsForPromise ->
       atom.workspace.destroyActivePaneItem()
-      atom.workspace.open('test.txt')
-
-  describe '::constructor', ->
-    it 'accepts not arguments', ->
-      messageRegistry = new MessageRegistry()
-      messageRegistry.deactivate()
-      expect(true).toBe(true)
+      atom.workspace.open('test.txt').then ->
+        messageRegistry?.deactivate()
+        messageRegistry = new MessageRegistry()

   describe '::set', ->
     it 'accepts info from LinterRegistry::lint', ->
-      messageRegistry = new MessageRegistry()
       {linterRegistry, editorLinter} = getLinterRegistry()
       wasUpdated = false
       linterRegistry.onDidUpdateMessages (linterInfo) ->
         wasUpdated = true
         messageRegistry.set(linterInfo)
-        expect(messageRegistry.updated).toBe(true)
+        expect(messageRegistry.hasChanged).toBe(true)
       waitsForPromise ->
         linterRegistry.lint({onChange: false, editorLinter}).then ->
           expect(wasUpdated).toBe(true)
           linterRegistry.deactivate()
-          messageRegistry.deactivate()

   describe '::onDidUpdateMessages', ->
     it 'is triggered asyncly with results', ->
-      messageRegistry = new MessageRegistry()
+      wasUpdated = false
       {linterRegistry, editorLinter} = getLinterRegistry()
       linterRegistry.onDidUpdateMessages (linterInfo) ->
         messageRegistry.set(linterInfo)
-        expect(messageRegistry.updated).toBe(true)
+        expect(messageRegistry.hasChanged).toBe(true)
+        messageRegistry.updatePublic()
       gotMessages = null
       messageRegistry.onDidUpdateMessages (messages) ->
+        wasUpdated = true
         gotMessages = messages
-      # TODO: Write this spec
+      waitsForPromise ->
+        linterRegistry.lint({onChange: false, editorLinter}).then ->
+          expect(wasUpdated).toBe(true)
+          linterRegistry.deactivate()

   describe '::deleteEditorMessages', ->
     it 'removes messages for that editor', ->
-      messageRegistry = new MessageRegistry()
+      wasUpdated = 0
       {linterRegistry, editorLinter} = getLinterRegistry()
       editor = editorLinter.editor
       linterRegistry.onDidUpdateMessages (linterInfo) ->
         messageRegistry.set(linterInfo)
-        expect(messageRegistry.updated).toBe(true)
-      gotMessages = null
-      messageRegistry.onDidUpdateMessages (messages) ->
-        gotMessages = messages
+        expect(messageRegistry.hasChanged).toBe(true)
+        messageRegistry.updatePublic()
+      messageRegistry.onDidUpdateMessages ({messages}) ->
+        wasUpdated = 1
+        expect(objectSize(messages)).toBe(1)
         messageRegistry.deleteEditorMessages(editor)
-      # TODO: Write this spec
+      waitsForPromise ->
+        linterRegistry.lint({onChange: false, editorLinter}).then ->
+          expect(wasUpdated).toBe(1)
+          linterRegistry.deactivate()


fixbug

fixbug

--- core/api/ticket.coffee
+++ core/api/ticket.coffee
@@ -225,8 +225,6 @@
                       callback null, result

               async.parallel tasks, (err, result) ->
-                console.log result
-
                 if req.body.members.add
                   for item in req.body.members.add
                     addToSetModifier.push result[item]._id
@@ -236,6 +234,8 @@
                     pullModifier.push result[item]._id

                 saveToDatabase()
+            else
+              saveToDatabase()

           else
             saveToDatabase()


Add builder visualizer.

Add builder visualizer.

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -20,8 +20,11 @@
 #    world.generateMap()
 #    world.carsNumber = 0
   window.visualizer = new Visualizer world, canvas
-  window.builderVisualizer = new Visualizer world, builderCanvas
-#  visualizer.start()
+
+  window.builderWorld = new World()
+  window.builderVisualizer = new Visualizer builderWorld, builderCanvas
+  window.builderVisualizer.start()
+
   window.settings = settings
 #   gui = new DAT.GUI()
 #   guiWorld = gui.addFolder 'world'


Fix auto adition of new users in general room

--- server/methods/setUsername.coffee
+++ server/methods/setUsername.coffee
@@ -28,7 +28,7 @@
 			# put user in general channel
 			ChatRoom.update '57om6EQCcFami9wuT',
 				$addToSet:
-					username: username
+					usernames: username

 			if not ChatSubscription.findOne(rid: '57om6EQCcFami9wuT', 'u._id': user._id)?
 				ChatSubscription.insert