client: koding utilities: fixed missing space

--- client/home/lib/utilities/components/kdcli/view.coffee
+++ client/home/lib/utilities/components/kdcli/view.coffee
@@ -13,8 +13,8 @@
           IDE with your VMs. Copy and paste the command below into your terminal.

       <CodeBlock cmd={@props.cmd}/>
-
Once installed, you can use kd list to list your Koding VMs and
-        kd mount to mount your VM to a local folder in your computer.
+
Once installed, you can use kd list to list your Koding VMs
+        and kd mount to mount your VM to a local folder in your computer.
         For detailed instructions:


major fixes to directory names

major fixes to directory names

--- projects.cson
+++ projects.cson
@@ -1,41 +1,17 @@
 [
-  {
-    title: "LF Word Count"
-    group: "Atom"
-    paths: [
-      "~\\Devel\\atom\\lloyd-flanagan-word-count"
-    ]
-    icon: "atom-icon"
-  }
   {
     title: "Atom Configuration"
     group: "Atom"
     paths: [
-      "~\\Devel\\atom\\atom_configuration"
+      "C:\\Users\\aflanaga\\Documents\\github\\atom\\atom_configuration"
     ]
     icon: "atom-icon"
-  }
-  {
-    title: "Atom"
-    group: "Atom"
-    paths: [
-      "~\\Devel\\atom\\atom"
-    ]
-    icon: "atom-icon"
-  }
-  {
-    title: "Svgmaps"
-    group: "BH Media"
-    paths: [
-      "~\\Devel\\svgmaps"
-    ]
-    icon: "icon-tools"
   }
   {
     title: "Atom Language Utl"
     group: "Atom"
     paths: [
-      "~\\Devel\\atom\\atom-language-utl"
+      "C:\\Users\\aflanaga\\Documents\\github\\atom\\atom-language-utl"
     ]
     icon: "atom-icon"
   }
@@ -43,15 +19,7 @@
     title: "Utl Indexer"
     group: "BLOX"
     paths: [
-      "~\\Devel\\utl_indexer"
-    ]
-    icon: "mdi mdi-magnet"
-  }
-  {
-    title: "Townnews Reader"
-    group: "BLOX"
-    paths: [
-      "~\\Devel\\townnews_reader"
+        "C:\\Users\\aflanaga\\Documents\\github\\utl_indexer"
     ]
     icon: "mdi mdi-magnet"
   }
@@ -59,7 +27,7 @@
     title: "Shared Libraries"
     group: "BH Media"
     paths: [
-      "~\\Devel\\shared_libraries"
+        "C:\\Users\\aflanaga\\Documents\\github\\shared_libraries"
     ]
     icon: "mdi mdi-magnet"
   }
@@ -67,7 +35,7 @@
     title: "Utl Lookup"
     group: "BLOX"
     paths: [
-      "~\\Devel\\utl_lookup"
+        "C:\\Users\\aflanaga\\Documents\\github\\utl_lookup"
     ]
     icon: "mdi mdi-magnet"
   }
@@ -75,23 +43,15 @@
     title: "Gmail Intfc"
     group: "Mail"
     paths: [
-      "~\\Devel\\personal\\mail-electron-intfc"
+        "C:\\Users\\aflanaga\\Documents\\github\\mail-electron-intfc"
     ]
     icon: "icon-mail-read"
-  }
-  {
-    title: "Hacker Web"
-    group: "HackRVA"
-    paths: [
-      "~\\Devel\\personal\\hackrva\\hacker-web"
-    ]
-    icon: "ion-social-html5"
   }
   {
     title: "Gmail Web Svcs"
     group: "Mail"
     paths: [
-      "~\\Devel\\personal\\mail-web-srvcs"
+        "C:\\Users\\aflanaga\\Documents\\github\\mail-web-srvcs"
     ]
     icon: "icon-mail-read"
   }
@@ -99,50 +59,24 @@
     title: "Badge2017"
     group: "HackRVA"
     paths: [
-      "\\home\\aflanagan\\Devel\\personal\\hackrva\\Harmony-Badge-2017\\firmware"
-      "\\home\\aflanagan\\Devel\\personal\\hackrva\\microchip\\harmony\\v1_09"
+        "C:\\Users\\aflanaga\\Documents\\github\\hackrva\\Harmony-Badge-2017\\firmware"
     ]
     icon: "mdi mdi-radioactive"
-  }
-  {
-    title: "The Jobs Network Widgets"
-    group: "BH Media"
-    paths: [
-      "~\\Devel\\richmond.com\\jobs.thejobnetwork.com"
-    ]
-    icon: "icon-package"
   }
   {
     title: "Symbol Quick Pane"
     group: "Atom"
     paths: [
-      "~\\Devel\\atom\\atom-symbol-quick-pane"
-      "~\\Devel\\atom\\atom\\src"
+        "C:\\Users\\aflanaga\\Documents\\github\\atom\\atom-symbol-quick-pane"
     ]
     icon: "atom-icon"
     devMode: true
   }
   {
-    title: "Site Setup"
-    group: "BH Media"
-    paths: [
-      "\\home\\aflanagan\\Devel\\site_setup"
-    ]
-    icon: "mdi mdi-beta"
-  }
-  {
-    title: "Nebhssports"
-    group: "BH Media"
-    paths: [
-      "\\home\\aflanagan\\Devel\\nebhssports"
-    ]
-    icon: "devicon-python-plain"
-  }
-  {
     title: "Node Find"
     group: "Node"
     paths: [
-      "\\home\\aflanagan\\Devel\\personal\\node_find"
+        "C:\\Users\\aflanaga\\Documents\\github\\node_find"
     ]
     icon: "icon-search"
   }
@@ -150,7 +84,7 @@
     title: "Api Helper"
     group: "BH Media"
     paths: [
-      "\\home\\aflanagan\\Devel\\realmatch\\api_helper"
+        "C:\\Users\\aflanaga\\Devel\\api_helper"
     ]
     icon: "devicon-php-plain"
   }
@@ -158,22 +92,8 @@
     title: "Realmatch Code"
     group: "BH Media"
     paths: [
-      "\\home\\aflanagan\\Devel\\realmatch\\realmatch_code"
+        "C:\\Users\\aflanaga\\Documents\\github\\realmatch_code"
     ]
     icon: "devicon-php-plain"
   }
-  {
-    title: "Www"
-    group: "BH Media"
-    paths: [
-      "\\home\\aflanagan\\Devel\\www"
-    ]
-  }
-  {
-    title: "Realmatch Code"
-    icon: "devicon-php-plain"
-    paths: [
-      "~\\Documents\\GitHub\\realmatch_code"
-    ]
-  }
 ]


Removed AppAid from Options

Removed AppAid from Options

--- app/views.coffee
+++ app/views.coffee
@@ -45,14 +45,17 @@
       (err, res) ->
         if err? then notify err.message; return
         kdAppNames = res.split('\n')[..-1]
-        kdAppNames = (
-          {title: appname, value: appname} for appname in kdAppNames)
-        appSelectBox.setSelectOptions kdAppNames
+        kdAppNameOpt = []
+        for appname in kdAppNames
+          if appname is 'appaid.kdapp' then continue
+          kdAppNameOpt.push {title: appname, value: appname}
+        appSelectBox.setSelectOptions kdAppNameOpt

     appLoadBtn = new KDButtonView
       title     : 'Load App'
       callback  : =>
-        notify 'Loading..!'
+        notify 'Loading..'
+

     appSplit = new KDSplitView
       type      : 'vertical'


Remove script package from disabled keymaps list

--- atom/config.cson
+++ atom/config.cson
@@ -60,7 +60,6 @@
       "linter"
       "merge-conflicts"
       "resize-indent"
-      "script"
     ]
     projectHome: "~/Repositories"
     telemetryConsent: "limited"


Tweak Atom more

Tweak Atom more

--- atom/config.cson
+++ atom/config.cson
@@ -1,4 +1,11 @@
 "*":
+  "activate-power-mode":
+    autoToggle: false
+    particles:
+      size: {}
+      spawnCount: {}
+      totalCount: {}
+    screenShake: {}
   "atom-beautify":
     analytics: false
     c_configPath: "/home/stephen/.uncrustify.cfg"


Re-enable minimap and :burn: cruft

Re-enable minimap and :burn: cruft

--- atom/config.cson
+++ atom/config.cson
@@ -35,8 +35,6 @@
     ]
     audioBeep: false
     disabledPackages: [
-      "minimap"
-      "minimap-find-and-replace"
     ]
   "auto-copyright":
     owner: "Lifted Studios"
@@ -97,95 +95,36 @@
   "asciidoc-preview": {}
   "find-and-replace":
     openProjectFindResultsInRightPane: true
-".html.php.text":
+".c.source":
   editor:
-    preferredLineLength: 100
-".elixir.html.text":
+    tabLength: 4
+".python.source":
   editor:
-    preferredLineLength: 100
-".basic.html.text":
+    tabLength: 4
+".java.source":
   editor:
-    preferredLineLength: 100
-".erb.html.text":
+    tabLength: 4
+".git-commit.text":
   editor:
-    preferredLineLength: 100
-".console.python.text":
+    preferredLineLength: 72
+".shell.source":
   editor:
-    preferredLineLength: 100
-".python.text.traceback":
+    tabLength: 4
+".json.source":
+  "wrap-guide":
+    enabled: false
+".asciidoc.source":
+  "wrap-guide":
+    enabled: false
   editor:
-    preferredLineLength: 100
-".python.regexp.source":
-  editor:
-    preferredLineLength: 100
+    softWrap: true
+".haml.text":
+  "wrap-guide":
+    enabled: false
 ".gfm.source":
   editor:
     showInvisibles: false
     softWrap: true
     tabLength: 4
-    preferredLineLength: 100
   "wrap-guide":
     enabled: false
-".litcoffee.source":
-  editor:
-    preferredLineLength: 100
-".coffee.source":
-  editor:
-    preferredLineLength: 100
-".elixir.source":
-  editor:
-    preferredLineLength: 100
-".elixir.text":
-  editor:
-    preferredLineLength: 100
-".source.yaml":
-  editor:
-    preferredLineLength: 100
-".pyjade.source":
-  editor:
-    preferredLineLength: 100
-".jade.source":
-  editor:
-    preferredLineLength: 100
-".haml.text":
-  "wrap-guide":
-    enabled: false
-  editor:
-    preferredLineLength: 100
-".hamlc.text":
-  editor:
-    preferredLineLength: 100
-".asciidoc.source":
-  "wrap-guide":
-    enabled: false
-  editor:
-    softWrap: true
-".json.source":
-  "wrap-guide":
-    enabled: false
-".shell.source":
-  editor:
-    tabLength: 4
-".git-commit.text":
-  editor:
-    preferredLineLength: 72
-".java.source":
-  editor:
-    tabLength: 4
-".plain.text":
-  editor:
-    preferredLineLength: 100
-".ruby.source":
-  editor:
-    preferredLineLength: 100
-".python.source":
-  editor:
-    tabLength: 4
-    preferredLineLength: 100
-".c.source":
-  editor:
-    tabLength: 4
-    preferredLineLength: 100
-".cpp.source":
-  editor:
-    preferredLineLength: 100


Revert "Temporarily disable language-c package"

This reverts commit 0c42e0b1b90f5bc8ccb059a8bc38ff08ac80ff17.

--- atom/config.cson
+++ atom/config.cson
@@ -49,7 +49,6 @@
       "welcome"
       "whitespace"
       "teletype"
-      "language-c"
     ]
     followSymlinks: false
     ignoredNames: [


Latest Atom configuration

Latest Atom configuration

--- atom/config.cson
+++ atom/config.cson
@@ -127,6 +127,7 @@
     onSave: "untabify"
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
 ".asciidoc.source":


Disable language-c pkg in favor of language-cpp14

--- atom/config.cson
+++ atom/config.cson
@@ -18,6 +18,7 @@
       "bookmarks"
       "autosave"
       "deprecation-cop"
+      "language-c"
     ]
     projectHome: "~"
     themes: [
@@ -77,3 +78,4 @@
     ]
     markerType: "dot"
   whitespace: {}
+  "find-and-replace": {}


Sort list of Atom packages with keymaps disabled

--- atom/config.cson
+++ atom/config.cson
@@ -49,16 +49,16 @@
       "Thumbs.db"
     ]
     packagesWithKeymapsDisabled: [
+      "atom-alignment"
+      "autoflow"
       "emmet"
-      "atom-alignment"
-      "linter"
-      "resize-indent"
-      "merge-conflicts"
-      "autoflow"
-      "link"
-      "grammar-selector"
       "encoding-selector"
       "go-to-line"
+      "grammar-selector"
+      "link"
+      "linter"
+      "merge-conflicts"
+      "resize-indent"
       "script"
     ]
     projectHome: "~/Local/Repositories"


Change font size in atom

Change font size in atom

--- atom/config.cson
+++ atom/config.cson
@@ -15,6 +15,7 @@
       "atom-material-syntax"
     ]
   editor:
+    fontSize: 12
     invisibles: {}
     showIndentGuide: true
     tabLength: 4


Tweak a few Atom settings

Tweak a few Atom settings

--- atom/config.cson
+++ atom/config.cson
@@ -3,6 +3,7 @@
     enabled: true
   core:
     audioBeep: false
+    destroyEmptyPanes: false
     projectHome: "/Users/randy/src"
     themes: [
       "one-light-ui"
@@ -40,6 +41,8 @@
   "ruby-test":
     specFramework: "rspec"
     testFramework: "minitest"
+  "split-diff":
+    syncHorizontalScroll: true
   tabs:
     enableVcsColoring: true
   "terminal-plus":


Add .alfredworkflow to list of ignored names

--- atom/config.cson
+++ atom/config.cson
@@ -38,6 +38,7 @@
       "*.out"
       "*.docx"
       "*.pages"
+      "*.alfredworkflow"
     ]
     packagesWithKeymapsDisabled: [
       "emmet"


Fonts that Atom likes

Fonts that Atom likes

--- atom/config.cson
+++ atom/config.cson
@@ -8,6 +8,7 @@
     titleBar: "custom"
     useTreeSitterParsers: true
   editor:
+    fontFamily: "SF Mono, Menlo, Consolas, DejaVu Sans Mono, monospace"
     showIndentGuide: true
     showInvisibles: true
   "exception-reporting":


Add more vim-unimpaired keymaps for atom

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -25,6 +25,7 @@
     # toggle snap-on panes
     'cmd-o': 'outline-view:toggle'
     'cmd-t': 'atom-ide-terminal:toggle'
+    'cmd-u': 'fb-interactive-smartlog:toggle-commit-info-view'

 # vim-mode-plus
 # ----------------------------------------
@@ -33,6 +34,7 @@

 'atom-text-editor.vim-mode-plus':
     # disable package defaults so global mappings work
+    'cmd-u':  'unset!'
     'ctrl-d': 'unset!'
     'ctrl-w ctrl-h': 'unset!'
     'ctrl-w ctrl-j': 'unset!'
@@ -49,11 +51,6 @@
     'ctrl-j': 'vim-mode-plus:scroll-half-screen-down'
     'ctrl-k': 'vim-mode-plus:scroll-half-screen-up'

-    # spell check on/off, correct
-    'y o s': 'spell-check:toggle'
-    'z =': 'spell-check:correct-misspelling'
-    'space w': 'spell-check:correct-misspelling'
-
 # normal mode
 'atom-text-editor.vim-mode-plus.normal-mode':
     # undo/redo
@@ -65,15 +62,23 @@
     # code navigation
     'ctrl-t': 'nuclide-navigation-stack:navigate-backwards'

-     # vim-unimpaired
+    # vim-unimpaired
+    '[ space': 'vim-mode-plus:add-blank-line-above'
     '] space': 'vim-mode-plus:add-blank-line-below'
-    '[ space': 'vim-mode-plus:add-blank-line-above'
+    '[ e': 'editor:move-line-up'
+    '] e': 'editor:move-line-down'
+    # nemonic: y o (switch on)
+    'y o s': 'spell-check:toggle'

     # make Y work like C and D
     'Y': 'vim-mode-plus:yank-to-last-character-of-line'

     # toggle highlighted search
     'space /': 'vim-mode-plus:toggle-highlight-search'
+
+    # spell check word
+    'z =': 'spell-check:correct-misspelling'
+    'space w': 'spell-check:correct-misspelling'

 # insert mode
 # 'atom-text-editor.vim-mode-plus.insert-mode':


Use apiClient headers as talkClient headers

- Fix for #1052

--- app/api/talk.coffee
+++ app/api/talk.coffee
@@ -7,18 +7,7 @@
   'Content-Type': 'application/json'
   'Accept': 'application/json'

-authClient.listen 'change', ->
-  authClient.checkCurrent()
-    .then (user) ->
-      if user
-        token = authClient._bearerToken
-        talkClient.headers['Authorization'] = "Bearer #{token}"
-      else
-        delete talkClient.headers['Authorization']
-    .catch (e) ->
-      throw new Error "Failed to checkCurrent auth from talk api client"
-
-# talkClient.headers = apiClient.headers
+talkClient.headers = apiClient.headers
 talkClient.handleError = apiClient.handleError

 window?.talkClient = talkClient


Return assets

Return assets

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -69,6 +69,7 @@
       for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{version}-full.nupkg"]
         cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
         assets.push({assetName: squirrelAsset, sourcePath: assetName})
+      assets
     when 'linux'
       buildDir = grunt.config.get('atom.buildDir')
       if process.arch is 'ia32'


Don't close modal when highlighting new factlink -- depends on https://github.com/Factlink/js-library/pull/150

--- core/app/backbone/controllers/client_controller.coffee
+++ core/app/backbone/controllers/client_controller.coffee
@@ -22,8 +22,7 @@
     if Factlink.Global.signed_in
       fact.save {},
         success: =>
-          # TODO: don't close modal when highlighting new factlink
-          @annotatedSiteEnvoy 'closeModal_highlightNewFactlink', params.fact, fact.id
+          @annotatedSiteEnvoy 'highlightNewFactlink', params.fact, fact.id

           unless params.guided
             Backbone.history.navigate "/client/facts/#{fact.id}", trigger: true


Stick KL-specific route into an if.

Stick KL-specific route into an if.

--- core/app/backbone/initializers/site_initializer.coffee
+++ core/app/backbone/initializers/site_initializer.coffee
@@ -4,40 +4,44 @@

 class FactlinkRouter extends Backbone.Router
   routes:
-    'feed': ->
-      showComponent ReactFeedSelection()
-      mp_track 'Viewed feed'
+    _.extend(
+      if window.Kennisland
+        'f/:fact_id': (fact_id) ->
+          Factlink.load_client_dependencies()

-    'search': 'search' # must be named
+          fact = new Fact id: fact_id
+          fact.fetch()

-    ':username/edit': ->
-      showComponent ReactProfileEdit model: currentSession.user()
+          showComponent ReactDiscussionStandalone
+            model: fact
+            initiallyFocusAddComment: true
+            key: fact_id
+            site_url: ('f/' + fact_id)
+      else
+        {}
+    ,

-    ':username/notification-settings': ->
-      showComponent ReactNotificationSettings model: currentSession.user()
+      'feed': ->
+        showComponent ReactFeedSelection()
+        mp_track 'Viewed feed'

-    ':username/change-password': ->
-      showComponent ReactChangePassword model: currentSession.user().password()
+      'search': 'search' # must be named

-    ':username': (username) ->
-      user = new User(username: username)
-      user.fetch()
+      ':username/edit': ->
+        showComponent ReactProfileEdit model: currentSession.user()

-      showComponent ReactProfile model: user
+      ':username/notification-settings': ->
+        showComponent ReactNotificationSettings model: currentSession.user()

-    'f/:fact_id': (fact_id) ->
-      Factlink.load_client_dependencies()
+      ':username/change-password': ->
+        showComponent ReactChangePassword model: currentSession.user().password()

-      fact = new Fact id: fact_id
-      fact.fetch()
+      ':username': (username) ->
+        user = new User(username: username)
+        user.fetch()

-      showComponent ReactDiscussionStandalone
-        model: fact
-        initiallyFocusAddComment: true
-        key: fact_id
-        site_url: ('f/' + fact_id)
-
-
+        showComponent ReactProfile model: user
+    )
   search: (params={}) ->
     @once 'route', (route) ->
       return if route == 'search'


redirect to dashboard when course is not found

--- tutor/src/components/student-dashboard/index.cjsx
+++ tutor/src/components/student-dashboard/index.cjsx
@@ -5,6 +5,7 @@
 isStepComplete = (step) -> step.is_completed
 StudentDashboard = require './dashboard'
 WindowHelpers = require '../../helpers/window'
+{NotificationActions} = require 'shared'

 StudentDashboardShell = React.createClass
   displayName: 'StudentDashboardShell'
@@ -17,9 +18,14 @@
     # Will display the redirect screen if course is a concept coach one
     willTransitionTo: (transition, params, query, callback) ->
       {courseId} = params
-      {is_concept_coach, webview_url} = CourseStore.get(courseId)
-      if is_concept_coach and webview_url
-        transition.redirect('viewStudentCCRedirect', {courseId})
+      course = CourseStore.get(courseId)
+      if course
+        {is_concept_coach, webview_url} = course
+        if is_concept_coach and webview_url
+          transition.redirect('viewStudentCCRedirect', {courseId})
+      else
+        NotificationActions.display(message: 'That is not a valid course', icon: 'exclamation-circle')
+        transition.redirect('dashboard')
       callback()

   render: ->


Fix Tab Stops

Fix Tab Stops

https://github.com/emmetio/emmet-atom/issues/197

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -9,6 +9,9 @@

 insertSnippet = (snippet, editor) ->
   atom.packages.getLoadedPackage('snippets')?.mainModule?.insert(snippet, editor)
+
+  # Fetch expansions and assign to editor
+  editor.snippetExpansion = atom.packages.getLoadedPackage('snippets')?.mainModule?.getExpansions(editor)[0]

 visualize = (str) ->
   str


Edge case error message.

Edge case error message.

--- src/Witness/Scripts/witness/dsl/pageAutomation.coffee
+++ src/Witness/Scripts/witness/dsl/pageAutomation.coffee
@@ -50,6 +50,8 @@
 				anchorObj.dispatchEvent evt
 			else if anchorObj.click
 				anchorObj.click()
+			else
+				throw new Error "Unable to simulate click in this web browser."

 		$(selector, @document).each -> fakeClick this


Remove debug line

Remove debug line

--- services/web/public/coffee/ide/history/HistoryV2Manager.coffee
+++ services/web/public/coffee/ide/history/HistoryV2Manager.coffee
@@ -21,7 +21,6 @@
 					@reloadDiff()

 			@$scope.$watch "history.selection.pathname", () =>
-				console.log("pathname updated, reloading diff")
 				@reloadDiff()

 		show: () ->


tests

tests

--- vendor/assets/javascripts/planning_view.js.coffee
+++ vendor/assets/javascripts/planning_view.js.coffee
@@ -3,44 +3,44 @@
 #
 $(document).ready ->

-#do stuff only on planning views
+  #do stuff only on planning views
   if $('#planning-view__container').length

-#START BUDGETS SPECIFIC FILTERS
-#
-#
-#  checkboxes for status and priority
-#  (see partial '_planning_filters.html.erb')
-#$(document).on 'change', "input[type='checkbox'][id='status'], input[type='checkbox'][id='priority']", () ->
-#  unchecked_status = []
-#  unchecked_priority = []
-#  $("input[type='checkbox'][id='status']:not(:checked)").each () ->
-#    unchecked_status.push $(this).val()
-#  $("input[type='checkbox'][id='priority']:not(:checked)").each () ->
-#    unchecked_priority.push $(this).val()
-#  $('.planning-view__row').each () ->
-#    el = $(this);
-#    status = el.data 'status-id'
-#    priority = el.data 'priority'
-#    if $.inArray(String(priority), unchecked_priority) == -1  && $.inArray(String(status), unchecked_status) == -1
-#      el.show()
-#    else
-#      el.hide()
-#
-#  all/none buttons
-#$(document).on 'click', '.filter-link', (e) ->
-#  e.preventDefault()
-#  el = $(this)
-#  $("input[type='checkbox'][name='" + el.data('target') + "']").prop 'checked', (el.data('action') == 'all')
-#  $("input[type='checkbox'][name='" + el.data('target') + "']").change()
-#
-#
-#END BUDGETS SPECIFIC FILTERS
-
-#START INTERACTIVE
-#
-#
-#  enable/disable keyboard shortcuts 'left' and 'right'
+    #START BUDGETS SPECIFIC FILTERS
+    #
+    #
+    #  checkboxes for status and priority
+    #  (see partial '_planning_filters.html.erb')
+    #$(document).on 'change', "input[type='checkbox'][id='status'], input[type='checkbox'][id='priority']", () ->
+    #  unchecked_status = []
+    #  unchecked_priority = []
+    #  $("input[type='checkbox'][id='status']:not(:checked)").each () ->
+    #    unchecked_status.push $(this).val()
+    #  $("input[type='checkbox'][id='priority']:not(:checked)").each () ->
+    #    unchecked_priority.push $(this).val()
+    #  $('.planning-view__row').each () ->
+    #    el = $(this);
+    #    status = el.data 'status-id'
+    #    priority = el.data 'priority'
+    #    if $.inArray(String(priority), unchecked_priority) == -1  && $.inArray(String(status), unchecked_status) == -1
+    #      el.show()
+    #    else
+    #      el.hide()
+    #
+    #  all/none buttons
+    #$(document).on 'click', '.filter-link', (e) ->
+    #  e.preventDefault()
+    #  el = $(this)
+    #  $("input[type='checkbox'][name='" + el.data('target') + "']").prop 'checked', (el.data('action') == 'all')
+    #  $("input[type='checkbox'][name='" + el.data('target') + "']").change()
+    #
+    #
+    #END BUDGETS SPECIFIC FILTERS
+
+    #START INTERACTIVE
+    #
+    #
+    #  enable/disable keyboard shortcuts 'left' and 'right'
     $(document).bind 'keydown', pa_planning_shortcuts
     $(document).bind 'keyup', pa_planning_shortcuts_unlock

@@ -80,124 +80,124 @@
     #  bind scroll to update header position
     $(window).bind 'scroll', pa_planning_update_header
     pa_planning_update_header()
-#
-#
-#END INITIALIZATION
+    #
+    #
+    #END INITIALIZATION
+
+    #START GLOBAL VARIABLES
+    #
+    #
+    window.pa_planning_y_limit = 0         #vertical scroll position for the floating header
+    window.pa_planning_ctrl_locker = false #keydown status to prevent from repeating control key
+    #
+    #
+    #END GLOBAL VARIABLES
+
+    #START UTILITY FUNCTION
+    #
+    #
+    #  update header's position
+    pa_planning_update_header = (e) ->
+      y = $(window).scrollTop()
+      if y > window.pa_planning_y_limit
+        if !$('.planning-view__header').hasClass 'planning-view__header--fixed'
+          $('.planning-view__header').addClass 'planning-view__header--fixed'
+          $('.planning-view__body').addClass 'planning-view__body--alone'
+      else
+        if $('.planning-view__header').hasClass 'planning-view__header--fixed'
+          $('.planning-view__header').removeClass 'planning-view__header--fixed'
+          $('.planning-view__body').removeClass 'planning-view__body--alone'
+
+    #  manage keyboard shortcuts
+    pa_planning_shortcuts = (e) ->
+      unless window.pa_planning_ctrl_locker
+        if e.keyCode == 37
+          #move left
+          window.pa_planning_ctrl_locker = true
+          pa_planning_horizontal_move 10
+        else if e.keyCode == 39
+          #move right
+          window.pa_planning_ctrl_locker = true
+          pa_planning_horizontal_move -10
+
+    #  unlock keyboard shortcuts
+    pa_planning_shortcuts_unlock = () ->
+      window.pa_planning_ctrl_locker = false
+
+    #  access properties of the wrapper's css rule
+    window.CCSStylesheetRuleStyle2 = (stylesheet, selector, style, value = undefined) ->
+      property = undefined
+      for i in document.styleSheets
+        if i.title && i.title.indexOf stylesheet != -1
+          rules = i[if document.all then 'rules' else 'cssRules']
+          for j in rules
+            if j.selectorText == selector
+              property = j.style
+              break
+          break
+      if property == undefined
+        css = "#planning-view__container .planning-view__row-wrapper { #{property}: #{value} }"
+        console.log css
+        head = document.head || document.getElementsByTagName('head')[0]
+        style = document.createElement 'style'
+        style.type = 'text/css'
+        style.title = 'pa-planning-cssRules'
+        if style.styleSheet
+          style.styleSheet.cssText = css;
+        else
+          style.appendChild(document.createTextNode css)
+        head.appendChild(style)
+      else
+        console.log "YES"
+        if value == undefined
+          return property[style]
+        else
+          return property[style] = value
+
+
+    pa_planning_wrapper_property = (property, value = undefined) ->
+      return CCSStylesheetRuleStyle2 'pa-planning-cssRules', '#planning-view__container .planning-view__row-wrapper', property, value
+
+    #  move left or right the wrappers
+    pa_planning_horizontal_move = (delta) ->
+      unless delta == 0
+        width = parseInt(pa_planning_wrapper_property 'width')
+        margin_left = parseFloat(pa_planning_wrapper_property 'margin-left') + delta;
+        min_margin_left = (width) * -1 + 90;
+        if margin_left >= 10
+          margin_left = 10
+          $('.planning-view__ctrl-move-left').hide()
+        else
+          $('.planning-view__ctrl-move-left').show()
+        if margin_left <= min_margin_left
+          margin_left = min_margin_left
+          $('.planning-view__ctrl-move-right').hide()
+        else
+          $('.planning-view__ctrl-move-right').show()
+        pa_planning_wrapper_property 'margin-left', margin_left + '%'
+
+    # zoom control
+    pa_planning_zoom = (zoom) ->
+      dropdown = $('.planning-view__header--control-zoom')
+      el = dropdown.find('a[data-zoom=' + zoom + ']')
+      dropdown.find('li').removeClass('active')
+      dropdown.find('a[data-toggle=dropdown] span').html(el.html())
+      el.parent().addClass('active')
+      old_width = parseFloat(pa_planning_wrapper_property 'width')
+      new_width = parseFloat(el.data 'zoom')
+      if old_width != new_width
+        old_margin_left = parseFloat(pa_planning_wrapper_property 'margin-left')
+        new_margin_left = ((old_margin_left + ((old_width - 100.0) / 2.0)) * new_width / old_width) - ((new_width - 100.0) / 2.0)
+        pa_planning_wrapper_property 'width', new_width + '%'
+        pa_planning_horizontal_move -(old_margin_left - new_margin_left)
+        if parseInt(el.data('nb-of-months')) > 30
+          $('.planning-view__header--month').addClass('vertical');
+        else
+          $('.planning-view__header--month').removeClass('vertical');
+    #
+    #
+    #END UTILITY FUNCTIONS

 #
 #
 #END DOCUMENT READY
-
-#START GLOBAL VARIABLES
-#
-#
-window.pa_planning_y_limit = 0         #vertical scroll position for the floating header
-window.pa_planning_ctrl_locker = false #keydown status to prevent from repeating control key
-#
-#
-#END GLOBAL VARIABLES
-
-#START UTILITY FUNCTION
-#
-#
-#  update header's position
-pa_planning_update_header = (e) ->
-  y = $(window).scrollTop()
-  if y > window.pa_planning_y_limit
-    if !$('.planning-view__header').hasClass 'planning-view__header--fixed'
-      $('.planning-view__header').addClass 'planning-view__header--fixed'
-      $('.planning-view__body').addClass 'planning-view__body--alone'
-  else
-    if $('.planning-view__header').hasClass 'planning-view__header--fixed'
-      $('.planning-view__header').removeClass 'planning-view__header--fixed'
-      $('.planning-view__body').removeClass 'planning-view__body--alone'
-
-#  manage keyboard shortcuts
-pa_planning_shortcuts = (e) ->
-  unless window.pa_planning_ctrl_locker
-    if e.keyCode == 37
-      #move left
-      window.pa_planning_ctrl_locker = true
-      pa_planning_horizontal_move 10
-    else if e.keyCode == 39
-      #move right
-      window.pa_planning_ctrl_locker = true
-      pa_planning_horizontal_move -10
-
-#  unlock keyboard shortcuts
-pa_planning_shortcuts_unlock = () ->
-  window.pa_planning_ctrl_locker = false
-
-#  access properties of the wrapper's css rule
-window.CCSStylesheetRuleStyle2 = (stylesheet, selector, style, value = undefined) ->
-  property = undefined
-  for i in document.styleSheets
-    if i.title && i.title.indexOf stylesheet != -1
-      rules = i[if document.all then 'rules' else 'cssRules']
-      for j in rules
-        if j.selectorText == selector
-          property = j.style
-          break
-      break
-  if property == undefined
-    css = "#planning-view__container .planning-view__row-wrapper { #{property}: #{value} }"
-    console.log css
-    head = document.head || document.getElementsByTagName('head')[0]
-    style = document.createElement 'style'
-    style.type = 'text/css'
-    style.title = 'pa-planning-cssRules'
-    if style.styleSheet
-      style.styleSheet.cssText = css;
-    else
-      style.appendChild(document.createTextNode css)
-    head.appendChild(style)
-  else
-    console.log "YES"
-    if value == undefined
-      return property[style]
-    else
-      return property[style] = value
-
-
-pa_planning_wrapper_property = (property, value = undefined) ->
-  return CCSStylesheetRuleStyle2 'pa-planning-cssRules', '#planning-view__container .planning-view__row-wrapper', property, value
-
-#  move left or right the wrappers
-pa_planning_horizontal_move = (delta) ->
-  unless delta == 0
-    width = parseInt(pa_planning_wrapper_property 'width')
-    margin_left = parseFloat(pa_planning_wrapper_property 'margin-left') + delta;
-    min_margin_left = (width) * -1 + 90;
-    if margin_left >= 10
-      margin_left = 10
-      $('.planning-view__ctrl-move-left').hide()
-    else
-      $('.planning-view__ctrl-move-left').show()
-    if margin_left <= min_margin_left
-      margin_left = min_margin_left
-      $('.planning-view__ctrl-move-right').hide()
-    else
-      $('.planning-view__ctrl-move-right').show()
-    pa_planning_wrapper_property 'margin-left', margin_left + '%'
-
-# zoom control
-pa_planning_zoom = (zoom) ->
-  dropdown = $('.planning-view__header--control-zoom')
-  el = dropdown.find('a[data-zoom=' + zoom + ']')
-  dropdown.find('li').removeClass('active')
-  dropdown.find('a[data-toggle=dropdown] span').html(el.html())
-  el.parent().addClass('active')
-  old_width = parseFloat(pa_planning_wrapper_property 'width')
-  new_width = parseFloat(el.data 'zoom')
-  if old_width != new_width
-    old_margin_left = parseFloat(pa_planning_wrapper_property 'margin-left')
-    new_margin_left = ((old_margin_left + ((old_width - 100.0) / 2.0)) * new_width / old_width) - ((new_width - 100.0) / 2.0)
-    pa_planning_wrapper_property 'width', new_width + '%'
-    pa_planning_horizontal_move -(old_margin_left - new_margin_left)
-    if parseInt(el.data('nb-of-months')) > 30
-      $('.planning-view__header--month').addClass('vertical');
-    else
-      $('.planning-view__header--month').removeClass('vertical');
-#
-#
-#END UTILITY FUNCTIONS


:art:

:art:

--- lib/html-helpers.coffee
+++ lib/html-helpers.coffee
@@ -1,9 +1,11 @@
 closest = (element, selector) ->
+  return unless element?
   return element if element.matches(selector)
   closest(element.parentElement, selector)

-indexOf = (element) ->
-  for child, index in element.parentElement.children
+indexOf = (element, elements) ->
+  elements ?= element.parentElement.children
+  for child, index in elements
     return index if element is child
   return -1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -118,6 +118,6 @@
   ]

   4.circularPoints (p, i) ->
-    ok p.x.toFixed.equal(points[i]), "#{p} == #{points[i]}"
+    ok p.x.toFixed(2) .equal(points[i]), "#{p} == #{points[i]}"

 module undefined


Basic functionality.

Basic functionality.

It's only showing one error out of many though right now.
I'll fix it, probably by spliting the string that's returned by newlines.

--- lib/linter-flint.coffee
+++ lib/linter-flint.coffee
@@ -3,49 +3,80 @@
     executablePath:
       type: 'string'
       default: ''
+    skipReadme:
+      type: 'boolean'
+      default: false
+    skipContributing:
+      type: 'boolean'
+      default: false
+    skipLicense:
+      type: 'boolean'
+      default: false
+    skipBootstrap:
+      type: 'boolean'
+      default: false
+    skipTestScript:
+      type: 'boolean'
+      default: false
+    skipScripts:
+      type: 'boolean'
+      default: false
+    colorOutput:
+      type: 'boolean'
+      default: false

   activate: ->
     console.log 'activate linter-flint' if atom.inDevMode()
     unless atom.packages.getLoadedPackages 'linter-plus'
-      @showError '[linter-flint] `linter-plus` package not found,
-       please install it'
+      @showError '[linter-flint] `linter-plus` package not found, please install it'

   showError: (message = '') ->
     atom.notifications.addError message

   provideLinter: ->
     {
-      scopes: []
+      scopes: ['*']
       scope: 'project'
       lint: @lint
     }

   lint: (TextEditor, TextBuffer) ->
     CP = require 'child_process'
-    Path = require 'path'
-    XRegExp = require('xregexp').XRegExp
+    path = require 'path'
+    xregexp = require('xregexp').XRegExp

-    regex = XRegExp('\\[(?<type>\w+)\\] (?<message>.*)')
+    regex = xregexp('\\[(?<type>\\w+)\\]\\s(?<message>.*)')

     executablePath = atom.config.get 'linter-flint.executablePath'
-    if executablePath.trim
-      cmd = Path(executablePath).join('flint').normalize
-    else
-      cmd = 'flint'
+    cmd = 'flint'
+    cmd = path.join(executablePath, cmd).normalize() if executablePath.trim
+    cmd = "#{cmd} --skip-readme" if atom.config.get 'linter-flint.skipReadme'
+    cmd = "#{cmd} --skip-contributing" if atom.config.get 'linter-flint.skipContributing'
+    cmd = "#{cmd} --skip-license" if atom.config.get 'linter-flint.skipLicense'
+    cmd = "#{cmd} --skip-bootstrap" if atom.config.get 'linter-flint.skipBootstrap'
+    cmd = "#{cmd} --skip-test-script" if atom.config.get 'linter-flint.skipTestScript'
+    cmd = "#{cmd} --skip-scripts" if atom.config.get 'linter-flint.skipScripts'
+    cmd = "#{cmd} --no-color" unless atom.config.get 'linter-flint.colorOutput'
+    console.log "linter-flint command: #{cmd}" if atom.inDevMode()

     return new Promise (Resolve) ->
       projectPath = atom.project.getPaths()[0]
       Data = []
-      Process = CP.exec(cmd, {cwd: Path.dirname(projectPath)})
-      Process.stdout.on 'data', (data) -> Data.push(data.toString())
+      Process = CP.exec(cmd, {cwd: projectPath})
+      Process.stderr.on 'data', (data) -> Data.push(data.toString())
       Process.on 'close', ->
         Content = []
         for line in Data
-          Content.push XRegExp.exec(line, regex)
+          l_regex = xregexp.exec(line, regex)
+          Content.push l_regex
+          console.log "linter-flint regex: #{l_regex}" if atom.inDevMode()
           console.log "linter-flint: #{line}" if atom.inDevMode()
         ToReturn = []
         Content.forEach (regex) ->
+          console.log "linter-flint regex content:  #{regex}" if atom.inDevMode()
           if regex
+            console.log "linter-flint type: #{regex.type}" if atom.inDevMode()
+            console.log "linter-flint message: #{regex.message}" if atom.inDevMode()
             ToReturn.push(
               type: regex.type,
               message: regex.message


Using lintserver() JSON-format both sending and receiving

--- lib/linter-julia.coffee
+++ lib/linter-julia.coffee
@@ -3,74 +3,8 @@
 tmp = require('tmp')
 fs = require('fs')

-lintfromserver = (socket, str, fname) ->
-  # This plus one probably because Julia starts index from 1
-  siz = Buffer.byteLength(str, 'utf8') + 1
-  socket.write "#{fname}\n"
-  socket.write "#{siz}\n"
-  socket.write str + "\n"
-
-doSomeMagic = (data,textEditor) ->
-  filePath = textEditor.getPath()
-  inptext = textEditor.getText()
-  column = parseInt(atom.config.get('linter-julia.column'),10)
-  ignore = atom.config.get('linter-julia.ignore').split(/\s+/)
-  ignorewarning = atom.config.get('linter-julia.ignorewarning')
-  ignoreinfo = atom.config.get('linter-julia.ignoreinfo')
-  showErrorcode = atom.config.get('linter-julia.showErrorcode')
-  linteroutput = [ ]
-
-  lines = data.split("\n")
-  for line in lines
-    try
-      splittedline = line.replace(filePath+":","").split(":")
-      numbers = splittedline[0].split(" ")
-      if numbers.length < 2 || numbers[1] in ignore
-        continue
-      row_number = parseInt(numbers[0],10) - 1
-      severity = (numbers[1])[0]
-      if severity.match("I")
-        type = "Info"
-        if ignoreinfo
-          continue
-      else if severity.match("W")
-        type = "Warning"
-        if ignorewarning
-          continue
-      else
-        type = "Error"
-
-
-      if showErrorcode
-        text = numbers[1] + " " + numbers[2] + ":" + splittedline[2]
-      else
-        text = numbers[2] + ":" + splittedline[2]
-      range = [[row_number, 0], [row_number, column]]
-      fullmsg = {
-        type
-        text
-        range
-        filePath
-      }
-      linteroutput.push(fullmsg)
-
-    catch e
-      if e.message != "Cannot read property 'split' of undefined"
-        console.log e
-
-  linteroutput
-
-
 module.exports =
   config:
-    column:
-      title: 'Error message column end, the column start is 0'
-      type: 'string'
-      default: '80'
-      description: "Lint.jl doesn't return the error column, thus the whole line
-                    is used, meaning column end is 80. To get back to the
-                    previous behavior change the column end to 1"
-      order: 1
     julia:
       title: 'Julia executable location, by default this comes from Juno'
       type: 'string'
@@ -79,7 +13,7 @@
         to use. For example:\n In Windows:
         C:\\Users\\Julia\\AppData\\Local\\Julia-0.5.0\\bin\\julia.exe\n
         In Linux: /usr/bin/julia"
-      order: 2
+      order: 1
     ignore:
       title: 'List of ignored Lint codes'
       type: 'string'
@@ -87,22 +21,22 @@
       description: "Some times you want to ignore some of the lint messages.
                     Give here the ignored error codes in a format:
                     E321 W361 I171"
-      order: 3
+      order: 2
     ignorewarning:
       title: "Don't show warnings"
       type: 'boolean'
       default: false
-      order: 4
+      order: 3
     ignoreinfo:
       title: "Don't show infos"
       type: 'boolean'
       default: false
-      order: 5
+      order: 4
     showErrorcode:
       title: "Show the Error codes i.e. E321 in the message"
       type: 'boolean'
       default: true
-      order: 6
+      order: 5

   activate: ->
     require('atom-package-deps').install('linter-julia', true)
@@ -111,14 +45,14 @@
     else
       julia = atom.config.get('julia-client.juliaPath')

-    tempfil = tmp.tmpNameSync({ prefix:'lintserver',postfix: 'sock'})
+    tempfil = tmp.tmpNameSync({ prefix:'lintserver', postfix: 'sock'})
     if process.platform == 'win32'
       global.named_pipe = '\\\\.\\pipe\\' + tempfil.split("\\").pop()
       pipetospawn = named_pipe.replace(/\\/g,"\\\\")
-      jcode = "using Lint; lintserver(\"#{pipetospawn}\")"
+      jcode = "using Lint;lintserver(\"#{pipetospawn}\",\"standard-linter-v1\")"
     else
       global.named_pipe = tempfil
-      jcode = "using Lint; lintserver(\"#{named_pipe}\")"
+      jcode = "using Lint; lintserver(\"#{named_pipe}\",\"standard-linter-v1\")"

     jserver = spawn julia, ['-e', jcode]
     jserver.stdout.on 'data', (data) -> console.log data.toString().trim()
@@ -137,16 +71,21 @@
       lintOnFly: true
       lint: (textEditor)->
         connection = net.createConnection(named_pipe)
-
         return new Promise (resolve, reject) ->
           data = []
           connection.on 'connect', () ->
-            inptext = textEditor.getText()
-            filePath = textEditor.getPath()
-            lintfromserver(connection, inptext, filePath)
+            ignore = atom.config.get('linter-julia.ignore').split(/\s+/)
+            json_input = {
+              'file': textEditor.getPath(),
+              'code_str': textEditor.getText(),
+              'ignore_codes': ignore,
+              'ignore_warnings': atom.config.get('linter-julia.ignorewarning'),
+              'ignore_info': atom.config.get('linter-julia.ignoreinfo'),
+              'show_code': atom.config.get('linter-julia.showErrorcode')
+            }
+            connection.write JSON.stringify(json_input)
           connection.on('error',reject)
           connection.on 'data', (chunk) ->
             data.push(chunk)
           connection.on 'close', () ->
-            allOfData = data.join('')
-            resolve(doSomeMagic(allOfData,textEditor))
+            resolve(JSON.parse(data.join("")))


Delegate decoration method on the minimap view

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -11,7 +11,7 @@
   Debug('minimap').includeInto(this)
   Delegato.includeInto(this)

-  @delegatesMethods 'getLineHeight', 'getCharHeight', 'getCharWidth', 'getLinesCount', 'getMinimapHeight', 'getMinimapScreenHeight', 'getMinimapHeightInLines', 'getFirstVisibleScreenRow', 'getLastVisibleScreenRow', 'addLineClass', 'removeLineClass', 'removeAllLineClasses', 'pixelPositionForScreenPosition', toProperty: 'miniEditorView'
+  @delegatesMethods 'getLineHeight', 'getCharHeight', 'getCharWidth', 'getLinesCount', 'getMinimapHeight', 'getMinimapScreenHeight', 'getMinimapHeightInLines', 'getFirstVisibleScreenRow', 'getLastVisibleScreenRow', 'addLineClass', 'removeLineClass', 'removeAllLineClasses', 'pixelPositionForScreenPosition', 'decorateMarker', 'removeDecoration', 'decorationsForScreenRowRange', 'removeAllDecorationsForMarker', toProperty: 'miniEditorView'

   @delegatesProperty 'lineHeight', toMethod: 'getLineHeight'
   @delegatesProperty 'charWidth', toMethod: 'getCharWidth'


testing l10n service

testing l10n service

--- test/unit/service.spec.coffee
+++ test/unit/service.spec.coffee
@@ -1,24 +1,38 @@
-describe "service", ->
+describe "L10n service", ->
 	provider = null
 	service = null
 	beforeEach ->
-		angular.module('test-app', ['l10n']).config (l10nProvider) ->
+		angular.module('test-service', ['l10n']).config (l10nProvider) ->
 			provider = l10nProvider
-			provider.add 'en-US',
+			l10nProvider.add 'en-US', key: 'message-en'
+			l10nProvider.add 'uk-UA', key: 'message-ua'

-		module 'test-app'
+		module 'test-service'
+		inject (l10n) -> service = l10n
 	afterEach ->
 		# cleanup
 		provider.locale = null
 		provider.localeMessages = {}
 		provider.db = {}

-	it "by default locale is en-us (angular defined)", ->
-		inject (l10n) -> service = l10n
+	it 'should have en-us as default locale (angular defined)', ->
 		expect(service.getLocale()).toEqual('en-us')

+	it 'should return all locales with getAllLocales()', ->
+		expect(service.getAllLocales()).toEqual(
+			'en-US': key: 'message-en'
+			'uk-UA': key: 'message-ua'
+		)
+
+	it 'should change locales using setLocale', ->
+		service.setLocale 'en-US'
+		expect(service.getLocale()).toBe('en-US')
+		service.setLocale 'ua-UK'
+		expect(service.getLocale()).toBe('ua-UK')
+		service.setLocale 'en-US'
+
 describe 'provider to service', ->
-	it "default locale can be changed using l10nProvider.setLocale", ->
+	it 'default locale can be changed using l10nProvider.setLocale', ->
 		angular.module('provider2service', ['l10n']).config (l10nProvider) ->
 			l10nProvider.setLocale 'uk-UA'
 		module 'provider2service'


Guard against no preview tab

Guard against no preview tab

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -48,7 +48,7 @@

     @subscriptions.add @pane.onDidChangeActiveItem (item) =>
       if atom.config.get('tabs.usePreviewTabs') and item instanceof TextEditor
-        if @getTabs().length > 1 and @tab.item isnt item and @tab.isPreviewTab
+        if @getTabs().length > 1 and @tab?.item isnt item and @tab?.isPreviewTab
           @pane.destroyItem(@tab.item)
         @tab = @tabForItem(item)
       @updateActiveTab()


Removed debug code

Removed debug code

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -170,7 +170,6 @@
     event.originalEvent.dataTransfer.setData 'from-routing-id', @getRoutingId()

     item = @pane.getItems()[element.index()]
-    console.log(item.getUri?())
     if item.getUri?
       event.originalEvent.dataTransfer.setData 'text/urilist', item.getUri?() ? ''

@@ -245,7 +244,6 @@
       @moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item) if item?
     else
       droppedUri = dataTransfer.getData('text/urilist')
-      console.log(droppedUri)
       atom.workspace.open(droppedUri).then (item) =>
         # Move the item from the pane it was opened on to the target pane
         # where it was dropped onto


Offline data for token. Make it consitence so if user reload they still have same token

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -22,9 +22,10 @@

     initialize: () ->
       code =
-        token: this.uuid()
+        token: if localStorage['token']? then localStorage['token'] else this.uuid()
         count: 12

+      localStorage['token'] = code.token
       bc = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=#{encodeURI(JSON.stringify(code))
 }&choe=UTF-8"
       @connection = new Connection(


Reenabled update condition

Reenabled update condition

--- coffee/index.iced
+++ coffee/index.iced
@@ -33,4 +33,4 @@

 await updater.isUpdateAvailable defer err, av

-updater.installUpdate() #if av #&& !updater.isInDev()
+updater.installUpdate() if av && !updater.isInDev()


groups: fixed double property assignment merge

--- workers/social/lib/social/models/group/membershippolicy.coffee
+++ workers/social/lib/social/models/group/membershippolicy.coffee
@@ -20,7 +20,6 @@
       #   default           : no
       webhookEndpoint     : String
       explanation         : String
-      fields              : Object

   explain:->
     return @explanation  if @explanation?


include bower dependencies in app concat

--- config.coffee
+++ config.coffee
@@ -4,8 +4,9 @@
     javascripts:
       joinTo:
         'javascripts/app.js': /^app/
-        'javascripts/vendor.js': /^vendor/
+        'javascripts/vendor.js': /^(bower_components|vendor)/
     stylesheets:
-      joinTo: 'stylesheets/app.css'
+      joinTo:
+        'css/app.css': /^(app|vendor|bower_components)/
     templates:
       joinTo: 'templates/app.js'


working on adding logging to changefeed tests

--- src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
+++ src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
@@ -23,7 +23,7 @@
     before(setup)
     after(teardown)

-    it 'create account, project feed, a project, and see it appear', (done) ->
+    it 'creates account, project feed, a project, and see it appear', (done) ->
         changefeed_id = misc.uuid()
         accounts = undefined
         projects = []
@@ -82,6 +82,8 @@
     before(setup)
     after(teardown)

+    #log = console.log
+    log = ->
     it 'create 3 accounts and several projects, and see them appear in one projects feed properly', (done) ->
         accounts = undefined
         projects = []
@@ -97,34 +99,37 @@
                     cb         : changefeed_series([
                         (x, cb) ->
                             expect(x.projects.length).toEqual(0)
+
+                            log 'create fist project'
                             create_projects 1, accounts[0], (err, v) ->
                                 projects.push(v[0])
                                 cb(err)
                         (x, cb) ->
                             expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], users:{"#{accounts[0]}":{group:"owner"}} } })
+                            log 'create another project'
                             create_projects 1, accounts[0], (err, v) ->
                                 projects.push(v[0])
                                 cb(err)
                         (x, cb) ->
                             expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[1], users:{"#{accounts[0]}":{group:"owner"}} } })

-                            # create a project that will get ignored by the feed...
+                            log 'create a project that will get ignored by the feed...'
                             create_projects 1, accounts[1], (err, v) ->
                                 if err
                                     cb(err); return
                                 projects.push(v[0])
-                                # ... until we add the first user to it, in which case....
+                                log '... until we add the first user to it, in which case....'
                                 db.add_user_to_project(project_id:v[0], account_id:accounts[0], cb:cb)
                         (x, cb) ->
-                            # ... it appears!
+                            log '... it appears!'
                             expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[2], users:{"#{accounts[0]}":{group:"collaborator"}, "#{accounts[1]}":{group:"owner"}} } })

-                            # Now add another collaborator
+                            log 'Now add another collaborator'
                             db.add_user_to_project(project_id:projects[2], account_id:accounts[2], cb:cb)
                         (x, cb) ->
                             expect(x).toEqual({ action: 'update', new_val: { project_id: projects[2], users:{"#{accounts[0]}":{group:"collaborator"}, "#{accounts[1]}":{group:"owner"}, "#{accounts[2]}":{group:"collaborator"}} } })

-                            # Now take first user back off
+                            log 'Now take first user back off'
                             db.remove_user_from_project(project_id:projects[2], account_id:accounts[0], cb:cb)
                         (x, cb) ->
                             expect(x).toEqual({ action: 'update', new_val: { project_id: projects[2], users:{"#{accounts[1]}":{group:"owner"}, "#{accounts[2]}":{group:"collaborator"}} } })
@@ -132,6 +137,7 @@
                         (x, cb) ->
                             expect(x).toEqual({ action: 'delete', old_val: { project_id: projects[2] }})

+                            log 'cancel feed'
                             db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                         (x, cb) ->
                             expect(x).toEqual({action:'close'})


makes no sense to have "lazyBound" alias "bound".  If you have no params, just use bound

--- client/Framework/core/KDObject.coffee
+++ client/Framework/core/KDObject.coffee
@@ -24,9 +24,7 @@

   bound: Bongo.bound

-  lazyBound:(method, rest...)->
-    return @bound method  unless rest.length
-    return @[method].bind this, rest...
+  lazyBound: (method, rest...)-> @[method].bind this, rest...

   ready:(listener)->
     if @readyState > 0 then listener()


shortcuts: fix 'change' events not triggering

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -109,7 +109,7 @@
     @_store._storage.update $set: pack, =>

       # and make sure everybody is aware of the changes we've just made
-      _.each (buffer, objs, collectionName) =>
+      _.each buffer, (objs, collectionName) =>
         collection = @get collectionName
         _.each objs, (value) =>
           @emit 'change', collection, collection.find name: value.name


add tooltip for small events

add tooltip for small events

--- client/app/views/calendar_view.coffee
+++ client/app/views/calendar_view.coffee
@@ -140,6 +140,8 @@
             spinTarget.html " "
             spinTarget.spin "tiny"

+        $(element).attr 'title', event.title
+
         return element

     onEventDragStop: (event, jsEvent, ui, view) ->


Refactored: proper coffee syntax

Refactored: proper coffee syntax

--- client/app/views/new_task_form.coffee
+++ client/app/views/new_task_form.coffee
@@ -21,7 +21,7 @@

         # If the list is empty, we need to show the form
         @taskList.tasks.on 'remove', (collection) =>
-            @toggleTaskForm false, true if @taskList.tasks.length == 0
+            @toggleTaskForm false, true if @taskList.tasks.length is 0

         # whether the user has written something or not in the new task form
         @hasUserTyped = false
@@ -53,7 +53,7 @@
             @newTaskButtonHandler()

             keyCode = event.which | event.keyCode
-            @taskCreationHandler(event) if keyCode is 13 # enter key
+            @taskCreationHandler event if keyCode is 13 # enter key

         @newTaskFormInput.focus (event) =>
             @newTaskFormInput.val("") unless @hasUserTyped
@@ -69,7 +69,7 @@

     # "new task" Button behaviour management
     newTaskButtonHandler: () ->
-        if !@hasUserTyped || !@newTaskFormInput.val()
+        if !@hasUserTyped or !@newTaskFormInput.val()
             @newTaskFormButton.addClass 'disabled'
             @newTaskFormButton.html 'new'
             @newTaskFormButton.unbind 'click'
@@ -118,7 +118,7 @@

         $(document).keyup (event) =>
             keyCode = event.which | event.keyCode
-            if (keyCode is 84 && event.altKey) # alt + t
+            if keyCode is 84 && event.altKey # alt + t
                 @toggleTaskForm(true)

         @toggleButton.click (event) =>
@@ -126,14 +126,14 @@

     handleDefaultFormState: () ->

-        show_form = $.cookie('todos_prefs:show_form')
+        show_form = $.cookie 'todos_prefs:show_form'

         # The collection must fire "reset" before the value is relevant
         isListEmpty = @taskList.tasks.length is 0

         # The form is shown by default if the cookie is set or
         # if it doesn't exist or if the collection is empty
-        if(show_form is 'true' || !show_form? || isListEmpty)
+        if show_form is 'true' or !show_form? or isListEmpty
             @showTaskForm()

         else


Refactored: moved the new task form logic into a function

--- client/app/views/todolist_view.coffee
+++ client/app/views/todolist_view.coffee
@@ -39,7 +39,11 @@
         @archiveTasks = @archiveList.tasks
         @refreshBreadcrump()

-        # New task form management
+        @initializeTaskManagement()
+
+        @el
+
+    initializeTaskManagement: () ->
         @newTaskFormButton = $(".new-task button")
         @newTaskFormInput = $(".new-task .description")
         # whether the user has written something or not in the new task form
@@ -60,10 +64,6 @@
             if @newTaskFormInput.val() is ""
                 @clearNewTask()
                 @hasUserTyped = false
-
-        # ./end New task form management
-
-        @el

     clearNewTask: () =>
         @newTaskButtonHandler()


Fix source title helper - return title or source prop

--- client/controllers/sourceModal.coffee
+++ client/controllers/sourceModal.coffee
@@ -80,7 +80,8 @@
       'save-modal'

   title: ->
-    Template.instance().selectedArticle.get().subject
+    article = Template.instance().selectedArticle.get()
+    article.title or article.subject

   url: ->
     Template.instance().selectedArticle.get().url


Add rude on/off natural langauge commands

--- lib/capabilities/ControlBrowserCapability.coffee
+++ lib/capabilities/ControlBrowserCapability.coffee
@@ -9,7 +9,8 @@
     on:
       forms: [
         '(turn|switch) on( the)? <control>'
-        '(turn|switch)( the)? <control> on']
+        '(turn|switch)( the)? <control> on'
+        '<control> on']
       resolve: (cap, {control}) ->
         control = cap.resolveControlName(control)
         if control?.hasCommand('turnOn') then {control: control} else null
@@ -22,7 +23,8 @@
     off:
       forms: [
         '(turn|switch|shut) off( the)? <control>'
-        '(turn|switch|shut)( the)? <control> off']
+        '(turn|switch|shut)( the)? <control> off'
+        '<control> off']
       resolve: (cap, {control}) ->
         control = cap.resolveControlName(control)
         if control?.hasCommand('turnOff') then {control: control} else null


fixing test

fixing test

--- test/itunes-search-test.coffee
+++ test/itunes-search-test.coffee
@@ -13,4 +13,4 @@
     require('../src/itunes-search')(@robot)

   it 'registers a respond listener', ->
-    expect(@robot.respond).to.have.been.calledWith(/music( me)? (.*)/)
+    expect(@robot.respond).to.have.been.calledWith(/music( me)? (.+)/i)


Fix #2534: Address @jashkenas review comment.

--- browser.coffee
+++ browser.coffee
@@ -18,10 +18,10 @@

 # Load a remote script from the current domain via XHR.
 CoffeeScript.load = (url, callback) ->
-  if window.ActiveXObject
-    xhr = new window.ActiveXObject('Microsoft.XMLHTTP')
+  xhr = if window.ActiveXObject
+    new window.ActiveXObject('Microsoft.XMLHTTP')
   else
-    xhr = new XMLHttpRequest()
+    new XMLHttpRequest()
   xhr.open 'GET', url, true
   xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
   xhr.onreadystatechange = ->


fix(test/idToken): Convert exp to ms before passing to Date constructor

--- test/models/idTokenSpec.coffee
+++ test/models/idTokenSpec.coffee
@@ -209,7 +209,7 @@

       token = new IDToken payload
       token.payload.exp.should.be.a.number
-      new Date(token.payload.exp).getDay().should.not.equal new Date().getDay()
+      new Date(token.payload.exp * 1000).getDay().should.not.equal new Date().getDay()

     it 'should require "iat" Issued time', ->
       IDToken.registeredClaims.iat.required.should.be.true


Write unit tests for postprocessor. Simplify names.

--- test/postprocessor.test.coffee
+++ test/postprocessor.test.coffee
@@ -57,7 +57,7 @@

     describe 'enumerations', ->
         simpleEnumerationSchema = null
-        simpleEnumerationDefinition = null
+        enumerationDefinition = null

         beforeEach ->
             simpleEnumerationSchema = postprocessor.postprocess([
@@ -69,60 +69,60 @@
                     { name: 'latitude' }
                 ]
             ])
-            simpleEnumerationDefinition = simpleEnumerationSchema.definitions.Coordinates
+            enumerationDefinition = simpleEnumerationSchema.definitions.Coordinates

         it 'should describe enumerations as extended definition', ->
             expect(simpleEnumerationSchema.definitions).to.have.property('Coordinates')

         it 'should describe enumeration as arrays with strict item count', ->
-            expect(simpleEnumerationDefinition).to.have.property('type', 'array')
-            expect(simpleEnumerationDefinition).to.have.property('maxItems', 2)
-            expect(simpleEnumerationDefinition).to.have.property('minItems', 2)
+            expect(enumerationDefinition).to.have.property('type', 'array')
+            expect(enumerationDefinition).to.have.property('maxItems', 2)
+            expect(enumerationDefinition).to.have.property('minItems', 2)

         it 'should list array item names', ->
-            expect(simpleEnumerationDefinition).to.have.property('elements').deep.equal([
+            expect(enumerationDefinition).to.have.property('elements').deep.equal([
                 'longitude'
                 'latitude'
             ])

         it 'should refer to destructured object definition', ->
-            expect(simpleEnumerationDefinition).to.have.property('destructuredTo')
-            expect(simpleEnumerationDefinition.destructuredTo)
+            expect(enumerationDefinition).to.have.property('destructuredTo')
+            expect(enumerationDefinition.destructuredTo)
                 .to.have.property('$ref', '#/definitions/Coordinates/definitions/__Coordinates__')

         it 'should generate definition for object to which array should be converted', ->
-            expect(simpleEnumerationDefinition.definitions).to.have.property('__Coordinates__')
+            expect(enumerationDefinition.definitions).to.have.property('__Coordinates__')

         describe 'destructured object definition', ->
-            simpleDestructuredObjectDefinition = null
+            destructuredObjectDefinition = null

             beforeEach ->
-                simpleDestructuredObjectDefinition =
-                    simpleEnumerationDefinition.definitions.__Coordinates__
+                destructuredObjectDefinition =
+                    enumerationDefinition.definitions.__Coordinates__

             it 'should be a definition', ->
-                expect(simpleDestructuredObjectDefinition).to.have.property('title', '__Coordinates__')
-                expect(simpleDestructuredObjectDefinition).to.have.property('type', 'object')
+                expect(destructuredObjectDefinition).to.have.property('title', '__Coordinates__')
+                expect(destructuredObjectDefinition).to.have.property('type', 'object')

             it 'should describe destructured properties', ->
-                expect(simpleDestructuredObjectDefinition).to.have.property('properties')
-                expect(simpleDestructuredObjectDefinition.properties).to.have.property('longitude')
-                expect(simpleDestructuredObjectDefinition.properties).to.have.property('latitude')
+                expect(destructuredObjectDefinition).to.have.property('properties')
+                expect(destructuredObjectDefinition.properties).to.have.property('longitude')
+                expect(destructuredObjectDefinition.properties).to.have.property('latitude')

             it 'should restrict properties exactly to what was destructured from array', ->
-                expect(simpleDestructuredObjectDefinition).to.have.property('additionalProperties', false)
-                expect(simpleDestructuredObjectDefinition).to.have.property('required').deep.equal([
+                expect(destructuredObjectDefinition).to.have.property('additionalProperties', false)
+                expect(destructuredObjectDefinition).to.have.property('required').deep.equal([
                     'longitude'
                     'latitude'
                 ])

     describe 'indexes', ->
-        simpleIndexSchema = null
-
-        beforeEach ->
-            simpleIndexSchema = postprocessor.postprocess([
+        indexSchema = null
+
+        beforeEach ->
+            indexSchema = postprocessor.postprocess([
                 type: 'object'
                 rule: 'definition'
                 isMainDefinition: true
@@ -135,23 +135,23 @@
             ])

         it 'should save indexes to dedicated hash', ->
-            expect(simpleIndexSchema.definitions.Tweet).to.have.property('indexes').deep.equals [
+            expect(indexSchema.definitions.Tweet).to.have.property('indexes').deep.equals [
                 key: [ 'id' ]
                 type: 'unique'
             ]

         it 'should add index properties and make them required if necessary', ->
-            expect(simpleIndexSchema.definitions.Tweet)
+            expect(indexSchema.definitions.Tweet)
                 .to.have.property('properties').deep.equals(id: {})
-            expect(simpleIndexSchema.definitions.Tweet)
+            expect(indexSchema.definitions.Tweet)
                 .to.have.property('required').deep.equals([ 'id' ])

     describe 'relations', ->
-        simpleRelationDefinition = null
-
-        beforeEach ->
-            simpleRelationDefinition = postprocessor.postprocess([
+        relationDefinition = null
+
+        beforeEach ->
+            relationDefinition = postprocessor.postprocess([
                 rule: 'relation'
                 out:
                     name: 'CREATED_BY'
@@ -168,24 +168,24 @@
             ])

         it 'should save relations to dedicated hash', ->
-            expect(simpleRelationDefinition.relations).to.have.property('author')
+            expect(relationDefinition.relations).to.have.property('author')

         it 'should save relation direction', ->
-            expect(simpleRelationDefinition.relations.author.out).to.have.property('name', 'CREATED_BY')
+            expect(relationDefinition.relations.author.out).to.have.property('name', 'CREATED_BY')

         it 'should save relation attributes', ->
-            expect(simpleRelationDefinition.relations.author.out).to.have.property('attributes')
-            expect(simpleRelationDefinition.relations.author.out.attributes).to.contain('when')
+            expect(relationDefinition.relations.author.out).to.have.property('attributes')
+            expect(relationDefinition.relations.author.out.attributes).to.contain('when')

         it 'should treat relations as implicit properties declarations', ->
-            expect(simpleRelationDefinition.properties).to.have.property('author')
-            expect(simpleRelationDefinition.properties.author).to.have.property('type', 'User')
+            expect(relationDefinition.properties).to.have.property('author')
+            expect(relationDefinition.properties.author).to.have.property('type', 'User')

         it 'should specify what property is managed by relation', ->
-            expect(simpleRelationDefinition.relations.author).to.have.property('property', 'author')
+            expect(relationDefinition.relations.author).to.have.property('property', 'author')

         it 'should treat relation attributes as implicit properties declarations', ->
-            expect(simpleRelationDefinition.properties).to.have.property('when').deep.equals
+            expect(relationDefinition.properties).to.have.property('when').deep.equals
                 type: 'object'
                 title: 'DateTime'


Align attribution to frame's lower right corner (#6868)

--- bokehjs/src/coffee/models/tiles/tile_renderer.coffee
+++ bokehjs/src/coffee/models/tiles/tile_renderer.coffee
@@ -35,17 +35,17 @@

     if isString(attribution) and attribution.length > 0
       if not @attributionEl?
-        border_width = @map_plot.outline_line_width
-        bottom_offset = @map_plot.min_border_bottom + border_width
-        right_offset = @map_frame._right.value - @map_frame._width.value
-        max_width = @map_frame._width.value - border_width
+        right = @plot_model.canvas._right.value - @plot_model.frame._right.value
+        bottom = @plot_model.frame._bottom.value
+        max_width = @map_frame._width.value
         @attributionEl = div({
           class: 'bk-tile-attribution'
           style: {
-            position: 'absolute'
-            bottom: "#{bottom_offset}px"
-            right: "#{right_offset}px"
+            position: "absolute"
+            bottom: "#{bottom}px"
+            right: "#{right}px"
             'max-width': "#{max_width}px"
+            padding: "2px"
             'background-color': 'rgba(255,255,255,0.8)'
             'font-size': '9pt'
             'font-family': 'sans-serif'


Revert "Simplify package metadata check"

--- services/web/public/coffee/ide/editor/directives/aceEditor/metadata/MetadataManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/metadata/MetadataManager.coffee
@@ -18,15 +18,41 @@
 					return
 				if change.action not in ['remove', 'insert']
 					return
+				cursorPosition = @editor.getCursorPosition()
+				end = change.end
+				range = new Range(end.row, 0, end.row, end.column)
+				lineUpToCursor = @editor.getSession().getTextRange range
+				if lineUpToCursor.trim() == '%' or lineUpToCursor.slice(0, 1) == '\\'
+					range = new Range(end.row, 0, end.row, end.column + 80)
+					lineUpToCursor = @editor.getSession().getTextRange range
+				commandFragment = getLastCommandFragment lineUpToCursor

-				end = change.end
-				lineText = @editor.getSession().getLine end.row
+				linesContainPackage = _.any(
+					change.lines,
+					(line) -> line.match(/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
+				)
+				linesContainReqPackage = _.any(
+					change.lines,
+					(line) -> line.match(/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
+				)
+				linesContainLabel = _.any(
+					change.lines,
+					(line) -> line.match(/\\label{(.{0,80}?)}/)
+				)
+				linesContainMeta =
+					linesContainPackage or
+					linesContainLabel or
+					linesContainReqPackage

-				# Defensive check against extremely long lines
-				return if lineText.length > 10000
+				lastCommandFragmentIsLabel = commandFragment?.slice(0, 7) == '\\label{'
+				lastCommandFragmentIsPackage = commandFragment?.slice(0, 11) == '\\usepackage'
+				lastCommandFragmentIsReqPack = commandFragment?.slice(0, 15) == '\\RequirePackage'
+				lastCommandFragmentIsMeta =
+					lastCommandFragmentIsPackage or
+					lastCommandFragmentIsLabel or
+					lastCommandFragmentIsReqPack

-				# Check if edited line contains metadata commands
-				if /\\(usepackage|RequirePackage|label)(\[.*])?({.*})?/.test(lineText)
+				if linesContainMeta or lastCommandFragmentIsMeta
 					@scheduleLoadCurrentDocMetaFromServer()

 			@editor.on "changeSession", (e) =>


commenting out hacky settings fix from dkobo

--- jsapp/xlform/src/view.surveyApp.coffee
+++ jsapp/xlform/src/view.surveyApp.coffee
@@ -331,6 +331,7 @@
       $inps = {}
       _settings = @survey.settings

+      ###
       if @$settings.form_id.length > 0
         $inps.form_id = @$settings.form_id.find('input').eq(0)
         $inps.form_id.change (evt)->
@@ -349,6 +350,7 @@
         $inps.style = @$settings.style.find('input,select').eq(0)
         $inps.style.change (evt)->
           _settings.set('style', $inps.style.val())
+      ###

     _render_addSubViews: ->
       meta_view = new $viewUtils.ViewComposer()


Update timestampable

Update timestampable

Set createdBy/updatedBy to 0 if insert/update initiated on server and
not set

--- behaviours/timestampable.coffee
+++ behaviours/timestampable.coffee
@@ -32,7 +32,9 @@
     if createdBy
       definition[createdBy] =
         autoValue: ->
-          unless @isFromTrustedCode
+          if @isFromTrustedCode and not @isSet
+            '0'
+          else
             if @isInsert
               @userId
             else if @isUpsert
@@ -40,7 +42,8 @@
             else
               @unset()

-        regEx: SimpleSchema.RegEx.Id
+        optional: true
+        regEx: new RegExp "(" + SimpleSchema.RegEx.Id.source + ")|^0$"
         type: String

     if updatedAt
@@ -58,7 +61,9 @@
     if updatedBy
       definition[updatedBy] =
         autoValue: ->
-          unless @isFromTrustedCode
+          if @isFromTrustedCode and @isUpdate and not @isSet
+            '0'
+          else
             if @isUpdate
               @userId
             else
@@ -66,28 +71,30 @@

         denyInsert: true
         optional: true
-        regEx: SimpleSchema.RegEx.Id
+        regEx: new RegExp "(" + SimpleSchema.RegEx.Id.source + ")|^0$"
         type: String

     @attachSchema new SimpleSchema definition

   else
-    @before.insert (userId, doc) ->
-      if createdAt
-        doc[createdAt] = new Date
-      if createdBy
-        doc[createdBy] = userId
+    if Meteor.isServer
+      @before.insert (userId, doc) ->
+        if createdAt
+          doc[createdAt] = new Date
+        if createdBy and not doc.createdBy?
+          userId ?= '0'
+          doc[createdBy] = userId

-    @before.update (userId, doc, fieldNames, modifier, options) ->
-      $set = modifier.$set
+      @before.update (userId, doc, fieldNames, modifier, options) ->
+        $set = modifier.$set

-      unless $set
-        $set = {}
+        unless $set
+          $set = {}

-      if updatedAt
-        $set[updatedAt] = new Date
-
-      if updatedBy
-        $set[updatedBy] = userId
+        if updatedAt
+          $set[updatedAt] = new Date
+        if updatedBy and not doc.updatedBy?
+          userId ?= '0'
+          $set[updatedBy] = userId

 CollectionBehaviour.define 'timestampable', behaviour


removing semicolons [e5236d7ed2e244e2df5b98665de1146033721d41]

--- review_front/app/components/copy-to-clipboard.coffee
+++ review_front/app/components/copy-to-clipboard.coffee
@@ -1,5 +1,5 @@
-`import Ember from 'ember';`
-`import ZeroClipboard from 'ember-cli-zero-clipboard/components/zero-clipboard'; `
+`import Ember from 'ember'`
+`import ZeroClipboard from 'ember-cli-zero-clipboard/components/zero-clipboard'`

 CopyToClipboard = ZeroClipboard.extend
   tagName: 'button'
@@ -16,4 +16,4 @@
         @justCopiedToggle()
       ), 1500

-`export default CopyToClipboard;`
+`export default CopyToClipboard`


sidebar: remove default workspace from machine link

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -26,9 +26,9 @@
     machineOwner     = machine.getOwner()
     isMyMachine      = machine.isMine()
     machineRoutes    =
-      own            : "/IDE/#{@alias}/my-workspace"
+      own            : "/IDE/#{@alias}"
       collaboration  : "/IDE/#{workspaces.first?.channelId}"
-      permanentShare : "/IDE/#{machine.uid}/my-workspace"
+      permanentShare : "/IDE/#{machine.uid}"

     machineType = 'own'


Next imports under module.exports in routes

--- routes.coffee
+++ routes.coffee
@@ -1,40 +1,41 @@
-express = require 'express'
+module.exports = (app) ->
+  # --- imports ---
+  express    = require 'express'
+  router     = express.Router()

-auth       = require './controllers/auth'
-queues     = require './controllers/queues'
-spots      = require './controllers/spots'
-exceptions = require './controllers/exceptions'
-main       = require './controllers/app'
+  auth       = require './controllers/auth'
+  queues     = require './controllers/queues'
+  spots      = require './controllers/spots'
+  exceptions = require './controllers/exceptions'
+  main       = require './controllers/app'

-router = express.Router()
+  # --- routes ---
+  router.get    '/auth/login'           , auth.storeNext, auth.login
+  router.get    '/auth/google/callback' , auth.callback, auth.after
+  router.get    '/auth/logout'          , auth.ensureAuthenticated, auth.logout

-router.get    '/auth/login'           , auth.storeNext, auth.login
-router.get    '/auth/google/callback' , auth.callback, auth.after
-router.get    '/auth/logout'          , auth.ensureAuthenticated, auth.logout
+  router.get    '/api/queues'           , queues.index
+  router.get    '/api/queues/:key'      , queues.show
+  router.post   '/api/queues'           , queues.create
+  router.put    '/api/queues/:key'      , queues.modify
+  router.delete '/api/queues/:key'      , queues.destroy
+  router.post   '/api/queues/:key/join' , queues.join

-router.get    '/api/queues'           , queues.index
-router.get    '/api/queues/:key'      , queues.show
-router.post   '/api/queues'           , queues.create
-router.put    '/api/queues/:key'      , queues.modify
-router.delete '/api/queues/:key'      , queues.destroy
-router.post   '/api/queues/:key/join' , queues.join
+  # Why is this called /api/slots not /api/spots? Well, funny you ask!
+  # It turns out my adblocker (uBlock Origin) blocks /api/spots! Why?
+  # WHO FLIPPIN KNOWS >:O
+  router.get    '/api/slots'            , spots.index
+  router.get    '/api/slots/:key'       , spots.show
+  router.post   '/api/slots'            , spots.create
+  router.delete '/api/slots/:key'       , spots.destroy

-# Why is this called /api/slots not /api/spots? Well, funny you ask!
-# It turns out my adblocker (uBlock Origin) blocks /api/spots! Why?
-# WHO FLIPPIN KNOWS >:O
-router.get    '/api/slots'            , spots.index
-router.get    '/api/slots/:key'       , spots.show
-router.post   '/api/slots'            , spots.create
-router.delete '/api/slots/:key'       , spots.destroy
+  router.get    '/admin/exceptions'     , exceptions.index
+  router.get    '/admin/exceptions/:id' , exceptions.show

-router.get    '/admin/exceptions'     , exceptions.index
-router.get    '/admin/exceptions/:id' , exceptions.show
+  router.get    '/'                     , main.app
+  router.get    '/:key'                 , auth.logInFirst, main.app

-router.get    '/'                     , main.app
-router.get    '/:key'                 , auth.logInFirst, main.app
-
-module.exports = (app) ->
-  # Register routers
+  # --- register routers ---
   # HTTPS redirects temporarily disabled. See controllers/app.coffee
   #app.use main.forceHTTPS
   app.use '/api'   , auth.ensureAuthenticated


Better int parsing

Better int parsing

--- resources/assets/coffee/_classes/beatmapset-filter.coffee
+++ resources/assets/coffee/_classes/beatmapset-filter.coffee
@@ -16,7 +16,11 @@
 #    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###

-parseInt10 = (string) -> parseInt string, 10
+parseInt10 = (string) ->
+  int = parseInt string, 10
+
+  int if _.isFinite(int)
+

 class @BeatmapsetFilter
   @castFromString:


Revert "Remove state instead of setting it to false-ish"

This reverts commit 75dc38d42124f28da6b87e7420a848f3d60843c6.

--- resources/assets/coffee/_classes/forum-topic-reply.coffee
+++ resources/assets/coffee/_classes/forum-topic-reply.coffee
@@ -96,7 +96,7 @@

   deactivate: =>
     @stickyFooter.markerDisable @marker()
-    @deleteState 'active'
+    @setState 'active', '0'
     $.publish 'stickyFooter:check'
     @disableFlash()
     button.classList.remove 'js-activated' for button in @toggleButtons


components: DbusIfaceProperties: remove error ports

--- components/DbusIfaceProperties.coffee
+++ components/DbusIfaceProperties.coffee
@@ -73,10 +73,6 @@
         return unless event is 'data'
         c.path = payload
         c.updateProxy()
-
-    c.outPorts.add 'error',
-      datatype: 'object'
-      required: no

     # helper function to add ports
     addOutPort = (component, prop) ->


Add shortcuts for project find

Add shortcuts for project find

--- keymaps/find-and-replace.cson
+++ keymaps/find-and-replace.cson
@@ -32,12 +32,16 @@
   'alt-ctrl-c': 'find-and-replace:toggle-case-option'
   'alt-ctrl-s': 'find-and-replace:toggle-selection-option'

+'.platform-darwin .project-find':
+  'alt-meta-/': 'project-find:toggle-regex-option'
+  'alt-meta-c': 'project-find:toggle-case-option'
+  'meta-enter': 'project-find:replace-all'
+
+'.platform-win32 .project-find':
+  'alt-ctrl-/': 'project-find:toggle-regex-option'
+  'alt-ctrl-c': 'project-find:toggle-case-option'
+  'ctrl-enter': 'project-find:replace-all'
+
 '.find-and-replace, .project-find, .project-find .results-view':
   'tab': 'find-and-replace:focus-next'
   'shift-tab': 'find-and-replace:focus-previous'
-
-'.platform-darwin .project-find':
-  'cmd-enter': 'project-find:replace-all'
-
-'.platform-win32 .project-find':
-  'ctrl-enter': 'project-find:replace-all'


improve idempotency of notification

improve idempotency of notification

--- app/assets/javascripts/application/services/notification.coffee
+++ app/assets/javascripts/application/services/notification.coffee
@@ -11,11 +11,14 @@
               else
                 AHP.subscribe(channel_name)

-    channel.bind 'pusher:subscription_succeeded', =>
-      channel.bind event_name, (data) =>
-        @getDelta(data) unless data.who == gon.who
+    channel.bind event_name, @pusherCallback
+
+    $(document).on 'page:fetch', =>
+      channel.unbind event_name, @pusherCallback

+  pusherCallback: (data) =>
+    @getDelta(data) unless data.who == gon.who

   getDelta: (data) ->
     $.ajax


Added check for localhost

Added check for localhost

--- app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
@@ -39,10 +39,11 @@
     __checkCompatibility: ->
       @getUserMedia = navigator.getUserMedia or navigator.webkitGetUserMedia or navigator.mozGetUserMedia
       isHttp = window.location.protocol == 'http:'
+      isLocalhost = window.location.hostname == 'localhost'
       if isHttp
         uploadcare.utils.warn('Camera not allowed for HTTP connections. To get access to camera please use HTTPS connection');
       @URL = window.URL or window.webkitURL
-      return !! @getUserMedia and Uint8Array and !isHttp
+      return !! @getUserMedia and Uint8Array and !(isHttp and !isLocalhost)

     __requestCamera: =>
       @__loaded = true


MainViewController: Scroll improvements

MainViewController: Scroll improvements

--- client/Main/maincontroller/mainviewcontroller.coffee
+++ client/Main/maincontroller/mainviewcontroller.coffee
@@ -9,6 +9,7 @@
     mainView         = @getView()
     mainController   = KD.singleton 'mainController'
     appManager       = KD.singleton 'appManager'
+    windowController = KD.singleton 'windowController'
     display          = KD.singleton 'display'
     @registerSingleton 'mainViewController', this, yes
     @registerSingleton 'mainView', mainView, yes
@@ -38,8 +39,8 @@
     then KDView.setElementClass body, 'add', 'super'
     else KDView.setElementClass body, 'remove', 'super'

-    mainViewController = this
-    window.onscroll = do ->
+
+    windowController.on 'ScrollHappened', do ->
       threshold     = 50
       lastScroll    = 0
       currentHeight = 0


Fix pin modal title

Fix pin modal title

--- client/landing/app/CommonViews/VerifyPINModal.coffee
+++ client/landing/app/CommonViews/VerifyPINModal.coffee
@@ -4,7 +4,7 @@
   constructor:(buttonTitle = "Submit", callback) ->

     options =
-      title                       : "Please provide the PIN that you've just received"
+      title                       : "Please provide the PIN that we've emailed you"
       overlay                     : yes
       width                       : 400
       height                      : "auto"


test modified copy-to-clipboard [#96883346]

--- review_front/app/components/copy-to-clipboard.coffee
+++ review_front/app/components/copy-to-clipboard.coffee
@@ -1,19 +1,35 @@
 `import Ember from 'ember'`
-`import ZeroClipboard from 'ember-cli-zero-clipboard/components/zero-clipboard'`

-CopyToClipboard = ZeroClipboard.extend
+CopyToClipboard = Ember.Component.extend
+  attributeBindings: [
+    'title'
+    'data-clipboard-text'
+    'data-clipboard-target'
+  ]
+  title: 'Copy to clipboard'
   tagName: 'button'
   classNames: ['btn', 'btn-default', 'btn-xs', 'copy-to-clipboard']
   justCopied: false

   justCopiedToggle: ->
     @set 'justCopied', !@justCopied
-
-  actions:
-    afterCopy: ->
+  didInsertElement: ->
+    client = new ZeroClipboard(@get('element'))
+    #bind aftercopy to an ember event
+    client.on 'aftercopy', ((event) ->
       @justCopiedToggle()
       Ember.run.later @, (->
         @justCopiedToggle()
       ), 1500
+      return
+    ).bind(this)
+    return
+  'data-clipboard-text': (->
+    @get 'text'
+  ).property('text')
+  'data-clipboard-target': (->
+    @get 'cbTarget'
+  ).property('cbTarget')
+

 `export default CopyToClipboard`


Add comments

Add comments

--- app/assets/javascripts/rglossa/react/components/centre/results/cwb_results_table.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/results/cwb_results_table.jsx.coffee
@@ -45,16 +45,19 @@

       fields = fields.map (field) ->
         tokens = field.split(/\s+/).map (token) ->
-          parts = token.split('/')
+          parts = token.split('/')  # the slash separates CWB attributes

           maxIndex = labels.length
           ot = []
           if maxIndex
+            # If there are any CWB attributes to show for each token, create data-ot HTML
+            # attributes that will be used by the Opentip jQuery plugin to display them in a tooltip
             for i in [1..maxIndex]
               if parts[i] != '__UNDEF__'
                 ot.push "#{labels[i-1]}: #{parts[i]}"
             "#{parts[0]}"
           else
+            # No CWB attributes, so no data-ot attribute needed
             "#{parts[0]}"

         tokens.join(' ')


Change input size

Change input size

[#97184290]

--- review_front/app/components/auto-select-input.coffee
+++ review_front/app/components/auto-select-input.coffee
@@ -1,8 +1,9 @@
 `import Ember from 'ember'`

 AutoSelectInput = Ember.TextField.extend
-  attributeBindings: ['readonly']
+  attributeBindings: ['readonly', 'size']
   readonly: true
+  size: 40

   mouseUp: ->
     @$().select()


Add test for CourseUtils.i18n

Add test for CourseUtils.i18n

--- test/utils/course_utils.coffee
+++ test/utils/course_utils.coffee
@@ -1,4 +1,5 @@
 require '../testHelper'
+require '../../public/assets/javascripts/i18n/en'
 CourseUtils = require '../../app/assets/javascripts/utils/course_utils'

 describe 'CourseUtils.generateTempId', ->
@@ -28,3 +29,14 @@
     expect(course.term).to.eq 'Fall 2015'
     expect(course.school).to.eq 'University of Wikipedia'
     expect(course.title).to.eq 'Introduction to Editing'
+
+describe 'CourseUtils.i18n', ->
+  it 'outputs an interface message based on a message key and prefix', ->
+    message = CourseUtils.i18n('students', 'courses_generic')
+    expect(message).to.eq 'Editors'
+  it 'defaults to the "courses" prefix if prefix is null', ->
+    message = CourseUtils.i18n('students', null)
+    expect(message).to.eq 'Students'
+  it 'takes an optional fallback prefix for if prefix is null', ->
+    message = CourseUtils.i18n('class', null, 'revisions')
+    expect(message).to.eq 'Class'


refresh the domain list when a custom domain is added

--- client/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
+++ client/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
@@ -74,6 +74,8 @@
         )

       @modalTabs.showPaneByIndex "2"
+
+      @emit "DomainSaved"

   loadSearchDomainPane:->


Clear property printing

Clear property printing

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -136,7 +136,6 @@

 	@define "textStyle",
 		get: ->
-			print TextLayer._textStyleProperties
 			_.pick @, TextLayer._textStyleProperties
 		set: (values) ->
 			for key, prop in _.pick values, TextLayer._textStyleProperties


Update packages.cson

Update packages.cson

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -7,6 +7,7 @@
   "atom-material-ui"
   "atom-pair"
   "atom-ternjs"
+  "atom-typescript"
   "autocomplete-go"
   "autocomplete-modules"
   "autocomplete-paths"
@@ -35,6 +36,7 @@
   "hyperclick"
   "js-hyperclick"
   "language-asciidoc"
+  "language-babel"
   "language-docker"
   "language-powershell"
   "linter"


Social:create user on koding

Social:create user on koding

--- servers/lib/server/handlers/getteammembers.test.coffee
+++ servers/lib/server/handlers/getteammembers.test.coffee
@@ -205,6 +205,19 @@
       ->
         # expecting user to be registered
         registerRequestParams = generateRegisterRequestParams
+          body              :
+            email           : inviteeEmail
+            username        : inviteeUsername
+            password        : inviteeUsername
+            passwordConfirm : inviteeUsername
+
+        # registering a new user
+        request.post registerRequestParams, (err, res, body) ->
+          expect(err)             .to.not.exist
+          expect(res.statusCode)  .to.be.equal 200
+          queue.next()
+        # expecting user to be registered
+        registerRequestParams = generateRegisterRequestParams
           body       :
             email    : groupOwnerEmail
             username : groupOwnerUsername


stop hiding notification on recursive run

--- client/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -98,7 +98,6 @@
       @mount.emit "fs.fetchContents.started"

       @utils.killWait kiteFailureTimer if kiteFailureTimer
-      @treeController.hideNotification()

       kiteFailureTimer = @utils.wait 5000, =>
         msg = "Couldn't fetch files! Click to retry"


update tests for image_upload

update tests for image_upload

--- client/apps/edit/components/admin/test/image_upload.test.coffee
+++ client/apps/edit/components/admin/test/image_upload.test.coffee
@@ -34,16 +34,32 @@

     it 'Renders the file input', ->
       $(ReactDOM.findDOMNode(@component)).find('input[type=file]').length.should.eql 1
+      $(ReactDOM.findDOMNode(@component)).html().should.containEql 'accept="image/jpg,image/jpeg,image/gif,image/png"'

-    it 'Renders an image preview if image is present', ->
+    it 'Renders an image preview if file is present', ->
       @component.setState src: 'http://artsy.net/image.jpg'
       $(ReactDOM.findDOMNode(@component)).html().should.containEql 'background-image: url(http://artsy.net/image.jpg)'

+    it 'Hides the preview if props.hidePreview is true', ->
+      @props.hidePreview = true
+      component = ReactDOM.render React.createElement(@ImageUpload, @props), (@$el = $ "

")[0]
+      component.setState src: 'http://artsy.net/image.jpg'
+      $(ReactDOM.findDOMNode(component)).html().should.not.containEql 'background-image: url(http://artsy.net/image.jpg)'
+
+    it 'Allows video uploads if props.hasVideo is true', ->
+      @props.hasVideo = true
+      component = ReactDOM.render React.createElement(@ImageUpload, @props), (@$el = $ "

")[0]
+      $(ReactDOM.findDOMNode(component)).html().should.containEql 'accept="image/jpg,image/jpeg,image/gif,image/png,video/mp4"'
+
+    it 'Renders a video preview if file is present', ->
+      @component.setState src: 'http://artsy.net/video.mp4'
+      $(ReactDOM.findDOMNode(@component)).html().should.containEql '<video src="http://artsy.net/video.mp4">'
+
     it 'Disables the file input if prop includes disabled', ->
-      @props['disabled'] = true
-      @component = ReactDOM.render React.createElement(@ImageUpload, @props), (@$el = $ "

")[0]
-      $(ReactDOM.findDOMNode(@component)).hasClass('disabled').should.eql true
-      $(ReactDOM.findDOMNode(@component)).find('input[type=file]').prop('disabled').should.eql true
+      @props.disabled = true
+      component = ReactDOM.render React.createElement(@ImageUpload, @props), (@$el = $ "

")[0]
+      $(ReactDOM.findDOMNode(component)).hasClass('disabled').should.eql true
+      $(ReactDOM.findDOMNode(component)).find('input[type=file]').prop('disabled').should.eql true

     it 'Renders a progress bar if progress', ->
       @component.setState progress: .5


profile: avatar hover event wip

profile: avatar hover event wip

--- client/landing/app/MainApp/lazy/staticavatarareaiconmenu.coffee
+++ client/landing/app/MainApp/lazy/staticavatarareaiconmenu.coffee
@@ -4,8 +4,12 @@

     super

+    @bindEvent 'mouseenter'
+
+    @on 'mouseenter', =>
+      @getDelegate().lockSidebar = yes
+
     @setClass "actions"
-    # @setClass "invisible" unless KD.isLoggedIn()

     sidebar  = @getDelegate()


global data for AnnotationIteration objects

--- app/assets/javascripts/annotation_iteration.coffee
+++ app/assets/javascripts/annotation_iteration.coffee
@@ -14,12 +14,12 @@

   iterate: (template, data) ->
     $('.interfaces-staging > div:not(.template)').remove()
-    $output = $(
-      Mustache.render(
-        template.outerHTML,
-        data
-      )
-    ).removeClass('template')
+    @currentData = data
+    mustacheParsedTemplate = Mustache.render(
+      template.outerHTML,
+      data
+    )
+    $output = $(mustacheParsedTemplate).removeClass('template')
     $('.interfaces-staging').append($output)

   saveChanges: (data) ->


AJAX mixin fix

AJAX mixin fix

--- app/assets/javascripts/mixins/ajax_mixin.js.coffee
+++ app/assets/javascripts/mixins/ajax_mixin.js.coffee
@@ -12,7 +12,7 @@
           data: data
           success: (response) =>
             console.info 'Payload', response
-            @store.pushPayload('menu_item',response)
+            @store.pushPayload(options.serializer || 'menu_item',response)
             resolve(response)

           error: (response) =>


Fix bug with getting container

Fix bug with getting container

--- _source/client/templates/components/controls/calendar.coffee
+++ _source/client/templates/components/controls/calendar.coffee
@@ -162,7 +162,7 @@

     # @TODO: better way to do this?
     setTimeout(->
-      container = self.data().container
+      container = $(self.data().container)[0]
       if container?.scrollHeight > container.offsetHeight
         difference = container.scrollHeight - container.offsetHeight
         $('section.calendar').css top: "-=#{difference}px"


removed debugging

removed debugging

--- core/app/backbone/factlink/add_model_to_collection_mixin.coffee
+++ core/app/backbone/factlink/add_model_to_collection_mixin.coffee
@@ -2,7 +2,6 @@

 Backbone.Factlink.AddModelToCollectionMixin =
   addModel: ->
-    console.info('adding', @model.get('title'), 'to collection', @options.addToCollection)
     if @wrapNewModel
       model = @wrapNewModel(@model)
     else


fixed classname

fixed classname

--- core/app/backbone/views/ndp_interacting_users_names_view.coffee
+++ core/app/backbone/views/ndp_interacting_users_names_view.coffee
@@ -1,4 +1,4 @@
-class window.NDPInteractorNamesView extends Backbone.Marionette.CompositeView
+class window.NDPInteractingUsersNamesView extends Backbone.Marionette.CompositeView
   className: 'ndp-interacting-users-names'
   template: 'fact_relations/ndp_interactors_names'
   itemView: InteractorNameView


cache remove item logic fix

cache remove item logic fix

--- workers/social/lib/social/models/activity/cache.coffee
+++ workers/social/lib/social/models/activity/cache.coffee
@@ -105,6 +105,7 @@
         if err
           console.warn err
           @emit "CachingFinished"
+          log "caching finished.\n"
         else

           # printOverview overview
@@ -123,7 +124,7 @@
             # terminate only if there are no new items to be cached
             if overview.length is 0
               cacheQueue.push =>
-                log "caching finished"
+                log "caching finished.\n"
                 @emit "CachingFinished"
               daisy cacheQueue
               return
@@ -143,7 +144,7 @@

           @createCache overview2d, =>
             @emit "CachingFinished"
-            log "caching finished"
+            log "caching finished.\n"

   @createCacheFromEarliestTo = (eternity)->
@@ -431,15 +432,33 @@
               overviewIndexToDelete = i
               break

-          unsetModifier = {}
+          updateTo   = if overviewIndexToDelete is cache.overview.length-1 then yes else no
+          updateFrom = if overviewIndexToDelete is 0 then yes else no
+
+          unsetModifier      = {}
+          setModifier        = {}
+          setModifier.isFull = no
           unsetModifier["activities.#{idToDelete}"] = 1
           unsetModifier["overview.#{overviewIndexToDelete}"] = 1
+
+          if Object.keys(cache.activities).length > 1
+            if updateTo
+              newLastIndex   = overviewIndexToDelete - 1
+              setModifier.to = cache.overview[newLastIndex].createdAt[cache.overview[newLastIndex].createdAt.length-1]
+
+            if updateFrom
+              newFirstIndex    = overviewIndexToDelete + 1
+              setModifier.from = cache.overview[newFirstIndex].createdAt[0]

           cache.update
             $unset : unsetModifier
             $set   : isFull : no
           , ->
             log "activity removed from cache!", Object.keys(cache.activities).length
+            if Object.keys(cache.activities).length is 0
+              cache.remove ->
+                log "cache instance removed!"
+

   printOverview = (overview)->


Report activities

Report activities

--- workers/social/lib/social/models/activity/index.coffee
+++ workers/social/lib/social/models/activity/index.coffee
@@ -130,6 +130,12 @@
                                  'sorts.likesCount'   : meta?.likes or 0
                               CActivity.update {_id}, op, -> queue.fin()

+  save: (rest...) ->
+    # StatsD activities
+    if KONFIG.statsd?.run
+      KONFIG.statsd.instance.increment('page.activity')
+    jraphical.Capsule::save.apply this, rest...
+
   fetchTeaser:(callback)->
     @fetchSubject (err, subject)->
       if err


Referral: removed quotes from object key

--- workers/social/lib/social/models/referral/index.coffee
+++ workers/social/lib/social/models/referral/index.coffee
@@ -385,7 +385,7 @@
     selector = {
       diskSizeInMB : $gte : 4096
       vmType       : "user"
-      webHome      : "$not" : new RegExp "guest-"
+      webHome      : $not : new RegExp "guest-"
     }

     JVM.someData selector, {webHome:1}, {}, (err, cursor)=>


ticket zoom: auto-scroll to bottom when rendering

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -1,4 +1,7 @@
 class App.TicketZoom extends App.Controller
+  elements:
+    '.main': 'main'
+
   events:
     'click .js-submit': 'submit'

@@ -385,6 +388,11 @@

     @bindScrollPageHeader()

+    @scrollToBottom()
+
+  scrollToBottom: ->
+    @main.scrollTop( @main.prop('scrollHeight') )
+
   bindScrollPageHeader: ->
     pageHeader = @$('.page-header')
     scrollHolder = pageHeader.scrollParent()


MCKIN-12135-MCKIN-12137 Added text for discussion post add/edit labels (#1561)

--- lms/static/coffee/src/customwmd.coffee
+++ lms/static/coffee/src/customwmd.coffee
@@ -126,6 +126,7 @@
       $elem.empty()
       _append = appended_id || ""
       wmdInputId = "wmd-input#{_append}"
+      for_label = if wmdInputId.endsWith("undefined") then gettext("Your question or idea") else gettext("Add a response:")
       $wmdPreviewContainer = $("<div>").addClass("wmd-preview-container")
           .attr("role", "region")
           .attr("aria-label", gettext("HTML preview of post"))
@@ -133,7 +134,7 @@
           .append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
       $wmdPanel = $("<div>").addClass("wmd-panel")
                  .append($("<div>").attr("id", "wmd-button-bar#{_append}"))
-                 .append($("<label>").addClass("sr").attr("for", wmdInputId).text(gettext("Post body")))
+                 .append($("<label>").addClass("sr").attr("for", wmdInputId).text(for_label))
                  .append($("<textarea>").addClass("wmd-input").attr("id", wmdInputId).html(initialText))
                  .append($wmdPreviewContainer)
       $elem.append($wmdPanel)