Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -14,7 +14,7 @@
       this.each (item) ->
         item.I[attr] = value

-  closest: () ->
+  closest: (selector) ->

   ###*
   Get a selection of GameObjects that match the specified selector criteria. The selector language


lowercase entire class

lowercase entire class

Signed-off-by: Steven Hoffman <f5bcacb8ce1651b7d1e56f2693431f20e085fdeb@valenciamgmt.com>

--- vendor/assets/javascripts/fustrate/_module.coffee
+++ vendor/assets/javascripts/fustrate/_module.coffee
@@ -76,7 +76,7 @@

     $('<span>')
       .text(text)
-      .prop('class', "label #{type}#{text.toLowerCase()}")
+      .prop('class', "label #{type}#{text}".toLowerCase())

   @icon: (type) ->
     $('<i class="fa">').addClass("fa-#{type}")


Icons for blueprints panel

Icons for blueprints panel

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -888,11 +888,26 @@
     dom.div null,
       dom.h3 null, "Blueprints"
       dom.ul null,
-        dom.li null, "Copied #{@props.stats.industryRamJobsCompletedCopyBlueprint} blueprints"
-        dom.li null, "#{@props.stats.industryRamJobsCompletedMaterialProductivity} ME jobs"
-        dom.li null, "#{@props.stats.industryRamJobsCompletedTimeProductivity} TE jobs"
-        dom.li null, "#{@props.stats.industryRamJobsCompletedInvention} invention jobs"
-        dom.li null, "#{@props.stats.industryRamJobsCompletedReverseEngineering} reverse engineering jobs"
+        dom.li null,
+          dom.img {src: 'images/icons/UI/Industry/copying.png'}
+          ' '
+          "Copied #{@props.stats.industryRamJobsCompletedCopyBlueprint} blueprints"
+        dom.li null,
+          dom.img {src: 'images/icons/UI/Industry/researchMaterial.png'}
+          ' '
+          "#{@props.stats.industryRamJobsCompletedMaterialProductivity} ME jobs"
+        dom.li null,
+          dom.img {src: 'images/icons/UI/Industry/researchTime.png'}
+          ' '
+          "#{@props.stats.industryRamJobsCompletedTimeProductivity} TE jobs"
+        dom.li null,
+          dom.img {src: 'images/icons/UI/Industry/invention.png'}
+          ' '
+          "#{@props.stats.industryRamJobsCompletedInvention} invention jobs"
+        dom.li null,
+          dom.img {src: 'images/icons/UI/Industry/manufacturing.png'}
+          ' '
+          "#{@props.stats.industryRamJobsCompletedReverseEngineering} reverse engineering jobs"
 )

 MiningPanel = React.createClass(


Fixed encoding conversion issue due to undefined fullPath

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -485,15 +485,21 @@
 					encoding ?= 'utf8'

 				# Convert into utf8
-				unless encoding.toLowerCase() in ['ascii','utf8','utf-8']
+				if encoding.toLowerCase() not in ['ascii','utf8','utf-8']
+					# Can convert?
 					if Iconv?
-						@log('info', "Converting encoding #{encoding} to UTF-8 on #{fullPath}")
-						try
-							buffer = new Iconv(encoding,'utf8').convert(buffer)
-						catch err
-							@log('warn', "Encoding conversion failed, therefore we cannot convert the encoding #{encoding} to UTF-8 on #{fullPath}")
+						@log('info', "Converting encoding #{encoding} to UTF-8 on #{relativePath}")
+
+						# Convert
+						d = require('domain').create()
+						d.on 'error', =>
+							@log('warn', "Encoding conversion failed, therefore we cannot convert the encoding #{encoding} to UTF-8 on #{relativePath}")
+						d.run ->
+							buffer = new Iconv(encoding, 'utf8').convert(buffer)
+
+					# Can't convert
 					else
-						@log('warn', "Iconv did not load, therefore we cannot convert the encoding #{encoding} to UTF-8 on #{fullPath}")
+						@log('warn', "Iconv did not load, therefore we cannot convert the encoding #{encoding} to UTF-8 on #{relativePath}")

 				# Apply
 				changes.encoding = encoding


Workspace: Minor PR fix.

Workspace: Minor PR fix.

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -117,8 +117,8 @@

       return callback err  if err

-      users = machine.users.filter (user) -> return user.username isnt nickname
-      users = users.map (user) -> return user.username
+      users = machine.users.filter (user) -> user.username isnt nickname
+      users = users.map (user) -> user.username

       if users.length
         { notifyByUsernames } = require './notify'


Don't rely on linux features of less than a year old due to outdated servers.

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -130,9 +130,9 @@
           'build/server/nu.min.js':                          ['build/nu.js']
     shell:
       gzip_js_files:
-        command: 'find build/ -iname \'*.js\'  -print0 | xargs -0 -n 1 gzip -kf -9'
+        command: ' find build/ -iname \'*.js\'  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '
       gzip_css_files:
-        command: 'find build/ -iname \'*.css\'  -print0 | xargs -0 -n 1 gzip -kf -9'
+        command: ' find build/ -iname \'*.css\'  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '

     copy:
       build:


Mark start_annotating etc. as obsolete.

Mark start_annotating etc. as obsolete.

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -65,8 +65,8 @@
           'build/jail_iframe.min.js':                        ['build/jail_iframe.js']
       all_except_jail_iframe:
         files:
-          'build/factlink.start_annotating.min.js':   ['build/factlink.start_annotating.js']
-          'build/factlink.stop_annotating.min.js':    ['build/factlink.stop_annotating.js']
+          'build/factlink.start_annotating.min.js':   ['build/factlink.start_annotating.js'] #Obsolete; remove 2014-04-01
+          'build/factlink.stop_annotating.min.js':    ['build/factlink.stop_annotating.js'] #Obsolete; remove 2014-04-01
           'build/factlink.start_highlighting.min.js': ['build/factlink.start_highlighting.js'] #Obsolete; remove 2014-04-01
           'build/factlink.stop_highlighting.min.js':  ['build/factlink.stop_highlighting.js'] #Obsolete; remove 2014-04-01
           'build/factlink_loader_basic.min.js':       ['build/factlink_loader_basic.js']


Don't pack spellchecker into asar archive

--- build/tasks/generate-asar-task.coffee
+++ build/tasks/generate-asar-task.coffee
@@ -14,6 +14,7 @@
       'ctags-darwin'
       'ctags-linux'
       'ctags-win32.exe'
+      '**/node_modules/spellchecker/**'
     ]
     unpack = "{#{unpack.join(',')}}"


fix(build): prebuild less was using wrong options, cache not used at runtime

--- build/tasks/prebuild-less-task.coffee
+++ build/tasks/prebuild-less-task.coffee
@@ -4,9 +4,7 @@
 module.exports = (grunt) ->
   grunt.registerMultiTask 'prebuild-less', 'Prebuild cached of compiled LESS files', ->
     LessCache = require 'less-cache'
-    prebuiltConfigurations = [
-      ['ui-light', 'ui-dark']
-    ]
+    prebuiltConfigurations = [['ui-light']]

     directory = path.join(grunt.config.get('nylasGruntConfig.appDir'), 'less-compile-cache')


Compile bootstrap before prebuilding cache

--- build/tasks/prebuild-less-task.coffee
+++ build/tasks/prebuild-less-task.coffee
@@ -21,6 +21,8 @@
     rm(path.join(appDir, 'node_modules', 'bootstrap', 'less'))

   grunt.registerMultiTask 'prebuild-less', 'Prebuild cached of compiled LESS files', ->
+    compileBootstrap()
+
     prebuiltConfigurations = [
       ['atom-dark-ui', 'atom-dark-syntax']
       ['atom-dark-ui', 'atom-light-syntax']
@@ -94,5 +96,3 @@
       for file in themeMains
         grunt.verbose.writeln("File #{file.cyan} created in cache.")
         cssForFile(file)
-
-    compileBootstrap()


:memo: Mention fedora/debian checks

:memo: Mention fedora/debian checks

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -73,9 +73,11 @@
         arch = 'amd64'
       {version} = grunt.file.readJSON('package.json')

+      # Check for a Debian build
       sourcePath = "#{buildDir}/atom-#{version}-#{arch}.deb"
       assetName = "atom-#{arch}.deb"

+      # Check for a Fedora build
       unless fs.isFileSync(sourcePath)
         rpmName = fs.readdirSync("#{buildDir}/rpm")[0]
         sourcePath = "#{buildDir}/rpm/#{rpmName}"


version bump

version bump

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

--- guanlecoja/config.coffee
+++ guanlecoja/config.coffee
@@ -9,12 +9,14 @@
 require("shelljs/global")

 gulp.task "publish", ['default'], ->
-    if not exec "git diff --no-ext-diff --quiet --exit-code"
-        echo "print commit your changes"
+    r = exec "git diff --no-ext-diff --quiet --exit-code"
+    if r.code > 0
+        exec("git status")
+        echo "Please commit your changes before publish!"
         return
     bower_json =
         name: "guanlecoja-ui"
-        version: "1.0.4"
+        version: "1.0.5"
         main: ["scripts.js", "styles.css", "fonts/*", "img/*"]
         description: "Sets of widgets and integrated bower dependencies useful for dashboard SPAs"
         dependencies: {}


Behaviour: Throw error on null object.

Behaviour: Throw error on null object.

--- engine/behaviour.coffee
+++ engine/behaviour.coffee
@@ -21,8 +21,11 @@
         @assignment_times = {}
         @objects = []
         if objects
-            for ob in objects
-                @assign ob
+            for ob,i in objects
+                if ob?
+                    @assign ob
+                else
+                    throw "Behaviour 'objects' list has a null element at index #{i}"

         @_root = @context.root
         @context.behaviours.push @


catch script load failure

catch script load failure

--- lib/commoncommon/web.coffee
+++ lib/commoncommon/web.coffee
@@ -79,6 +79,8 @@

   # require module name, with optional callback on success.
   # passes failed module name to callback on error, null on success.
+  # the callback is only called after the module and all of its dependencies
+  # have loaded.
   require: (name, callback) ->
     mod = @modules[name]
     if mod
@@ -99,20 +101,33 @@
     mod.pushOnload callback if callback

     path = @libpath + '/' + name.replace(/\./g, '/') + '.js'
-    script = document.createElement 'script'
+    script = mod.script = document.createElement 'script'
     script.type = 'text/javascript'
     script.src = path
     # script.onload = -> console.log "#{path} loaded"
     script.onerror = ->
-      # this doesn't work, need a timer or something :(
-      # console.log "failed to load #{path}"
+      # this doesn't work directly.. is called later after dom completion
       mod.failed = true
       if callback
         callback(name)
       else
         alert "error requiring #{name}"
+
     @head.appendChild script
-    # require a module, which in turn will require its dependencies
+
+    # script.onerror doesn't work directly so have to wait for the document
+    # readystatechange to go back to complete. at this stage any unloaded
+    # script has its "onerror" event manually called.
+    unless @_monitored
+      @_monitored = true
+      document.onreadystatechange = =>
+        return unless 'complete' == document.readyState
+        delete @_monitored
+        for own name, mod of @modules
+          if mod.loading and mod.script
+            do mod.script.onerror
+
+    @this

 window.cc = new CC


improve error message on parse error

improve error message on parse error


Add a guard that fixes strict mode

Add a guard that fixes strict mode

--- app/js/jasmine-fixture.coffee
+++ app/js/jasmine-fixture.coffee
@@ -32,7 +32,7 @@
       currentJasmineFixture

     $whatsTheRootOf = (that) ->
-      if that.jquery?
+      if that?.jquery?
         that
       else if $('#jasmine_content').length > 0
         $('#jasmine_content')


Use Workspace::getActiveEditor()

Use Workspace::getActiveEditor()

--- lib/markdown-preview.coffee
+++ lib/markdown-preview.coffee
@@ -14,14 +14,13 @@
       new MarkdownPreviewView(pathname)

   show: ->
-    paneView = atom.workspaceView.getActivePaneView()
-    editor = paneView.getActiveItem()
-
-    unless editor.getGrammar?().scopeName is "source.gfm"
-      console.warn("Can not render markdown for '#{editor.getUri() ? 'untitled'}'")
+    editor = atom.workspace.getActiveEditor()
+    unless editor? and editor.getGrammar().scopeName is "source.gfm"
+      console.warn("Can not render markdown for '#{editor?.getUri() ? 'untitled'}'")
       return

+    previousActivePaneView = atom.workspaceView.getActivePaneView()
     uri = "markdown-preview://#{editor.getPath()}"
     atom.workspace.open(uri, split: 'right').done (markdownPreviewView) ->
       markdownPreviewView.renderMarkdown()
-      paneView.focus()
+      previousActivePaneView.focus()


put templates in templates object

put templates in templates object

--- app/js/views/templates.coffee
+++ app/js/views/templates.coffee
@@ -1,27 +1,23 @@
-requesting = {}
+Factlink.templates =
+  create: """
+

+
+        Add Factlink
+        Added
+

-Factlink.templates = {}
-
-Factlink.templates.getTemplate = (str, callback = ->) ->
-  if str == "create"
-    callback -> """
-

-
-          Add Factlink
-          Added
-
-
-        Loading...
-

-    """
-  else if str == "indicator"
-    callback -> """
+      Loading...
+

+  """
+  indicator: """

         Show Factlink
         Loading...

-    """
-  else alert "meh"
+  """
+
+Factlink.templates.getTemplate = (name, callback = ->) ->
+  callback -> Factlink.templates[name]

 Factlink.templates.preload = ->
   Factlink.templates.getTemplate 'indicator'


minor updates to tutorial text

minor updates to tutorial text

--- app/lib/tutorial_steps.coffee
+++ app/lib/tutorial_steps.coffee
@@ -5,7 +5,7 @@
 module.exports = [
   new Step
     header: 'Welcome to SpaceWarps!'
-    content: 'Gravitiational Lenses are very rare astronomical objects. We need your help to search the entire Canada-France-Hawaii Telescope Legacy Survey in order to discover new lenses. This short tutorial will show you how to identify gravitational lenses.'
+    content: 'Gravitiational Lenses are very rare astronomical objects. This short tutorial will show you how to identify gravitational lenses.'
     attachment:
       to: '.primary'
       x: 0.5
@@ -14,7 +14,14 @@

   new Step
     header: 'What are gravitational lenses?'
-    content: 'Gravitational lenses are massive astronomical objects – such as galaxies – that lie in front of more distant galaxies. Light rays from the background galaxy traveling towards our telescope are bent by the gravity of the foreground galaxy. Just as the Earth\'s gravity keeps everyday objects (including us!) on the ground, the gravitational pull of massive galaxies also attracts light: they act like huge natural magnifying glasses, focusing the light of the background galaxy towards us.'
+    content: 'Gravitational lenses are massive astronomical objects – such as galaxies – that lie in front of more distant galaxies. Light rays from the background galaxy traveling towards our telescope are bent by the gravity of the foreground galaxy.'
+    attachment:
+      to: '.primary'
+    block: '.primary, .controls:nth(0)'
+
+  new Step
+    header: 'What are gravitational lenses?'
+    content: 'Just as the Earth\'s gravity keeps everyday objects (including us!) on the ground, the gravitational pull of massive galaxies also attracts light: they act like huge natural magnifying glasses, focusing the light of the background galaxy towards us.'
     attachment:
       to: '.primary'
     block: '.primary, .controls:nth(0)'
@@ -66,9 +73,7 @@

   new Step
     header: 'Good job!'
-    content: "Great, you've helped identify a gravitational lens! As you continue to search for gravitational lenses you can use the Spotter's Guide as reference.  Over your first few classifications we'll give you a few more tips and access to some different tools to help you as you search for these rare objects.  We'll also throw in a few training images containing simulated gravitational lenses, giving you a chance to test your skills.
-
-
Click 'Finished marking!' to continue.
"
+    content: "Great, you've helped identify a gravitational lens! As you continue to search for gravitational lenses you can use the Spotter's Guide as reference.

Over your first few classifications we'll give you a few more tips and access to some different tools to help you as you search for these rare objects. We'll also throw in a few training images containing simulated gravitational lenses, giving you a chance to test your skills.

Click 'Finished marking!' to continue."
     attachment:
       to: 'svg'
       at:


Groups: searchRoute is regressed from dashboard

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -12,7 +12,6 @@
       "/:name?/Dashboard/:section" : ({params : {section,name}})->
         handler name, (app)-> app.loadSection title : decodeURIComponent section
     hiddenHandle : yes
-    searchRoute : "/Dashboard?q=:text:"

   constructor: (options = {}, data) ->


don't leak kites!

don't leak kites!

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -91,8 +91,16 @@
         who   : @getWhoParams name, correlationName
     .then(kite.bound 'setTransport')
     .then(kite.bound 'logTransportFailures')
-    .catch(@error.bind this)
+    .catch (err) =>
+      warn err

+      {message} = err
+      message   = if message then message else err
+
+      ErrorLog.create message
+
+      # delete this from the cache:
+      @setCachedKite name, correlationName
     kite

   getKiteProxy: (options) ->
@@ -112,6 +120,9 @@
   getKite: (options = {}) ->
     { name, correlationName, region } = options

+    if (kite = @getCachedKite name, correlationName)?
+      return kite
+
     kite = @getKiteProxy options

     @fetchRegion(correlationName, region).then (region) =>
@@ -125,11 +136,3 @@
     .catch(@error.bind this)

     kite
-
-  error: (err)->
-    warn err
-
-    {message} = err
-    message   = if message then message else err
-
-    ErrorLog.create message


Activity: AppView::open now handles 404s

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -45,16 +45,33 @@

   open: (type, slug) ->

+    {socialapi, router, notificationController} = KD.singletons
+
+
+    kallback = (data) =>
+      name = if slug then "#{type}-#{slug}" else type
+      pane = @tabs.getPaneByName name
+
+      if pane
+      then @tabs.showPane pane
+      else @createTab name, data
+
     @sidebar.selectItemByRouteOptions type, slug
+    item = @sidebar.selectedItem

-    item = @sidebar.selectedItem or @sidebar.public
-    data = item.getData()
-    name = if slug then "#{type}-#{slug}" else type
-    pane = @tabs.getPaneByName name
+    if type is 'public'
+      item = @sidebar.public
+      kallback item.getData()

-    if pane
-    then @tabs.showPane pane
-    else @createTab name, data
+    else if not item
+      socialapi.cacheable type, slug, (err, data) ->
+        if err then router.handleNotFound router.getCurrentPath()
+        else
+          notificationController.emit 'AddedToChannel', data
+          KD.utils.wait 1000, -> kallback data
+
+    else
+      kallback item.getData()

   createTab: (name, data) ->


Remove tree-view observation

Remove tree-view observation

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -32,7 +32,11 @@
     atom.commands.dispatch(@workspaceView, 'tree-view:reveal-active-file')

   handleEvents: ->
-    @observeTreeView()
+    atom.commands.onDidDispatch ({type, target}) =>
+      console.log type
+      switch type
+        when 'core:confirm' then @onCoreConfirm()
+        when 'tree-view:remove' then @onRemove(target)

     atom.workspace.observeTextEditors (editor) =>
       editor.onDidSave =>
@@ -43,46 +47,23 @@
     atom.workspace.onDidOpen ({uri, item, pane, index}) ->
       console.log 'OPENED ' + uri

-    atom.commands.onDidDispatch ({type}) ->
-      console.log type
-
-    atom.commands.add atom.views.getView(atom.workspace),
-      'tree-view:remove': ({target}) => @onRemove(target)
-      'tree-view:toggle': => @observeTreeView()
-      'learn-ide:tree-view-mutation': ({detail}) => @onTreeViewMutation(detail)
-      'learn-ide:tree-view-add': => @onTreeViewAdd()
-
-  observeTreeView: ->
-    treeViewEl = document.getElementsByClassName('tree-view')[0]
-    return unless treeViewEl?
-
-    mutationObserver = new MutationObserver (mutations) =>
-      atom.commands.dispatch(@workspaceView, 'learn-ide:tree-view-mutation', mutations)
-
-    mutationObserver.observe(treeViewEl, {subtree: true, childList: true})
-
   onSave: (editor) =>
     editorElement = atom.views.getView(editor)
     atom.commands.dispatch(editorElement, 'line-ending-selector:convert-to-LF')

     {project, buffer} = editor
     {file} = buffer
-    inCodeDir = !!@formatFilePath(buffer.file.path).match(/\.atom\/code/)
-    console.log 'Saving: Path - ' + @formatFilePath buffer.file.path + ' Matches? - ' + inCodeDir
+    inCodeDir = !!@formatPath(buffer.file.path).match(/\.atom\/code/)
+    console.log 'Saving: Path - ' + @formatPath buffer.file.path + ' Matches? - ' + inCodeDir
     return unless inCodeDir

     @popupNoConnectionWarning() if @connectionState is 'closed'
     @sendSave(editor.project, buffer.file, buffer)

-  onTreeViewMutation: (mutations) ->
-    domNodesAdded = _.any(mutations, (m) -> not _.isEmpty(m.addedNodes))
+  onCoreConfirm: ->
+    @syncAdditions()

-    if domNodesAdded
-      atom.commands.dispatch(@workspaceView, 'learn-ide:tree-view-add')
-    else
-      @filesystemTree.reload()
-
-  onTreeViewAdd: ->
+  syncAdditions: ->
     prevEntries = _.clone(@filesystemTree.entries)
     newEntries = _.difference(@filesystemTree.reload(), prevEntries)
     return if _.isEmpty(newEntries)
@@ -94,31 +75,31 @@
   onRemove: (node) ->
     @sendRemove(node.dataset.path || node.firstChild.dataset.path)

-  sendAddFile: (path) =>
+  sendAddFile: (path) ->
     ipc.send 'fs-local-add-file', JSON.stringify(
       action: 'local_add_file'
       project:
-        path: @formatFilePath(@projectPath)
+        path: @formatPath(@projectPath)
       file:
-        path: @formatFilePath(path)
+        path: @formatPath(path)
       )

-  sendAddFolder: (path) =>
+  sendAddFolder: (path) ->
     ipc.send 'fs-local-add-folder', JSON.stringify(
       action: 'local_add_folder'
       project:
-        path: @formatFilePath(@projectPath)
+        path: @formatPath(@projectPath)
       file:
-        path: @formatFilePath(path)
+        path: @formatPath(path)
       )

   sendSave: (project, file, buffer) ->
     ipc.send 'fs-local-save', JSON.stringify(
       action: 'local_save'
       project:
-        path: @formatFilePath(project.getPaths()[0])
+        path: @formatPath(project.getPaths()[0])
       file:
-        path: @formatFilePath(file.path)
+        path: @formatPath(file.path)
         digest: file.digest,
       buffer:
         content: window.btoa(unescape(encodeURIComponent(buffer.getText())))
@@ -128,12 +109,12 @@
     ipc.send 'fs-local-delete', JSON.stringify(
       action: 'local_delete'
       project:
-        path: @formatFilePath(@projectPath)
+        path: @formatPath(@projectPath)
       file:
-        path: @formatFilePath(path)
+        path: @formatPath(path)
       )

-  formatFilePath: (path) ->
+  formatPath: (path) ->
     if path.match(/:\\/)
       path.replace(/(.*:\\)/, '/').replace(/\\/g, '/')
     else


fix(minifyHtml): do not minify prior to templateCache

Wrecks formatting for markdown and syntax

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -606,7 +606,7 @@
 		.then processCss
 		.then processSourceStyles

-gulp.task 'templateCache', ['copy:temp', 'minify:views'], ->
+gulp.task 'templateCache', ['copy:temp'], ->
 	return true if not isProd

 	options =


fix - remove conditional logic

fix - remove conditional logic

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -935,7 +935,7 @@

 # Open the app in the default browser
 gulp.task 'open', ['server'], ->
-	openApp() if useBackendless
+	openApp()

 # Execute Plato complexity analysis
 gulp.task 'plato', ['clean:working'], ->
@@ -1097,7 +1097,7 @@
 # Start a web server without rebuilding
 gulp.task 'serve', ->
 	startServer()
-	openApp() if useBackendless
+	openApp()

 # Start a web server
 gulp.task 'server', ['build'], ->


fixed generation of sourcemaps

fixed generation of sourcemaps

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,11 +1,14 @@
 gulp = require 'gulp'
 gulp_sourcemaps = require 'gulp-sourcemaps'
 gulp_coffee = require 'gulp-coffee'
+gulp_concat = require 'gulp-concat'

 gulp.task 'coffee', ->
-	gulp.src 'src/**/*.coffee'
+	gulp.src './src/**/*.coffee'
 	.pipe gulp_sourcemaps.init()
 	.pipe gulp_coffee(bare: true)
-	.pipe gulp.dest 'build'
+	.pipe gulp_concat 'angular-amazon-login.js'
+	.pipe gulp_sourcemaps.write './'
+	.pipe gulp.dest './build'

 gulp.task 'default', ['coffee']


Add a RecentProjects component Remove the two PromiseRenderers in favour of storing API responses in component state.

--- app/pages/home-logged-in.cjsx
+++ app/pages/home-logged-in.cjsx
@@ -60,6 +60,55 @@
       <Link to="/projects" className="call-to-action standard-button x-large"><Translate content="home.featuredProjects.button" /></Link>

+RecentProjects = React.createClass
+  displayName: 'RecentProjects'
+
+  getInitialState: ->
+    projects: []
+    activityCounts: {}
+    title: ""
+    href: ''
+    action: ""
+
+  componentWillMount: ->
+    @props.user
+      .get("project_preferences", page_size: 4, sort: '-updated_at')
+      .then (projectPreferences) =>
+        userProjects = projectPreferences.map (projectPreference) -> projectPreference.links.project
+        activityCounts = {}
+        if userProjects.length > 0
+          activityCounts[projectPreference.links.project] = projectPreference.activity_count for projectPreference in projectPreferences
+          @setState
+            activityCounts: activityCounts
+            title: "home.recentProjects.title"
+            href: "/users/#{@props.user.login}/stats"
+            action: "home.recentProjects.button"
+          projectsPromise = apiClient.type('projects').get(userProjects)
+        else
+          @setState
+            title: "home.recentProjects.altTitle"
+            href: '/projects'
+            action: "home.recentProjects.altButton"
+          projectsPromise = apiClient.type('projects').get(launch_approved: true, page_size: 4, cards: true)
+        projectsPromise
+      .then (projects) =>
+        @setState {projects}
+
+  render: ->
+

+      <Translate component="h5" content={@state.title} />
+

+        {for project in @state.projects
+          badge = @state.activityCounts[project.id] ? 0
+

+            <ProjectIcon project={project} badge={badge} />
+
+
}
+

+      <Link to={@state.href} className="call-to-action standard-button x-large"><Translate content={@state.action} /></Link>
+

+
+
 module.exports = React.createClass
   displayName: 'HomePage'

@@ -81,39 +130,7 @@

           <ZooniverseLogoType />
-          <PromiseRenderer promise={@lastFourProjects()}>{(projectPreferences) =>
-            if projectPreferences.length > 0
-

-                <Translate component="h5" content="home.recentProjects.title" />
-

-                  {projectPreferences.map (projectPreference) =>
-                    <PromiseRenderer key={projectPreference.id} promise={projectPreference.get 'project'} catch={null} then={(project) =>
-                      if project?
-

-                          <ProjectIcon project={project} badge={projectPreference.activity_count} />
-
-

-                      else
-                        null
-                    } />}
-

-                <Link to="#{baseLink}users/#{@props.user.login}/stats" className="call-to-action standard-button x-large"><Translate content="home.recentProjects.button" /></Link>
-

-            else
-

-                <Translate component="h5" content="home.recentProjects.altTitle" />
-                <PromiseRenderer promise={apiClient.type('projects').get(launch_approved: true, page_size: 4, cards: true)}>{(projects) =>
-

-                    {projects.map (project) ->
-

-                        <ProjectIcon key={project.id} project={project} />
-
-
}
-

-                }</PromiseRenderer>
-                <Link to="/projects" className="call-to-action standard-button hero-button x-large"><Translate content="home.recentProjects.altButton" /></Link>
-

-          }</PromiseRenderer>
+          <RecentProjects user={@props.user} />


Removal of Best Practices Link

Removal of Best Practices Link

--- app/pages/lab/help/index.cjsx
+++ app/pages/lab/help/index.cjsx
@@ -10,7 +10,6 @@
       howToBuildAProject: 'How to Build a Project'
       policies: 'Policies'
       glossary: 'Glossary'
-      bestPractices: 'Best Practices'

 module.exports = React.createClass
   displayName: 'HowTo'
@@ -36,7 +35,6 @@
             <IndexLink to="/help" activeClassName="active"><Translate content="help.nav.howToBuildAProject" /></IndexLink>
             <Link to="/help/glossary" activeClassName="active" onClick={@logClick?.bind(this, 'help.nav.glossary')}><Translate content="help.nav.glossary"/></Link>
             <Link to="/help/lab-policies" activeClassName="active" onClick={@logClick?.bind(this, 'help.nav.policies')}><Translate content="help.nav.policies" /></Link>
-            <Link to="/lab-best-practices/introduction" activeClassName="active" onClick={@logClick?.bind(this, 'help.nav.bestPractices')}><Translate content="help.nav.bestPractices" /></Link>
           </nav>
         </div>
       </section>


Add build's building property.

Add build's building property.

--- lib/api.coffee
+++ lib/api.coffee
@@ -61,9 +61,11 @@
     timestamp: new Date().getTime()
     fullDisplayName: "#{job.name} ##{number}"
     id: dateFormat new Date(), 'yyyy-mm-dd_HH-MM-ss'
+    building: true
   job.builds.push build
   fn = ->
     build.result = result ? 'SUCCESS'
+    build.building = false
     workspace._onUpdateJob job.name
   setTimeout fn, duration ? 1
   build


i18n tooltip in preference-util.coffee

i18n tooltip in preference-util.coffee

- also save content before i18n in attr data-before-localized

--- lib/preferences-util.coffee
+++ lib/preferences-util.coffee
@@ -19,10 +19,9 @@
     before = String(elem.textContent)
     return if before == text
     elem.innerHTML = text    # NOTE text may contain HTML
-    elem.setAttribute('title', before)
+    elem.setAttribute('title', text)
     elem.setAttribute('data-localized', 'true')
-    # NOTE indicator for localized elemnt
-    # TODO elem.setAttribute('data-before-localized', before)
+    elem.setAttribute('data-before-localized', before)

   @getTextMatchElement = (area, query, text) ->
     elems = area.querySelectorAll(query)


Realtime Manager: Reset states when disposed

--- client/ide/lib/realtimemanager.coffee
+++ client/ide/lib/realtimemanager.coffee
@@ -293,3 +293,5 @@
     @realtimeDoc.close()
     @isDisposed = yes
     @destroy()
+    @readyState = 0
+    @isReady = no


Fix bug. Add `deepClone`

Fix bug. Add `deepClone`

--- src/6.misc.coffee
+++ src/6.misc.coffee
@@ -1,5 +1,6 @@
 Object.isObject = (x) -> typeof x in ["object", "function"] and x != null
 Object.isNormalObject = (x) -> Object.isObject(x) and typeof x != "function" and not Array.isArray(x)
+# TODO: need to use new concept
 Object.clone = (x) ->
     y = {}
     for key in Object.keys(x)
@@ -12,14 +13,14 @@
 Object.forEachOfAll = (x, callback) -> Object.allKeys(x).forEach((key) -> callback(key, x[key]))
 Object.deepAssign = (target, sources...) ->
     sources.forEach((source) ->
-        assign = (target, source) ->
+        deepAssign = (target, source) ->
             Object.forEach(source, (key, value) ->
-                if Object.isObject(value)
-                    assign(target[key], value)
+                if Object.isObject(target[key]) and Object.isObject(value)
+                    deepAssign(target[key], value)
                 else
                     target[key] = value
             )
-        assign(target, source)
+        deepAssign(target, source)
     )
     target
 Object.absorb = (subject, objects...) ->
@@ -29,16 +30,27 @@
     subject
 Object.deepAbsorb = (subject, objects...) ->
     objects.forEach((object) ->
-        absorb = (subject, object) ->
+        deepAbsorb = (subject, object) ->
             Object.forEach(object, (key, value) ->
-                if Object.isObject(value)
-                    absorb(subject[key], value)
+                if Object.isObject(subject[key]) and Object.isObject(value)
+                    deepAbsorb(subject[key], value)
                 else
                     subject[key] = value if subject[key] == undefined
             )
-        absorb(subject, object)
+        deepAbsorb(subject, object)
     )
     subject
+Object.deepClone = (x) ->
+    target = {}
+    deepExtend = (target, source) ->
+        Object.forEach(source, (key, value) ->
+            if Object.isObject(value)
+                target[key] = {}
+                deepExtend(target[key], value)
+            else
+                target[key] = value
+        )
+    deepExtend(target, x)
 JSON.clone = (x) -> JSON.parse(JSON.stringify(x))
 Date::add = (x) -> # `x` must be a number
     new Date(@ - (-x))


Fixed name of helper

Fixed name of helper

--- client/templates/photos/photos.coffee
+++ client/templates/photos/photos.coffee
@@ -1,2 +1,2 @@
-Template.photos.helpers
+Template.photospage.helpers
   photos: -> Photos.find {}, {}


there's an issue with jasmine-reporters

there's an issue with jasmine-reporters

--- test/e2e/protractor.conf.coffee
+++ test/e2e/protractor.conf.coffee
@@ -1,16 +1,16 @@
-require 'jasmine-reporters'
+# require 'jasmine-reporters'
 wrench = require 'wrench'

 onPrepare = () ->
-  dir = 'test/report/protractor/'
+  # dir = 'test/report/protractor/'

   # https://github.com/angular/protractor/issues/38#issuecomment-29801069
   global.select = global.by

-  wrench.mkdirSyncRecursive dir
+  # wrench.mkdirSyncRecursive dir

-  # https://github.com/angular/protractor/issues/268
-  jasmine.getEnv().addReporter new jasmine.JUnitXmlReporter dir, true, true
+  # # https://github.com/angular/protractor/issues/268
+  # jasmine.getEnv().addReporter new jasmine.JUnitXmlReporter dir, true, true

 config =
   onPrepare: onPrepare
@@ -25,7 +25,7 @@
   config.sauceKey = process.env.SAUCE_ACCESS_KEY
   config.capabilities =
     'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
-    'build': process.env.TRAVIS_BUILD_NUMBER
-    'name': "Radian build #{process.env.TRAVIS_BUILD_NUMBER}"
+    build: process.env.TRAVIS_BUILD_NUMBER
+    name: "Radian build #{process.env.TRAVIS_BUILD_NUMBER}"

 exports.config = config


Fix variable name

Fix variable name

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -174,7 +174,7 @@
       }
       @postSignedRequest("/me/request.json", params, (jqXHR) =>
         carrotResponse = $.parseJSON(jqXHR.responseText)
-        fb_data = $.extend({}, response.fb_data, opts)
+        fb_data = $.extend({}, carrotResponse.fb_data, opts)
         postMethod(fb_data,
           (fbResponse) =>
             if fbResponse


Make Align always pixelAligned

Make Align always pixelAligned

--- framer/Align.coffee
+++ framer/Align.coffee
@@ -1,3 +1,5 @@
+pixelRound = parseInt
+
 center = (layer, property, offset=0) ->

 	parent = Screen
@@ -6,8 +8,8 @@
 	borderWidth = parent.borderWidth
 	borderWidth ?= 0

-	x = (parent.width / 2) - (layer.width / 2) - borderWidth + offset
-	y = (parent.height / 2) - (layer.height / 2) - borderWidth + offset
+	x = pixelRound((parent.width / 2) - (layer.width / 2) - borderWidth + offset)
+	y = pixelRound((parent.height / 2) - (layer.height / 2) - borderWidth + offset)

 	return x if property is "x"
 	return y if property is "y"
@@ -18,7 +20,7 @@
 	throw Error "Align.left only works for x" unless property is "x"
 	parent = Screen
 	parent = layer.parent if layer.parent
-	return 0 + offset
+	return pixelRound(0 + offset)

 right = (layer, property, offset=0) ->
 	throw Error "Align.right only works for x" unless property is "x"
@@ -26,13 +28,13 @@
 	parent = layer.parent if layer.parent
 	borderWidth = parent.borderWidth
 	borderWidth ?= 0
-	return parent.width - (2 * borderWidth) - layer.width + offset
+	return pixelRound(parent.width - (2 * borderWidth) - layer.width + offset)

 top = (layer, property, offset=0) ->
 	throw Error "Align.top only works for y" unless property is "y"
 	parent = Screen
 	parent = layer.parent if layer.parent
-	return 0 + offset
+	return pixelRound(0 + offset)

 bottom = (layer, property, offset=0) ->
 	throw Error "Align.bottom only works for y" unless property is "y"
@@ -40,7 +42,7 @@
 	parent = layer.parent if layer.parent
 	borderWidth = parent.borderWidth
 	borderWidth ?= 0
-	return parent.height - (2 * borderWidth) - layer.height + offset
+	return pixelRound(parent.height - (2 * borderWidth) - layer.height + offset)

 # Helper to see if we are dealing with a function or result of a function
 wrapper = (f, name) ->


All ancestors have a scale now

All ancestors have a scale now

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -513,8 +513,7 @@
 		scale = 1
 		scale = @scale * @scaleX if self
 		for parent in @ancestors(context=true)
-			if parent.scale?
-				scale *= parent.scale
+			scale *= parent.scale
 			if parent.scaleX?
 				scale *= parent.scaleX
 		return scale
@@ -523,8 +522,7 @@
 		scale = 1
 		scale = @scale * @scaleY if self
 		for parent in @ancestors(context=true)
-			if parent.scale?
-				scale *= parent.scale
+			scale *= parent.scale
 			if parent.scaleY?
 				scale *= parent.scaleY
 		return scale
@@ -533,8 +531,7 @@
 		scale = 1
 		scale = @scale * @scaleX if self
 		for parent in @ancestors(context=false)
-			if parent.scale?
-				scale *= parent.scale
+			scale *= parent.scale
 			if parent.scaleX?
 				scale *= parent.scaleX
 		return scale
@@ -543,8 +540,7 @@
 		scale = 1
 		scale = @scale * @scaleY if self
 		for parent in @ancestors(context=false)
-			if parent.scale?
-				scale *= parent.scale
+			scale *= parent.scale
 			if parent.scaleY?
 				scale *= parent.scaleY
 		return scale


Fix scrolling bug due to drag event canceling

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -986,7 +986,7 @@
 			@_domEventManager.removeAllListeners(eventName)

 	_parentDraggableLayer: ->
-		for layer in @ancestors().concat(@)
+		for layer in @ancestors()
 			return layer if layer._draggable?.enabled
 		return null


:arrow_up: add children in object

:arrow_up: add children in object

--- src/server/stateInitializer/dir-tree.coffee
+++ src/server/stateInitializer/dir-tree.coffee
@@ -80,6 +80,7 @@
           name: gchild.name
           kindString: gchild.kindString
           path: (def_arr ? arr)[0..-2].concat [gchild.name]
+          children: (gchild.children || []).map((ggchild) -> {name: ggchild.name, kindString: ggchild.kindString})
     else
       res.dir = {}
       res.dir[arr[0]] = arrayToDirTree(arr[1..-1], top, def_arr ? arr)


Fixing hubs service spec

Fixing hubs service spec

--- spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee
+++ spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee
@@ -1,46 +1,45 @@
 describe "Hubs service", ->
   Hubs = null
   Enterprises = null
-  CurrentHubMock = {}
+  CurrentHubMock = {}
   hubs = [
     {
       id: 2
       active: false
       orders_close_at: new Date()
       is_distributor: true
-      has_shopfront: true
+      has_hub_listing: true
     }
     {
       id: 3
       active: false
       orders_close_at: new Date()
       is_distributor: true
-      has_shopfront: true
+      has_hub_listing: true
     }
     {
       id: 1
       active: true
       orders_close_at: new Date()
       is_distributor: true
-      has_shopfront: true
+      has_hub_listing: true
     }
   ]

-
+
   beforeEach ->
     module 'Darkswarm'
-    angular.module('Darkswarm').value('enterprises', hubs)
+    angular.module('Darkswarm').value('enterprises', hubs)
     module ($provide)->
-      $provide.value "CurrentHub", CurrentHubMock
+      $provide.value "CurrentHub", CurrentHubMock
       null
     inject ($injector)->
-      Enterprises = $injector.get("Enterprises")
+      Enterprises = $injector.get("Enterprises")
       Hubs = $injector.get("Hubs")

   it "filters Enterprise.hubs into a new array", ->
     expect(Hubs.hubs[0]).toBe Enterprises.enterprises[2]
-    # Because the $filter is a new sorted array
+    # Because the $filter is a new sorted array
     # We check to see the objects in both arrays are still the same
-    Enterprises.enterprises[2].active = false
+    Enterprises.enterprises[2].active = false
     expect(Hubs.hubs[0].active).toBe false
-


Update import integration spec.

Update import integration spec.

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -501,8 +501,8 @@
         expect(result[0]).toBe '[row 2] New product created.'
         csv =
           """
-          productType,variantId,sku,name,metaTitle,metaKeywords
-          #{@productType.id},1,a111,mySeoProdcut,,changed
+          productType,variantId,sku,name,metaTitle,metaDescription,metaKeywords
+          #{@productType.id},1,a111,mySeoProdcut,,b,changed
           """
         im = createImporter()
         im.import(csv)


data/init is a post request

data/init is a post request

--- src/steroids/servers/BuildServer.coffee
+++ src/steroids/servers/BuildServer.coffee
@@ -146,7 +146,7 @@

         res.status(404).json {error: error}

-    @app.get "/__appgyver/data/init", (req, res) =>
+    @app.post "/__appgyver/data/init", (req, res) =>
       res.header "Access-Control-Allow-Origin", "*"
       res.header "Access-Control-Allow-Headers", "Content-Type"


client list API

client list API

--- src/steroids/servers/BuildServer.coffee
+++ src/steroids/servers/BuildServer.coffee
@@ -234,6 +234,12 @@

       winston.log logLevel, message, metadata

+    @app.get "/__appgyver/clients", (req, res) =>
+      res.header "Access-Control-Allow-Origin", "*"
+      res.header "Access-Control-Allow-Headers", "Content-Type"
+
+      res.send
+        clients: @clients

     @app.get "/refresh_client?:timestamp", (req, res) =>


AppController: Code styling.

AppController: Code styling.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1035,10 +1035,8 @@

   prepareCollaboration___: ->

-    @rtm          = new RealTimeManager
-    { channelId } = @workspaceData
-
-    # return @rtm.ready => @statusBar.share.show()  unless channelId
+    @rtm        = new RealTimeManager
+    {channelId} = @workspaceData

     @rtm.ready => @statusBar.share.show()


headers should use dashes, not underscore

--- client/lanes/models/Sync.coffee
+++ client/lanes/models/Sync.coffee
@@ -83,7 +83,7 @@
         options.headers ||= {}
         options.withCredentials = true
         if Lanes.config.csrf_token
-            options.headers['X_CSRF_TOKEN'] = Lanes.config.csrf_token
+            options.headers['X-CSRF-TOKEN'] = Lanes.config.csrf_token
         options.contentType = "application/json"

         new _.Promise( (resolve, reject) ->


Added attribution for BrowserDetect

Added attribution for BrowserDetect

--- client/lib/BrowserDetect.coffee
+++ client/lib/BrowserDetect.coffee
@@ -1,4 +1,5 @@
-
+# taken from - https://github.com/rahul/js.browser-detection/blob/master/browser-detection.coffee
+# Granted under MIT license

 window.browser =
   initialize: (user_agent) ->


Validation: Added test for whitespaces for alphabetic validator.

--- src/tests/validation/test_validation.coffee
+++ src/tests/validation/test_validation.coffee
@@ -46,6 +46,11 @@
     it 'should return true for an alphabetic string', ->
       expect(spark.validation.isAlphabetic('Fatih')).toBeTruthy()
       expect(spark.validation.isAlphabetic('fatihacet')).toBeTruthy()
+
+
+    it 'should return true if string includes whitespaces', ->
+      expect(spark.validation.isAlphabetic('fatih acet')).toBeTruthy()
+      expect(spark.validation.isAlphabetic('my name is fatih acet ')).toBeTruthy()

     it 'should return false if string has a number in it', ->


Adds UI Prototyping Controller and associated action files

Route to controller only active in development
includes misc mathjax examples I used for equation builder

--- javascripts/widgets/ui_elements.coffee
+++ javascripts/widgets/ui_elements.coffee
@@ -1,9 +1,9 @@
 #= require almond_wrapper
 #= require lib
 #= require jquery
+#= require widgets/mathml

-
-ttm.define 'widgets/ui_elements', ['lib/class_mixer'], (class_mixer)->
+ttm.define 'widgets/ui_elements', ['lib/class_mixer', 'widgets/mathml'], (class_mixer, mathml_renderer)->
   class Button
     initialize: (@opts={})->
     render: (opts={})->
@@ -18,13 +18,42 @@
     initialize: (@opts={})->
     render: (opts={})->
       opts = _.extend({},@opts, opts)
-      figure = $("
#{opts.default || '0'}
")
-      opts.element.append figure
+      @figure = $("
#{opts.default || '0'}
")
+      opts.element.append @figure
+      @figure
+
+    update: (value)->
+      @figure.html(value)
   class_mixer(MathDisplay)
+
+  class MathMLDisplay
+    initialize: (@opts={})->
+      @mathml_renderer = @opts.mathml_renderer || mathml_renderer
+    render: (opts={})->
+      opts = _.extend({}, @opts, opts)
+      @figure = $("""
+

+          <math xmlns=\"http://www.w3.org/1998/Math/MathML\">
+                  <mrow><mn>0</mn></mrow>
+          </math>
+
""")
+      @math_element = @figure.find('math')
+      opts.element.append @figure
+      @figure
+
+    update: (mathml)->
+      #@math_element.html(mathml)
+
+      @mathml_renderer.render(@math_element[0])
+
+
+  class_mixer(MathMLDisplay)
+

   exports =
     button_builder: Button
     math_display_builder: MathDisplay
+    mathml_display_builder: MathMLDisplay

   return exports


More cleanup of SelectBinding

More cleanup of SelectBinding

--- src/view/bindings/select_binding.coffee
+++ src/view/bindings/select_binding.coffee
@@ -14,17 +14,16 @@
   constructor: (definition) ->
     super
     definition.node.removeAttribute('data-bind')
-
-    @backingView.on 'childBindingAdded', @_boundChildBindingAdded = @childBindingAdded.bind(this)
+    @backingView.on 'childBindingAdded', @childBindingAdded
     @bind()

   die: ->
-    @backingView.off 'childBindingAdded', @_boundChildBindingAdded
+    @backingView.off 'childBindingAdded', @childBindingAdded
     super

   childBindingAdded: (binding) =>
     if binding instanceof Batman.DOM.CheckedBinding
-      binding.on 'dataChange', => @nodeChange()
+      binding.on 'dataChange', @nodeChange
     else if binding instanceof Batman.DOM.IteratorBinding
       binding.backingView.on 'itemsWereRendered', => @_fireDataChange(@get('filteredValue'))
     else


Fixed url for good news everyone

Fixed url for good news everyone

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -54,7 +54,7 @@
       msg.send url

   robot.respond /(GOOD NEWS EVERYONE[,.!]?) (.*)/i, (msg) ->
-    memeGenerator msg, 79, 108785, msg.match[1], msg.match[2], (url) ->
+    memeGenerator msg, 1591, 112464, msg.match[1], msg.match[2], (url) ->
       msg.send url

   robot.respond /khanify (.*)/i, (msg) ->


don't allow 0-value or empty amount outputs

--- client/tx/lib/tx-builder.coffee
+++ client/tx/lib/tx-builder.coffee
@@ -126,7 +126,9 @@
       # Ignore empty addresses
       return unless address = $this.find('[name=address]').val().trim()

-      amount = +parseValue $this.find('[name=value]').val().trim()
+      unless amount = +parseValue $this.find('[name=value]').val().trim()
+        throw new Error 'BTC amount cannot be left blank. Please fill in the amount of coins you want to send.'
+
       try out_script = create_out_script address
       catch err
         $this.find('[name=address]').focus()


Link to shared item

Link to shared item

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1557,7 +1557,7 @@
         show_social_media = require('./customize').commercial and single_file_data.is_public
         url = @construct_public_share_url(single_file)
         button_before =
-            <Button bsStyle='default' onClick={=>console.log "TODO OPEN"}>
+            <Button bsStyle='default' onClick={=>window.open(url, "_blank")}>
                 <Icon name='external-link' />
             </Button>


misc fixes and actions added from other repo/user contrib

--- src/scripts/redmine.coffee
+++ src/scripts/redmine.coffee
@@ -4,6 +4,9 @@
 #                                                     *With optional notes
 # update <issue-id> with "<note>"  - Adds a note to the issue
 # add <hours> hours to <issue-id> ["comments"]  - Adds hours to the issue with the optional comments
+# link me <issue-id> - Returns a link to the redmine issue
+# set <issue-id> to <int>% ["comments"] - Updates an issue and sets the percent done
+#
 #
 # Note: <issue-id> can be formatted in the following ways:
 #       1234, #1234, issue 1234, issue #1234
@@ -36,9 +39,35 @@
 module.exports = (robot) ->
   redmine = new Redmine process.env.HUBOT_REDMINE_BASE_URL, process.env.HUBOT_REDMINE_TOKEN

+  # Robot link me <issue>
+  robot.respond /link me (?:issue )?(?:#)?(\d+)/i, (msg) ->
+    id = msg.match[1]
+    msg.reply "#{redmine.url}/issues/#{id}"
+
+  # Robot set <issue> to <percent>% ["comments"]
+  robot.respond /set (?:issue )?(?:#)?(\d+) to (\d{1,2})%?(?: "?([^"]+)"?)?/i, (msg) ->
+    [id, percent, notes] = msg.match[1..3]
+    percent = parseInt percent
+
+    if notes?
+      notes = "#{msg.message.user.name}: #{userComments}"
+    else
+      notes = "Ratio set by: #{msg.message.user.name}"
+
+    attributes =
+      "notes": notes
+      "done_ratio": percent
+
+    redmine.Issue(id).update attributes, (err, data, status) ->
+      if status == 200
+        msg.reply "Set ##{id} to #{percent}%"
+      else
+        msg.reply "Update failed! (#{err})"
+
   # Robot add <hours> hours to <issue_id> ["comments for the time tracking"]
   robot.respond /add (\d{1,2}) hours? to (?:issue )?(?:#)?(\d+)(?: "?([^"]+)"?)?/i, (msg) ->
     [hours, id, userComments] = msg.match[1..3]
+    hours = parseInt hours

     if userComments?
       comments = "#{msg.message.user.name}: #{userComments}"
@@ -50,7 +79,7 @@
       "hours": hours
       "comments": comments

-    redmine.TimeEntry(null).create attributes, (status,data) ->
+    redmine.TimeEntry(null).create attributes, (error, data, status) ->
       if status == 201
         msg.reply "Your time was logged"
       else
@@ -103,14 +132,35 @@
     attributes =
       "notes": "#{msg.message.user.name}: #{note}"

-    redmine.Issue(id).update attributes, (err, data) ->
-      if err?
-        if err == 404
+    redmine.Issue(id).update attributes, (err, data, status) ->
+      unless data?
+        if status == 404
           msg.reply "Issue ##{id} doesn't exist."
         else
           msg.reply "Couldn't update this issue, sorry :("
       else
         msg.reply "Done! Updated ##{id} with \"#{note}\""
+
+  # Robot add issue to "<project>" [traker <id>] with "<subject>"
+  robot.respond /add (?:issue )?(?:\s*to\s*)?(?:"?([^" ]+)"? )(?:tracker\s)?(\d+)?(?:\s*with\s*)("?([^"]+)"?)/i, (msg) ->
+    [project_id, tracker_id, subject] = msg.match[1..3]
+
+    attributes =
+      "project_id": "#{project_id}"
+      "subject": "#{subject}"
+
+    if tracker_id?
+      attributes =
+        "project_id": "#{project_id}"
+        "subject": "#{subject}"
+        "tracker_id": "#{tracker_id}"
+
+    redmine.Issue().add attributes, (err, data, status) ->
+      unless data?
+        if status == 404
+          msg.reply "Couldn't update this issue, #{status} :("
+      else
+        msg.reply "Done! Added issue #{data.id} with \"#{subject}\""

   # Robot assign <issue> to <user> ["note to add with the assignment]
   robot.respond /assign (?:issue )?(?:#)?(\d+) to (\w+)(?: "?([^"]+)"?)?/i, (msg) ->
@@ -131,14 +181,15 @@
       attributes["notes"] = "#{msg.message.user.name}: #{note}" if note?

       # get our issue
-      redmine.Issue(id).update attributes, (err, data) ->
-        if err?
-          if err == 404
+      redmine.Issue(id).update attributes, (err, data, status) ->
+        unless data?
+          if status == 404
             msg.reply "Issue ##{id} doesn't exist."
           else
             msg.reply "There was an error assigning this issue."
         else
           msg.reply "Assigned ##{id} to #{user.firstname}."
+          msg.send '/play trombone' if parseInt(id) == 3631

   # Robot redmine me <issue>
   robot.respond /(?:redmine|show)(?: me)? (?:issue )?(?:#)?(\d+)/i, (msg) ->
@@ -147,8 +198,8 @@
     params =
       "include": "journals"

-    redmine.Issue(id).show params, (err, data) ->
-      unless data?
+    redmine.Issue(id).show params, (err, data, status) ->
+      unless status == 200
         msg.reply "Issue ##{id} doesn't exist."
         return false

@@ -264,7 +315,10 @@
     update: (attributes, callback) =>
       @put "/issues/#{id}.json", {issue: attributes}, callback

-  TimeEntry: (id) ->
+    add: (attributes, callback) =>
+      @post "/issues.json", {issue: attributes}, callback
+
+  TimeEntry: (id = null) ->

     create: (attributes, callback) =>
       @post "/time_entries.json", {time_entry: attributes}, callback
@@ -315,18 +369,18 @@
         switch response.statusCode
           when 200
             try
-              callback null, JSON.parse(data)
+              callback null, JSON.parse(data), response.statusCode
             catch err
-              callback null, data or { }
+              callback null, (data or { }), response.statusCode
           when 401
             throw new Error "401: Authentication failed."
           else
             console.error "Code: #{response.statusCode}"
-            callback response.statusCode, null
+            callback null, null, response.statusCode

       response.on "error", (err) ->
         console.error "Redmine response error: #{err}"
-        callback err, null
+        callback err, null, response.statusCode

     if method in ["POST", "PUT"]
       request.end(body, 'binary')
@@ -335,4 +389,4 @@

     request.on "error", (err) ->
       console.error "Redmine request error: #{err}"
-      callback err, null
+      callback err, null, 0


Updated wording of error message.

Updated wording of error message.

--- src/scripts/weather.coffee
+++ src/scripts/weather.coffee
@@ -11,7 +11,7 @@
       return msg.send err if err

       city = body.getElementsByTagName("city")[0]
-      return msg.send "Sorry, but you didn't specify a location." if not city or not city.getAttribute
+      return msg.send "Sorry, but I couldn't find that location." if not city or not city.getAttribute

       city = city.getAttribute("data")


fix copy.coffee

fix copy.coffee

--- grunt/copy.coffee
+++ grunt/copy.coffee
@@ -1,16 +1,14 @@
 module.exports =
 	server:
 		files: [
-			{
-				expand: true
-				src: ['server/server.pexe', 'server/server.nmf']
-				dest: 'dist/'
-			}
+			expand: true
+			src: ['server/server.pexe', 'server/server.nmf']
+			dest: 'dist/'
 		]
 	img:
 		files: [
-		expand: true
-		flatten: true
-		src: ['img/**/*.svg']
-		dest: 'dist/img/'
+			expand: true
+			flatten: true
+			src: ['img/**/*.svg']
+			dest: 'dist/img/'
 		]


better awareness

better awareness

--- library/membrane/aux.coffee
+++ library/membrane/aux.coffee
@@ -60,6 +60,7 @@
     # This is the place where you would be importing the dependencies.
     prelude: (symbol, context, request, next) ->
         assert auxiliaries = @constructor.aux() or {}
+        assert context.auxiliaries = _.keys auxiliaries
         mapper = (closure) -> _.map auxiliaries, closure
         routines = mapper (value, key) -> (callback) ->
             assert _.isObject singleton = value.obtain()


Revert awareness for now

Revert awareness for now

--- library/membrane/aux.coffee
+++ library/membrane/aux.coffee
@@ -61,8 +61,6 @@
     prelude: (symbol, context, request, next) ->
         assert auxiliaries = @constructor.aux() or {}
         mapper = (closure) -> _.map auxiliaries, closure
-        q = (k) -> symbol: k, qualified: "#{symbol}.#{k}"
-        context.auxiliaries = mapper (v, k, c) -> q k, v
         routines = mapper (value, key) -> (callback) ->
             assert _.isObject singleton = value.obtain()
             assert _.isObject ecc = context.caching ?= {}


correction

correction

--- src/notice/capsule/capsule.coffee
+++ src/notice/capsule/capsule.coffee
@@ -8,8 +8,8 @@
     Testable = class Capsule   # latest Definition available for testing

         #
-        # ./message  - creates one of these
-        # ./notifier - pushes it down the middleware pipeline
+        # ./lifecycle  - creates one of these
+        # ../notifier  - pushes it down the middleware pipeline
         #

         constructor: (params = {}) ->


This bug went unnoticed for a very long time

--- core/modules.coffee
+++ core/modules.coffee
@@ -18,7 +18,7 @@

   shutdown: ()=>
     @beforeUnload() if typeof @beforeUnload is 'function'
-    @engine.unregisterCommands @commands
+    @engine.commands.unregisterCommands @commands

 global.BotModule = BotModule


Add `extend` method to Router

Add `extend` method to Router

This method takes an array of functions, which are passed the Router instance. This is useful for splitting route creation across several files.

--- src/Router.coffee
+++ src/Router.coffee
@@ -40,4 +40,8 @@
       return -> match
     return null

+  extend: (fns) ->
+    fn this for fn in fns
+    return
+
 module.exports = type.build()


removed console statements

removed console statements

--- src/textwrap/helpers/tspan.coffee
+++ src/textwrap/helpers/tspan.coffee
@@ -66,8 +66,6 @@
         textBox = d3.select(vars.container.value.node().lastChild)
     unless textBox.empty()
       words = textBox.text().match(/[^\s-]+-?/g)
-      console.log words
-      console.log "\n"
       ellipsis()

   lineWidth = () ->
@@ -79,7 +77,6 @@
       width

   ellipsis = ->
-    console.log words
     if words and words.length
       lastWord = words.pop()
       lastChar = lastWord.charAt(lastWord.length - 1)


add function to rethink: given a blob sha1 and path, copies blob to a file in that path, then mark that blob as "backup:true".

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -2509,6 +2509,7 @@
         opts = defaults opts,
             uuid       : required
             save_in_db : false  # if true and blob isn't in db and is only in gcloud, copies to local db (for faster access e.g., 20ms versus 5ms -- i.e., not much faster; gcloud is FAST too.)
+            touch      : true
             cb         : required   # cb(err) or cb(undefined, blob_value) or cb(undefined, undefined) in case no such blob
         x = undefined
         blob = undefined
@@ -2546,7 +2547,7 @@
                     cb()
         ], (err) =>
             opts.cb(err, blob)
-            if blob?
+            if blob? and opts.touch
                 # blob was pulled from db or gcloud, so note that it was accessed (updates a counter)
                 @touch_blob(uuid : opts.uuid)
         )
@@ -2595,6 +2596,35 @@
                     cb(); return
                 # successful upload to gcloud -- set x.gcloud
                 @table('blobs').get(opts.uuid).update(gcloud:opts.bucket).run(cb)
+        ], (err) => opts.cb?(err))
+
+    # Write the give blob to the file [path]/[sha1]; if write successful, sets
+    # the backup field in the database to true.
+    backup_blob: (opts) =>
+        opts = defaults opts,
+            uuid  : required  # uuid=sha1-based uuid of blob
+            path  : required  # target path
+            cb    : undefined # cb(err)
+        blob = undefined
+        async.series([
+            (cb) =>
+                @get_blob
+                    uuid  : opts.uuid
+                    touch : false
+                    cb    : (err, x) =>
+                        blob = x
+                        cb(err)
+            (cb) =>
+                if blob?
+                    fs.writeFile(require('path').join(opts.path, opts.uuid), blob, cb)
+                else
+                    cb()
+            (cb) =>
+                if blob?
+                    # successful write to file
+                    @table('blobs').get(opts.uuid).update(backup:true).run(cb)
+                else
+                    cb()
         ], (err) => opts.cb?(err))

     # Copied all blobs that will never expire to a google cloud storage bucket.


Fixed under certain condition, the combined flag can be negative

--- index.cjsx
+++ index.cjsx
@@ -607,7 +607,7 @@
             when '/kcsapi/api_req_hensei/combined' then postBody.api_combined_type
             else @state.combinedFlag
           combinedFlag ?= 0
-          if combinedFlag == 0
+          if combinedFlag <= 0
             sortieInfo = Object.clone window._decks[0].api_ship
             combinedInfo = Object.clone initId
           else


the concurrent_error thing just doesn't work

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -113,7 +113,7 @@
             warning  : 30          # display warning and stop using connection if run takes this many seconds or more
             error    : 10*60       # kill any query that takes this long (and corresponding connection)
             concurrent_warn  : 500  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
-            concurrent_error : 700  # if nonzero, and this many queries at once, any query fails after a slight delay.
+            concurrent_error : 0  # if nonzero, and this many queries at once, any query fails after a slight delay.
             mod_warn : 2           # display MOD_WARN warning in log if any query modifies at least this many docs
             cache_expiry  : 15000  # expire cached queries after this many milliseconds (default: 15s)
             cache_size    : 250    # cache this many queries; use @...query.run({cache:true}, cb) to cache result for a few seconds


fix: synchronize fs methods

fix: synchronize fs methods

--- index.cjsx
+++ index.cjsx
@@ -2,7 +2,7 @@
 Promise = require 'bluebird'
 path = require 'path'
 async = Promise.coroutine
-fs = Promise.promisifyAll require('fs-extra'), { multiArgs: true }
+fs = require('fs-extra')
 request = Promise.promisifyAll require('request'), { multiArgs: true }
 {Traditionalized} = require './traditionalized'
 dbg.extra('subtitlesAudioResponse')
@@ -43,21 +43,21 @@
   i18n['poi-plugin-subtitle-data'].setLocale(locale)
   ___ = i18n["poi-plugin-subtitle-data"].__.bind(i18n["poi-plugin-subtitle-data"])

-loadSubtitles = async (_path) ->
-  [err] = yield fs.ensureFileAsync _path
-  [data] = yield fs.readFileAsync _path
+loadSubtitles = (_path) ->
+  fs.ensureFileSync _path
+  data = fs.readFileSync _path
   data = "{}" if not data or data.length is 0
   JSON.parse data

-loadBackupSubtitles = async (locale) ->
+loadBackupSubtitles = (locale) ->
   abbr = locale[...2]
   if abbr is 'zh'
-    [dataBackup] = yield fs.readFileAsync subtitlesBackupPath['zh-CN']
+    dataBackup = fs.readFileSync subtitlesBackupPath['zh-CN']
     subtitlesBackup = JSON.parse dataBackup
     if not subtitles['zh-CN']?.version or +subtitles['zh-CN']?.version < +subtitlesBackup?.version or not subtitles['zh-TW']?.version
       data = dataBackup
       subtitles['zh-CN'] = subtitlesBackup
-      [err] = yield fs.writeFileAsync subtitlesI18nPath['zh-CN'], data
+      fs.writeFileSync subtitlesI18nPath['zh-CN'], data
       # Convert backup subtitle to zh-TW and save
       subtitles['zh-TW'] = {}
       for shipIdOrSth, value of subtitlesBackup
@@ -66,15 +66,14 @@
         else
           subtitles['zh-TW'][shipIdOrSth] = {} unless subtitles['zh-TW'][shipIdOrSth]
           subtitles['zh-TW'][shipIdOrSth][voiceId] = Traditionalized(text) for voiceId, text of value
-      [err] = yield fs.writeFileAsync subtitlesI18nPath['zh-TW'], JSON.stringify(subtitles['zh-TW'], null, '\t')
-  else
-    [dataBackup] = yield fs.readFileAsync subtitlesBackupPath[locale]
+      fs.writeFileSync subtitlesI18nPath['zh-TW'], JSON.stringify(subtitles['zh-TW'], null, '\t')
+  else
+    dataBackup = fs.readFileSync subtitlesBackupPath[locale]
     subtitlesBackup = JSON.parse dataBackup
     if not subtitles[locale]?.version or +subtitles[locale]?.version < +subtitlesBackup?.version
       data = dataBackup
       subtitles[locale] = subtitlesBackup
-      [err] = yield fs.writeFileAsync subtitlesI18nPath[locale], data
-  console.error err if err
+      fs.writeFileSync subtitlesI18nPath[locale], data
   dataBackup

 getSubtitles = async () ->
@@ -82,9 +81,9 @@
   # Load I18n Data
   for lang in langs
     continue if lang is 'zh-TW'
-    subtitles[lang] = yield loadSubtitles(subtitlesI18nPath[lang])
+    subtitles[lang] = loadSubtitles(subtitlesI18nPath[lang])
     # Load backup subtitle
-    dataBackup[lang] = yield loadBackupSubtitles(lang)
+    dataBackup[lang] = loadBackupSubtitles(lang)
   # Update subtitle data from remote server
   if i18n["poi-plugin-subtitle"].locale not in langs
     locale = 'ja-JP'
@@ -121,16 +120,14 @@
       window.success "#{__('Update Success')}(#{version})",
         stickyFor: 3000
     for lang in langs
-      [err] = yield fs.writeFileAsync subtitlesI18nPath[lang], JSON.stringify(subtitles[lang], null, '\t')
-      throw err if err
+      fs.writeFileSync subtitlesI18nPath[lang], JSON.stringify(subtitles[lang], null, '\t')
   catch e
     if e instanceof Error
       console.error "#{e.name}: #{e.message}\n#{e.stack}"
     else
       console.error e
     if locale isnt 'zh-TW'
-      [err] = yield fs.writeFileAsync subtitlesI18nPath[locale], dataBackup[locale]
-      console.error err if err
+      fs.writeFileSync subtitlesI18nPath[locale], dataBackup[locale]
       subtitles[locale] = JSON.parse dataBackup[locale]
     # window.warn "语音字幕自动更新失败，请联系有关开发人员，并手动更新插件以更新字幕数据",
     #   stickyFor: 5000


storage: don't re-backup a project if we made a backup already and the project hasn't been edited

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -310,8 +310,9 @@
 # make sure everything from 2 years ago (or older) has a backup
 s.backup_projects(database:db, min_age_m:2 * 60*24*365, age_m:1e8, time_since_last_backup_m:1e8, cb:(e)->console.log("DONE",e))

-# make sure everything modified in the last weke has at least one backup made within the last day
-s.backup_projects(database:db, age_m:7*24*60, me_since_last_backup_m:24*60, threads:1, cb:(e)->console.log("DONE",e))
+# make sure everything modified in the last week has at least one backup made within
+# the last day (if it was backed up after last edited, it won't be backed up again)
+s.backup_projects(database:db, age_m:7*24*60, me_since_last_backup_m:7*24, threads:1, cb:(e)->console.log("DONE",e))
 ###
 exports.backup_projects = (opts) ->
     opts = defaults opts,
@@ -329,7 +330,7 @@
                 opts.database.recent_projects
                     age_m     : opts.age_m
                     min_age_m : opts.min_age_m
-                    pluck     : ['last_backup', 'project_id']
+                    pluck     : ['last_backup', 'project_id', 'last_edited']
                     cb        : (err, v) ->
                         if err
                             cb(err)
@@ -337,6 +338,9 @@
                             projects = []
                             cutoff = misc.minutes_ago(opts.time_since_last_backup_m)
                             for x in v
+                                if x.last_backup? and x.last_edited? and x.last_backup >= x.last_edited
+                                    # no need to make another backup, since already have an up to date backup
+                                    continue
                                 if not x.last_backup? or x.last_backup <= cutoff
                                     projects.push(x.project_id)
                             cb()
@@ -527,7 +531,7 @@
         (cb) ->
             dbg('ensure that all backup files are readable by the salvus user (only user on this system)')
             misc_node.execute_code
-                command : 'chown'
+                command : 'chmod'
                 args    : ['a+r', '-R', bup]
                 timeout : 60
                 cb      : cb


Better attr handling in #make

Better attr handling in #make

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -677,12 +677,14 @@
     _id = @_add( _elem )
     @_initCache( _id )
     if _options?
-      if _options.attrs?
-        if _options.attrs instanceof Array
-          for _attr in _options.attrs
+      _attrs = _options.attrs if _options.attrs?
+      _attrs = _options.attr if _options.attr? and not _attr?
+      if _attrs?
+        if _attrs instanceof Array
+          for _attr in _attrs
             @setAttr( _id, _attr[0], _attr[1], true )
         else
-          for _attrName, _attrValue of _options.attrs
+          for _attrName, _attrValue of _attrs
             @setAttr( _id, _attrName, _attrValue, true )
       if _options.styles
         @setStyles( _id, _options.styles )


Allow passing selectize options to simpleFormSelectize

--- vendor/assets/javascripts/simple_form_extension/selectize.coffee
+++ vendor/assets/javascripts/simple_form_extension/selectize.coffee
@@ -1,20 +1,25 @@
 class Selectize
-  constructor: (@$el) ->
+
+  selectizeDefaults: ->
+    mode: if @single then 'single' else 'multi'
+    maxItems: if @single then 1 else @$el.data('max-items')
+    sortField: 'text'
+    plugins: ['remove_button']
+    create: @creatable
+    render: @renderOptions()
+    options: @$el.data('collection')
+
+  constructor: (@$el, @options) ->
     @single = @$el.data('multi') is false
     @el = @$el[0]

-    creatable = @$el.data('creatable')
+    @creatable = @$el.data('creatable')

     @$el.val('')

+    console.log _.defaults @options, @selectizeDefaults()
     @$el.selectize(
-      mode: if @single then 'single' else 'multi'
-      maxItems: if @single then 1 else @$el.data('max-items')
-      sortField: 'text'
-      plugins: ['remove_button']
-      create: creatable
-      render: @renderOptions()
-      options: @$el.data('collection')
+      _.defaults @options, @selectizeDefaults()
     )

     if (value = @$el.data('value'))
@@ -44,11 +49,11 @@
         </div>
       """

-$.fn.simpleFormSelectize = ->
+$.fn.simpleFormSelectize = (options = {}) ->
   @each (i, el) ->
     $select = $(el)
     return if $select.data('simple-form:selectize')
-    instance = new Selectize($select)
+    instance = new Selectize($select, options)
     $select.data('simple-form:selectize', instance)

 onPageReady ->


Switching to multiple arguments rather than an object payload

--- src/assets.coffee
+++ src/assets.coffee
@@ -72,8 +72,5 @@
 		# Apply mutations if they were defined
 		applyAssetMutation: (asset, src) ->
 			if @assetMutator
-			then @assetMutator
-				asset: asset
-				src: src
-				vm: @
+			then @assetMutator asset, src, @
 			else src


check for exercise before validating

check for exercise before validating

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -173,6 +173,7 @@
       questions:[_.extend({}, QuestionStore.getTemplate(), {id: questionId})]

     validate: (id) ->
+      return {valid: false, reason: 'exercise not found'} unless @_local[id]
       _.reduce @_local[id].questions, (memo, question) ->
         validity = QuestionStore.validate(question.id)


Machine: Use slice instead of splice to copy the array.

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -370,7 +370,7 @@

     { targets, asOwner, permanent } = options

-    users = @users.splice 0
+    users = @users.slice 0

     for user in targets
       users = addUser users, { user, asOwner, permanent }
@@ -392,7 +392,7 @@

     { targets, permanent, inform, force } = options

-    users = @users.splice 0
+    users = @users.slice 0

     for user in targets
       users = excludeUser { users, user, permanent, force }


ManagedProvider: instead of full queryString keep only kiteId

If we keep all the query information with in queryString it will cause some
issues in future. For example if we keep version information with in JMachine
and if the klient in that machine updates it self, we will fail to connect to it
But instead we can keep continue using kiteId only which is unique. And wont
change with klient kite updates.

--- workers/social/lib/social/models/computeproviders/managed.coffee
+++ workers/social/lib/social/models/computeproviders/managed.coffee
@@ -25,6 +25,8 @@

   return yes

+getKiteIdOnly = (queryString)->
+  "///////#{queryString.split('/').reverse()[0]}"

 module.exports = class Managed extends ProviderInterface

@@ -43,7 +45,8 @@

     return  unless isValid {queryString, ipAddress}, callback

-    provider = @providerSlug
+    queryString = getKiteIdOnly queryString
+    provider    = @providerSlug

     { guessNextLabel, fetchUserPlan, fetchUsage } = require './computeutils'


controller tests refactoring

controller tests refactoring

--- jsapp/test/runner.coffee
+++ jsapp/test/runner.coffee
@@ -68,6 +68,7 @@
 describe '', () ->
   beforeEach ->
     window.$ = sinon.stub()
+    $.returns sinon.stubObject(jQuery(window))
     $.withArgs(window).returns
       bind: sinon.stub()
       unbind: sinon.stub()


fix(client file paths): output absolute file paths in spa.html instead of relative

--- helpers/path.coffee
+++ helpers/path.coffee
@@ -26,7 +26,7 @@
 	removeLocPartial: (locPaths, partial) ->
 		paths   = {}
 		partial = @format partial
-		partial += '/' unless @hasTrailingSlash partial
+		# partial += '/' unless @hasTrailingSlash partial
 		for own k, v of locPaths
 			paths[k] = []
 			v.forEach (path, i) ->


Add more debug logs

Add more debug logs

--- server/testSystems/Codeforces.coffee
+++ server/testSystems/Codeforces.coffee
@@ -118,6 +118,7 @@
             @ftaa = @randomToken()
             @bfaa = @randomToken()
             tta = @tta()
+            console.log page, csrf
             page = await @download("#{BASE_URL}/enter", {
                 method: 'POST',
                 form: {


Add support for different binning methods, partial fix for issue #16

--- src/ubret/histogram.coffee
+++ src/ubret/histogram.coffee
@@ -5,10 +5,13 @@
   constructor: (selector) ->
     super selector
     @opts.axis2  = 'Count'
-    @binFunc = d3.layout.histogram()
+
+  start: =>
+    @binFunc = if @opts.bins? then d3.layout.histogram(@opts.bins) else d3.layout.histogram()
+    super

   setupData: =>
-    data = _(@opts.data).map (d) => d[@opts.axis1]
+    data = _(@opts.data).pluck @opts.axis1
     @xDomain = d3.extent data
     @graphData = @binFunc data
     @yDomain = [0, d3.max(@graphData, (d) -> d.y)]


Export Point and Range from telepath

Export Point and Range from telepath

--- exports/atom-api.coffee
+++ exports/atom-api.coffee
@@ -1,4 +1,5 @@
 {View, $$, $$$} = require '../src/space-pen-extensions'
+{Point, Range} = require 'telepath'

 module.exports =
   _: require '../src/underscore-extensions'
@@ -11,6 +12,8 @@
   File: require '../src/file'
   fs: require '../src/fs-utils'
   Git: require '../src/git'
+  Point: Point
+  Range: Range
   ScrollView: require '../src/scroll-view'
   Subscriber: require '../src/subscriber'
   View: View


Fix #23 : Don't use http, but '//' for URL scheme

Remove standard HTTP scheme to more generic "//" which will work with HTTP and HTTPS sites.

--- leafs/referrers/client.coffee
+++ leafs/referrers/client.coffee
@@ -89,7 +89,7 @@
 				return '' if not row?

 				row.time_text	= prettyDate(new Date(row.time*1000).toISOString().replace '.000', '')
-				row.favicon		= if blacklist.indexOf(row.referrer.host) is -1 then "http://#{ row.referrer.host }/favicon.ico" else 'assets/img/link.ico'
+				row.favicon		= if blacklist.indexOf(row.referrer.host) is -1 then "//#{ row.referrer.host }/favicon.ico" else 'assets/img/link.ico'

 				"""

@@ -132,7 +132,7 @@

 				return '' if not row?

-				row.favicon = if blacklist.indexOf(row.referrer.host) is -1 then "http://#{ row.referrer.host }/favicon.ico" else ''
+				row.favicon = if blacklist.indexOf(row.referrer.host) is -1 then "//#{ row.referrer.host }/favicon.ico" else ''

 				"""

@@ -161,4 +161,4 @@

-			"""+			"""


Simplified value binder

Simplified value binder

--- src/binder.coffee
+++ src/binder.coffee
@@ -35,16 +35,8 @@
   routine: (el, value) ->
     if el.tagName is 'INPUT' and el.type is 'radio'
       el.setAttribute 'value', value
-    else if window.jQuery?
-      el = jQuery el
-
-      if value?.toString() isnt el.val()?.toString()
-        el.val if value? then value else ''
-    else
-      if el.type is 'select-multiple'
-        o.selected = o.value in value for o in el if value?
-      else if value?.toString() isnt el.value?.toString()
-        el.value = if value? then value else ''
+    else if value?.toString() isnt el.value?.toString()
+      el.value = if value? then value else ''

 binders.if =
   block: true


color fix for task-overview

color fix for task-overview

--- app/assets/javascripts/admin/views/task/task_overview_view.coffee
+++ app/assets/javascripts/admin/views/task/task_overview_view.coffee
@@ -314,7 +314,7 @@
       if @doDrawUser(user)
         userName = user.firstName + " " + user.lastName
         workingHours = user.workingHours
-        color = @colorJet(128 - (workingHours - @chosenMinHours) * (128 / @chosenMaxHours - @chosenMinHours) + 128)
+        color = @colorJet(128 - (workingHours - @chosenMinHours) * (128 / (@chosenMaxHours - @chosenMinHours)) + 128)

         @quoted(userName) + " [id="+ @quoted(user.id) + ", shape=box, fillcolor=" + @quoted(color) + "]"
     )).join(";")


Fix issue with tooltips going way off the circles

--- app/assets/javascripts/angular/directives/neighborhood_map.coffee
+++ app/assets/javascripts/angular/directives/neighborhood_map.coffee
@@ -29,9 +29,8 @@
       $scope.neighborhood.map.data.addListener('click', (e) ->
         if e.feature.getGeometry().getType() == 'Point'
           $scope.mapInfoWindow.setPosition(e.feature.getGeometry().get());
-          $scope.mapInfoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
-
-        if e.feature.getGeometry().getType() == 'Polygon'
+          $scope.mapInfoWindow.setOptions({pixelOffset: new google.maps.Size(0, 0)});
+        else if e.feature.getGeometry().getType() == 'Polygon'
           $scope.mapInfoWindow.setPosition(getPolygonCenter(e.feature.getGeometry()));
           $scope.mapInfoWindow.setOptions({pixelOffset: new google.maps.Size(0, 0)});


Tests bugfix.

Tests bugfix.

--- tests.coffee
+++ tests.coffee
@@ -62,7 +62,7 @@

   end = new Date().valueOf()

-  console.log "Building package time (offline #{ offline }): #{ (end - start) / 1000 } s"
+  console.log "Building package time: #{ (end - start) / 1000 } s"

   test.equal pkg.sourceRoot, packageDirectory
   test.equal pkg.packageName, 'acme:foobar'


Fix failing test in Safari

Fix failing test in Safari

--- test/src/system/text_formatting_test.coffee
+++ test/src/system/text_formatting_test.coffee
@@ -22,13 +22,13 @@
             done()

 editorTest "inserting a link", (expectDocument) ->
-  typeCharacters "a ", ->
+  typeCharacters "a", ->
     clickToolbarButton attribute: "href", ->
       ok isToolbarDialogActive(attribute: "href")
       typeInToolbarDialog "http://example.com", attribute: "href", ->
-        expectAttributes([0, 2], {})
-        expectAttributes([2, 20], href: "http://example.com")
-        expectDocument("a http://example.com\n")
+        expectAttributes([0, 1], {})
+        expectAttributes([1, 19], href: "http://example.com")
+        expectDocument("ahttp://example.com\n")

 editorTest "editing a link", (done) ->
   insertString("a")


uodate password success triggers toast

uodate password success triggers toast

--- app/components/profile-settings-page/profile-settings-page.coffee
+++ app/components/profile-settings-page/profile-settings-page.coffee
@@ -38,13 +38,12 @@
       Dialog.custom
         scope: $scope
         template: require('./change-password-dialog.tmpl.html')
-        controller: ($mdDialog, $scope) ->
+        controller: ($mdDialog, $scope, Toast) ->
           $scope.formParams = {}
           $scope.savePassword = ->
             Records.users.updatePassword($scope.formParams)
               .then (res, status) ->
-                console.log('res: ', res)
-                console.log('status: ', status)
+                Toast.show('Your new password was saved')
               .catch (err, status) ->
                 console.log('err: ', err)
                 console.log('status: ', status)


SSL cert issue if fixed on wallet.dogeparty.io—no hack needed here anymore

--- src/services/dogeparty.coffee
+++ src/services/dogeparty.coffee
@@ -1,7 +1,4 @@
 shared = require("./_shared")
-
-# todo: find a better way
-process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'

 dogeparty = (addr) -> shared.party(addr, [["XDP", "https://wallet.dogeparty.io/_api"], ["XDPTEST", "http://testnet.wallet.dogeparty.io/_t_api"]])


add submitted to litePaper

add submitted to litePaper

--- app/assets/javascripts/models/lite_paper.js.coffee
+++ app/assets/javascripts/models/lite_paper.js.coffee
@@ -4,6 +4,7 @@
   title: a('string')
   shortTitle: a('string')
   flow: DS.belongsTo('flow')
+  submitted: a('boolean')
   displayTitle: (->
     @get('title') || @get('shortTitle')
   ).property 'title', 'shortTitle'


CHG: frontend entry file exports

CHG: frontend entry file exports

--- frontend/src/tao/index.coffee
+++ frontend/src/tao/index.coffee
@@ -7,12 +7,9 @@
 import helpers from './helpers'

 export default {
-  Module
-  Component
-  Application
-  Page
-  AttributeManager
-  helpers
+  Module, Component, Application, Page, AttributeManager, helpers
 }

-export { Module, Component, Application, Page, AttributeManager, helpers }
+export {
+  Module, Component, Application, Page, AttributeManager, helpers
+}


change confirm_close default

change confirm_close default

--- src/smc-util/db-schema.coffee
+++ src/smc-util/db-schema.coffee
@@ -232,7 +232,7 @@
                     theme                     : "default"
                     undo_depth                : 300
                 other_settings  :
-                    confirm_close     : false
+                    confirm_close     : true
                     mask_files        : true
                     page_size         : 50
                     standby_timeout_m : 10


More robust un-hover

More robust un-hover

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -57,7 +57,7 @@
   constructor: ->
     super
     @$el.on 'mouseleave', => @_removeClass 'fl-button-state-hovered'
-    $(document).on 'mouseenter', => @_removeClass 'fl-button-state-hovered'
+    $(document).on 'mousemove', => @_removeClass 'fl-button-state-hovered'

   _textContainer: ($el) ->
     for el in $el.parents()


Remove been-active class; this was a workaround to avoid nondeterministic styling transitions during load, but we inline all the styles now.

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -22,7 +22,7 @@
     console.log @el.className
     #TODO:what's this line do?
     #FactlinkJailRoot.$factlinkCoreContainer.find('div.fl-button').removeClass('active')
-    @$el.addClass 'active been-active'
+    @$el.addClass 'active'
     @frame.$el.addClass 'factlink-frame-visible'
     console.log @el.className


Added some comments

Added some comments

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -3,6 +3,8 @@
 class RobustHover
   constructor: (@$el, @_callbacks) ->
     @_hovered = false
+
+    # Use mousemove instead of mouseenter since it is more reliable
     @$el.on 'mousemove', @_onMouseEnter
     @$el.on 'mouseleave', @_onMouseLeave

@@ -14,15 +16,17 @@
   _onMouseEnter: =>
     return if @_hovered

+    # Mouse leaves when *external* document registers mousemove
+    $(document).on 'mousemove', @_onMouseLeave
+
     @_hovered = true
-    $(document).on 'mousemove', @_onMouseLeave
     @_callbacks.mouseenter?()

   _onMouseLeave: =>
     return unless @_hovered

+    $(document).off 'mousemove', @_onMouseLeave
     @_hovered = false
-    $(document).off 'mousemove', @_onMouseLeave
     @_callbacks.mouseleave?()

 class Button


Fix typo.

Fix typo.

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -3,7 +3,7 @@
 {Prelude} = require './wrapper'

 addIncludes = (options, main, callback) ->
-  async.map ([k,v] for k,v of options.includes), ([requireAs, absolutePath], callback) ->
+  async.map ([k,v] for k,v of options.include), ([requireAs, absolutePath], _callback) ->
     mod = new Module absolutePath,
       absolutePath: absolutePath
       basePath: main.basePath
@@ -12,7 +12,7 @@
     mod.parse paths: options.paths, (err) =>
       return callback err if err?
       main.dependencies[requireAs] = mod
-      callback()
+      _callback()

   , (err) ->
     throw err if err


Test that service details include the image install id

Signed-off-by: Tim Perry <5ee0edb9e2229c0838f1959779f1949031de0123@resin.io>

--- tests/integration/models/device.spec.coffee
+++ tests/integration/models/device.spec.coffee
@@ -936,6 +936,10 @@
 								installs__service: dbService.id
 								device: device.id
 					]
+				.spread (oldWebInstall, newWebInstall, dbInstall) =>
+					@oldWebInstall = oldWebInstall
+					@newWebInstall = newWebInstall
+					@dbInstall = dbInstall

 		describe 'resin.models.device.getWithServiceDetails()', ->

@@ -955,15 +959,18 @@
 						current_release: @currentRelease.commit
 						current_services:
 							web: [
+								id: @newWebInstall.id
 								commit: 'new-release-commit'
 								status: 'downloading'
 								download_progress: 50
 							,
+								id: @oldWebInstall.id
 								commit: 'old-release-commit'
 								status: 'running'
 								download_progress: 100
 							]
 							db: [
+								id: @dbInstall.id
 								commit: 'new-release-commit'
 								status: 'running'
 								download_progress: 100


pricing: define VM coupon quotas

pricing: define VM coupon quotas

--- workers/social/lib/social/models/payment/subscription.coffee
+++ workers/social/lib/social/models/payment/subscription.coffee
@@ -175,8 +175,10 @@
     @checkQuota {@usage, @couponCode, spend, multiplyFactor}, callback

   vmCouponQuota =
-    "1freevm"   : 1
-    "2freevms"  : 2
+    "1FREEVM"   : 1
+    "2FREEVMS"  : 2
+    "3FREEVMS"  : 3
+    "4FREEVMS"  : 4

   checkQuota: (options, callback) ->
     {usage, spend, couponCode, multiplyFactor} = options


minor

minor

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -158,7 +158,7 @@

       Status
-      Api
+      API
       Blog
       About


Groups: converted some events to forwardEvent

--- client/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/app/Applications/Groups.kdapplication/AppController.coffee
@@ -62,11 +62,9 @@
       serviceType : 'group'
       group       : group.slug
       isExclusive : yes
-    @groupChannel.on "MemberJoinedGroup", (member)=>
-      @emit "MemberJoinedGroup", member
-
-    @groupChannel.on "FollowHappened", (params)=>
-      @emit "FollowHappened", params
+
+    @forwardEvent @groupChannel, "MemberJoinedGroup"
+    @forwardEvent @groupChannel, "FollowHappened"

     @groupChannel.once 'setSecretNames', callback


No need this.

No need this.

--- client/app/Applications/Topics.kdapplication/AppController.coffee
+++ client/app/Applications/Topics.kdapplication/AppController.coffee
@@ -8,7 +8,6 @@
     ,options
     super options,data
     @listItemClass = TopicsListItemView
-    @_searchValue = ""
     @controllers = {}

   bringToFront:()->


Raising timeouts to avoid dumb async erros

--- tests/tests/acceptance/fs-operations.coffee
+++ tests/tests/acceptance/fs-operations.coffee
@@ -204,13 +204,13 @@

     # editing
     backup = fs.readFileSync files_path.jade
-    new setTimeout (-> fs.appendFileSync files_path.jade, ' ' ), 500
+    new setTimeout (-> fs.appendFileSync files_path.jade, ' ' ), 800

     # deleting
-    new setTimeout (-> fs.unlinkSync files_path.jade ), 1000
+    new setTimeout (-> fs.unlinkSync files_path.jade ), 1600

     # crating
-    new setTimeout (-> fs.writeFileSync files_path.jade, backup ), 1500
+    new setTimeout (-> fs.writeFileSync files_path.jade, backup ), 2400

   it 'editing, deleting and creating style partial', (done)->
@@ -260,13 +260,13 @@

     # editing
     backup = fs.readFileSync files_path.styl
-    new setTimeout (-> fs.appendFileSync files_path.styl, ' ' ), 500
+    new setTimeout (-> fs.appendFileSync files_path.styl, ' ' ), 800

     # deleting
-    new setTimeout (-> fs.unlinkSync files_path.styl ), 1000
+    new setTimeout (-> fs.unlinkSync files_path.styl ), 1600

     # crating
-    new setTimeout (-> fs.writeFileSync files_path.styl, backup ), 1500
+    new setTimeout (-> fs.writeFileSync files_path.styl, backup ), 2400

   it 'creating script with inexistent require', (done)->


correct is_intrinsic usage - the % is needed

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -1289,7 +1289,7 @@
                 if is_intrinsic(n.expression, "%setSlot")
                         n.expression.arguments[2] = intrinsic getLocal_id, [iife_rv_id]
                         replacement.body.push n
-                else if is_intrinsic(n.expression, "%setGlobal") or is_intrinsic(n.expression, "setLocal".expression)
+                else if is_intrinsic(n.expression, "%setGlobal") or is_intrinsic(n.expression, "%setLocal")
                         n.expression.arguments[1] = intrinsic getLocal_id, [iife_rv_id]
                         replacement.body.push n

@@ -1313,7 +1313,7 @@
                 if is_intrinsic(n.expression, "%setSlot")
                         n.expression.arguments[2] = intrinsic getLocal_id, [iife_rv_id]
                         replacement.body.push n
-                else if is_intrinsic(n.expression, "%setGlobal") or is_intrinsic(n.expression, "setLocal")
+                else if is_intrinsic(n.expression, "%setGlobal") or is_intrinsic(n.expression, "%setLocal")
                         n.expression.arguments[1] = intrinsic getLocal_id, [iife_rv_id]
                         replacement.body.push n


Change tab length in GFM.

Change tab length in GFM.

Signed-off-by: Jason Lynch <68c46a606457643eab92053c1c05574abb26f861@calindora.com>

--- topics/atom.symlink/config.cson
+++ topics/atom.symlink/config.cson
@@ -23,6 +23,9 @@
   "tree-view":
     alwaysOpenExisting: true
   whitespace: {}
+".gfm.source":
+  editor:
+    tabLength: 8
 ".plain.text":
   editor:
     tabLength: 8


Set size, weight and lineHeight defaults in the define getters instead of separate conditionals in constructor

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -33,15 +33,6 @@
 			# Else: macOS
 			else
 				@_setStyle("-apple-system, SF UI Text, Helvetica Neue", @fontSize, @fontWeight, @lineHeight)
-
-		if not @fontSize
-			@_setStyle(@fontFamily, 40, @fontWeight, @lineHeight)
-
-		if not @fontWeight
-			@_setStyle(@fontFamily, @fontSize, 400, @lineHeight)
-
-		if not @lineHeight
-			@_setStyle(@fontFamily, @fontSize, @fontWeight, 1.25)

 		# Reset width and height
 		@_setSize(@autoWidth, @autoHeight)
@@ -91,15 +82,15 @@
 		set: (value) -> @_setStyle(value, @fontSize, @fontWeight, @lineHeight, @letterSpacing, @textAlign, @textTransform)

 	@define "fontSize",
-		get: -> @style.fontSize
+		get: -> @style.fontSize or 40
 		set: (value) -> @_setStyle(@fontFamily, value, @fontWeight, @lineHeight, @letterSpacing, @textAlign, @textTransform)

 	@define "fontWeight",
-		get: -> @style.fontWeight
+		get: -> @style.fontWeight or 400
 		set: (value) -> @_setStyle(@fontFamily, @fontSize, value, @lineHeight, @letterSpacing, @textAlign, @textTransform)

 	@define "lineHeight",
-		get: -> @style.lineHeight
+		get: -> @style.lineHeight or 1.25
 		set: (value) -> @_setStyle(@fontFamily, @fontSize, @fontWeight, value, @letterSpacing, @textAlign, @textTransform)

 	@define "letterSpacing",


Display error page

Display error page

--- js/error.coffee
+++ js/error.coffee
@@ -0,0 +1,8 @@
+$ ->
+  # Set the title
+  document.title = "Circle - " + renderContext.title
+
+  # Display page
+  $("body").attr("id","error").html HAML['header'](renderContext)
+  $("body").append HAML['error'](renderContext)
+  $("body").append HAML['footer'](renderContext)


feat: ship tag update

feat: ship tag update


Avoid NPE when no subpage

Avoid NPE when no subpage

--- js/inner.coffee
+++ js/inner.coffee
@@ -448,9 +448,8 @@

   loadAdminPage: (cx, subpage) =>
-    subpage = subpage.replace('/', '')
-
     if subpage
+      subpage = subpage.replace('/', '')
       $.getJSON "/api/v1/admin/#{subpage}", (data) =>
         @admin(data)
     @renderAdminPage subpage


no need for date string in rollbar env param

--- builders/Builder.coffee
+++ builders/Builder.coffee
@@ -236,4 +236,4 @@

   getEnvForRollbar: ->
     version = @config.client.version
-    return if version == "0.0.1" then "dev" else version+Date.now()
+    return if version == "0.0.1" then "dev" else version


new pusher key

new pusher key

--- js/libs/pusher.coffee
+++ js/libs/pusher.coffee
@@ -1,7 +1,7 @@
 CI.Pusher = class Pusher
   constructor: (@login) ->
     key = switch window.renderContext.env
-      when "production" then "6465e45f8c4a30a2a653"
+      when "production" then "961a1a02aa390c0a446d"
       else "3f8cb51e8a23a178f974"

     @pusher = new window.Pusher(key, { encrypted: true})


update atom packages

update atom packages

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -12,6 +12,9 @@
   "busy-signal"
   "editorconfig"
   "file-icons"
+  "go-debug"
+  "go-plus"
+  "go-signature-statusbar"
   "highlight-selected"
   "intentions"
   "language-lua"
@@ -44,10 +47,11 @@
   "rubocop-auto-correct"
   "ruby-block"
   "ruby-test-switcher"
+  "sort-lines"
   "Sublime-Style-Column-Selection"
   "table-editor"
   "theme-switcher"
   "travis-ci-status"
   "wordcount"
   "Zen"
-]+]


Added some atom packages

Added some atom packages

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -28,6 +28,7 @@
   "go-debug"
   "go-get"
   "go-plus"
+  "go-signature-statusbar"
   "godoc"
   "gofmt"
   "gometalinter-linter"
@@ -65,6 +66,7 @@
   "oceanic-next"
   "package-sync"
   "pigments"
+  "prettier-atom"
   "react"
   "seti-syntax"
   "seti-ui"


Fix members and channels loader

Fix members and channels loader

--- src/client.coffee
+++ src/client.coffee
@@ -11,7 +11,6 @@

 apiPrefix = '/api/v4'
 usersRoute = '/users'
-teamsRoute = '/teams'
 messageMaxRunes = 4000

 tlsverify = !(process.env.MATTERMOST_TLS_VERIFY or '').match(/^false|0|no|off$/i)
@@ -89,11 +88,13 @@
         if data && not data.error
             @preferences = data
             @emit 'preferencesLoaded', { preferences: @preferences }
+            @logger.debug 'Loaded Preferences...'

     _onMe: (data, headers) =>
         if data && not data.error
             @me = data
             @emit 'meLoaded', { me: @me }
+            @logger.debug 'Loaded Me...'

     _onTeams: (data, headers) =>
         if data && not data.error
@@ -113,7 +114,7 @@
         @teamRoute() + '/channels/' + channelId

     teamRoute: ->
-        teamsRoute + '/' + @teamID
+        usersRoute + '/me/teams/' + @teamID

     getMe: ->
         @_apiCall 'GET', usersRoute + '/me', null, @_onMe
@@ -129,7 +130,7 @@

     loadUsersList: ->
         # Load userlist
-        @_apiCall 'GET', @teamRoute() + '/users/0/1000', null, @_onProfiles
+        @_apiCall 'GET', @teamRoute() + '/members', null, @_onProfiles
         @_apiCall 'GET', @channelRoute(''), null, @_onChannels


Properly use member limit argument

Properly use member limit argument

--- src/client.coffee
+++ src/client.coffee
@@ -234,7 +234,7 @@
             callback(data)

     getChannelInfo: (channelID, memberLimit, callback) ->
-        @_apiCall 'GET', '/channels/' + channelID + '/extra_info/2', null, (data, header) =>
+        @_apiCall 'GET', '/channels/' + channelID + '/extra_info/' + memberLimit, null, (data, header) =>
             callback(data)

     getAllChannels: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -46,6 +46,12 @@
     parsedColor[3] ||= 1.0

     return hslToRgb(parsedColor)
+
+  shiftLightness = (amount) ->
+    hsl = @toHsl()
+    hsl[2] += amount
+
+    return Color2(hslToRgb(hsl))

   hslToRgb = (hsl) ->
     [h, s, l, a] = (parseFloat(channel) for channel in hsl)


Save Config to filesystem after adding a plugin.

--- src/config.coffee
+++ src/config.coffee
@@ -64,6 +64,7 @@
         name: plugin_name
         opts: opts

+      @save()
       @config[hostname]

   getPlugin: (hostname, plugin_name) ->