issue #1463: check assignee field to prevent null err

--- src/scripts/jira-issues.coffee
+++ src/scripts/jira-issues.coffee
@@ -67,7 +67,9 @@
                   message = "[" + key + "] " + json.fields.summary
                   message += '\nStatus: '+json.fields.status.name

-                  if ('value' of json.fields.assignee or 'displayName' of json.fields.assignee)
+                  if (json.fields.assignee == null)
+                    message += ', unassigned'
+                  else if ('value' of json.fields.assignee or 'displayName' of json.fields.assignee)
                     if (json.fields.assignee.name == "assignee" and json.fields.assignee.value.displayName)
                       message += ', assigned to ' + json.fields.assignee.value.displayName
                     else if (json.fields.assignee and json.fields.assignee.displayName)


_version was conflicting with superclass property

--- src/search-subscription.coffee
+++ src/search-subscription.coffee
@@ -11,7 +11,7 @@
   constructor: (@_terms, @_accountIds) ->
     super(null, {asResultSet: true})

-    @_version = 0
+    @_termsVersion = 0
     _.defer => @retrievePage(0)

   terms: =>
@@ -19,7 +19,7 @@

   setTerms: (terms) =>
     @_terms = terms
-    @_version += 1
+    @_termsVersion += 1
     @retrievePage(0)

   replaceRange: (range) =>
@@ -28,7 +28,8 @@
   # Accessing Data

   retrievePage: (idx) =>
-    version = @_version += 1
+    termsVersion = @_termsVersion += 1
+    resultCount = 0
     resultIds = []

     @_accountIds.forEach (aid) =>
@@ -39,10 +40,14 @@
         json: true
         returnsModel: true
       .then (threads) =>
-        return unless @_version is version
+        return unless @_termsVersion is termsVersion
+        resultCount += 1
+        resultIds = resultIds.concat _.pluck(threads, 'id')

-        resultIds = resultIds.concat _.pluck(threads, 'id')
-        query = DatabaseStore.findAll(Thread).where(id: resultIds).order(Thread.attributes.lastMessageReceivedTimestamp.descending())
-        @replaceQuery(query)
+        # Don't emit a "result" until we have at least one thread to display.
+        # Otherwise it will show "No Results Found"
+        if resultIds.length > 0 or resultCount is @_accountIds.length
+          query = DatabaseStore.findAll(Thread).where(id: resultIds).order(Thread.attributes.lastMessageReceivedTimestamp.descending())
+          @replaceQuery(query)

 module.exports = SearchSubscription


webapp/project upgrades: typo

webapp/project upgrades: typo

--- src/smc-webapp/r_upgrades.cjsx
+++ src/smc-webapp/r_upgrades.cjsx
@@ -195,7 +195,7 @@
         <UpgradeAdjustor
             key                                  = {"adjustor-#{project_id}"}
             project_id                           = {project_id}
-            total_project_quotas                 = {@props.get_total_project_quotas(id) }
+            total_project_quotas                 = {@props.get_total_project_quotas(project_id) }
             upgrades_you_can_use                 = {@props.get_total_upgrades()}
             upgrades_you_applied_to_all_projects = {@props.get_total_upgrades_you_have_applied()}
             upgrades_you_applied_to_this_project = {@props.get_upgrades_you_applied_to_project(project_id)}


gcloud: tail the show_console.

gcloud: tail the show_console.

--- salvus/smc_gcloud.coffee
+++ salvus/smc_gcloud.coffee
@@ -223,6 +223,9 @@
                 if err
                     console.log("ERROR -- ", err)
                 else
+                    n = output.length
+                    if n > 15000
+                        output = output.slice(n - 15000)
                     console.log(output)

     # DIFFICULT change configuration of this VM
@@ -664,7 +667,7 @@
         if opts.size_GB? and opts.size_GB < 10
             opts.cb?("size_GB must be at least 10")
             return
-
+
         dbg("starting...")
         config = {}
         if opts.snapshot?


syncstring: improve logging and fix so that poll interval reacts to existence of updates

--- salvus/syncstring.coffee
+++ salvus/syncstring.coffee
@@ -359,7 +359,7 @@

     poll_for_updates: (interval=INIT_POLL_INTERVAL) =>
         retry = (interval) =>
-            next_interval = Math.max(INIT_POLL_INTERVAL,Math.min(MAX_POLL_INTERVAL, POLL_DECAY_RATIO*interval))
+            next_interval = Math.max(INIT_POLL_INTERVAL, Math.min(MAX_POLL_INTERVAL, POLL_DECAY_RATIO*interval))
             @dbg("poll_for_updates", "waiting #{next_interval}ms...")
             setTimeout((()=>@poll_for_updates(next_interval)), interval)

@@ -432,7 +432,7 @@
                 if err
                     cb(err)
                 else
-                    new_updates = @_process_updates updates, (err) =>
+                    @_process_updates updates, (err, new_updates) =>
                         # ignore err, since it would be in writing back
                         cb(undefined, new_updates)

@@ -467,12 +467,13 @@
                     new_patches[update.string_id].push(update)

         if misc.len(new_patches) == 0
-            # nothing further to do
-            cb()
-            return false
+            @dbg("_process_updates", "no new patches, so nothing further to do")
+            cb(undefined, false)
+            return

         #if updates.length > 0
         #    @dbg("_process_updates",misc.to_json(new_patches))
+        @dbg("_process_updates", "#{misc.len(new_patches)} new patches")

         # There are new patches
         write_updates = false
@@ -557,12 +558,13 @@

         if write_updates
             #@dbg("_process_updates","writing our own updates back")
-            @_write_updates_to_db(cb)  # safe to call skipping lock, since we have the lock
+            # safe to call skipping lock, since we have the lock
+            @_write_updates_to_db (err) =>
+                cb(err, true)
         else
             #@dbg("_process_updates","no further updates from us (stable)")
-            cb()
-
-        return true  # there were patches to apply
+            cb(undefined, true)
+

     _squash_patches: (opts) =>
         opts = defaults opts,


syncstrings: better logging and further relax polling.

--- salvus/syncstring.coffee
+++ salvus/syncstring.coffee
@@ -267,7 +267,7 @@
 # Times below are in milliseconds.

 # Polling parameters:
-INIT_POLL_INTERVAL     = 3000
+INIT_POLL_INTERVAL     = 5000
 MAX_POLL_INTERVAL      = 30000   # TODO: for testing -- for deploy make longer!
 POLL_DECAY_RATIO       = 1.3

@@ -285,7 +285,9 @@
 # having to apply hundreds of patches when opening a string, and saves
 # space.   (Of course, having the complete history could also be
 # interesting...)
-DB_PATCH_SQUASH_THRESH = 100
+#DB_PATCH_SQUASH_THRESH = 100
+# basically disable for now until we more carefully debug this.
+DB_PATCH_SQUASH_THRESH = 1000000000

 # emits a 'change' event whenever live is changed as a result of syncing with the database
 class StringsDBString extends EventEmitter
@@ -356,6 +358,7 @@
     poll_for_updates: (interval=INIT_POLL_INTERVAL) =>
         retry = (interval) =>
             next_interval = Math.max(INIT_POLL_INTERVAL,Math.min(MAX_POLL_INTERVAL, POLL_DECAY_RATIO*interval))
+            @dbg("poll_for_updates", "waiting #{next_interval}ms...")
             setTimeout((()=>@poll_for_updates(next_interval)), interval)

         if misc.len(@strings) == 0
@@ -412,7 +415,7 @@
         @_call_with_lock(((cb)=>@_read_updates_from_db(string_ids, age, cb)), cb)

     _read_updates_from_db: (string_ids, age, cb) =>
-        #@dbg("_read_updates_from_db", misc.to_json(string_ids))
+        @dbg("_read_updates_from_db", misc.to_json(string_ids))
         if not @db?
             cb("database not initialized"); return
         where = {string_id:{'in':string_ids}}
@@ -445,6 +448,7 @@
         # will later implement things to make faster.  If this database sync thing
         # turns out to be sensible.
         #  SIMPLIFIED: ignore is_first and never trim.
+        @dbg("_process_updates", "process #{updates.length} updates")
         new_patches = {}
         for update in updates
             update.timestamp = update.timestamp - 0  # better to key map based on string of timestamp as number


remove debug statement

remove debug statement

--- app/classifier/tasks/combo/index.cjsx
+++ app/classifier/tasks/combo/index.cjsx
@@ -88,7 +88,6 @@
                   props.onChange Object.assign({}, props.annotation, { value: allComboAnnotations })
               if props.workflow.tasks[props.annotation?.task]?.type is 'combo'
                 idx = allComboTypes.lastIndexOf(taskType)
-                console.log(allComboAnnotations, allComboTypes, idx)
                 if idx > -1
                   # if the current annotation is for the combo task pass in the `inner` annotations
                   fauxAnnotation = allComboAnnotations[idx]


Add empty return on each backlog controller.

--- app/coffee/controllers/backlog.coffee
+++ app/coffee/controllers/backlog.coffee
@@ -45,6 +45,8 @@
         $scope.$emit("stats:update")
         $data.loadUsersAndRoles($scope)

+    return
+

 BacklogUserStoriesController = ($scope, $rootScope, $q, rs, $data, $modal) ->
     calculateStats = ->
@@ -306,6 +308,8 @@

     $scope.$on "select2:changed", (ctx, value) ->
         $scope.form.tags = value
+
+    return

 BacklogBulkUserStoriesModalController = ($scope, $rootScope, $gmOverlay, rs, $gmFlash, $i18next) ->
     $scope.bulkFormOpened = false
@@ -357,6 +361,9 @@

     $scope.$on "select2:changed", (ctx, value) ->
         $scope.form.tags = value
+
+    return
+

 BacklogMilestonesController = ($scope, $rootScope, rs, $gmFlash, $i18next) ->
     # Local scope variables
@@ -405,6 +412,8 @@
             calculateStats()
             $rootScope.$broadcast("milestones:loaded", $scope.milestones)

+    return
+

 BacklogMilestoneController = ($scope, rs, $gmFlash, $i18next) ->
     calculateTotalPoints = (us) ->
@@ -463,6 +472,8 @@

     calculateStats()
     $scope.$on("sortable:changed", normalizeMilestones)
+
+    return

 module = angular.module("greenmine.controllers.backlog", [])


Fix github issue #63: Milestones renamed to sprints in permissions interface

--- app/coffee/modules/admin/roles.coffee
+++ app/coffee/modules/admin/roles.coffee
@@ -241,12 +241,12 @@
             categories = []

             milestonePermissions = [
-                { key: "view_milestones", description: "View milestones" }
-                { key: "add_milestone", description: "Add milestone" }
-                { key: "modify_milestone", description: "Modify milestone" }
-                { key: "delete_milestone", description: "Delete milestone" }
-            ]
-            categories.push({ name: "Milestones", permissions: setActivePermissions(milestonePermissions) })
+                { key: "view_milestones", description: "View sprints" }
+                { key: "add_milestone", description: "Add sprint" }
+                { key: "modify_milestone", description: "Modify sprint" }
+                { key: "delete_milestone", description: "Delete sprint" }
+            ]
+            categories.push({ name: "Sprints", permissions: setActivePermissions(milestonePermissions) })

             userStoryPermissions = [
                 { key: "view_us", description: "View user story" }


[backport] remove repeated api calls

[backport] remove repeated api calls

--- app/coffee/modules/issues/list.coffee
+++ app/coffee/modules/issues/list.coffee
@@ -168,7 +168,7 @@
             for obj in value
                 obj.selected = if isSelected(obj.type, obj.id) then true else undefined

-    loadFilters: ->
+    loadFilters: () ->
         urlfilters = @.getUrlFilters()

         if urlfilters.q
@@ -561,8 +561,8 @@
             html = $compile(html)($scope)
             $el.find(".filter-list").html(html)

-        selectQFilter = debounceLeading 100, (value) ->
-            return if value is undefined
+        selectQFilter = debounceLeading 100, (value, oldValue) ->
+            return if value is undefined or  value == oldValue

             $ctrl.replaceFilter("page", null, true)

@@ -575,7 +575,10 @@

             reloadIssues()

-        $scope.$watch("filtersQ", selectQFilter)
+        unwatchIssues = $scope.$watch "issues", (newValue) ->
+            if !_.isUndefined(newValue)
+                $scope.$watch("filtersQ", selectQFilter)
+                unwatchIssues()

         # Dom Event Handlers
         $el.on "click", ".filters-cats > ul > li > a", (event) ->


fix(kanban): zoom level refresh card

fix(kanban): zoom level refresh card

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -709,9 +709,10 @@
             $scope.$watch 'item', onChange
             # ignore the first watch because is the same as item
             $scope.$watch 'zoomLevel', () =>
-                if !initializeZoom
+                if initializeZoom
+                    onChange()
+                else
                     initializeZoom = true
-                    onChange()

             $scope.$on "$destroy", ->
                 $el.off()
@@ -776,9 +777,10 @@
             $scope.$watch 'item', onChange
             # ignore the first watch because is the same as item
             $scope.$watch 'zoomLevel', () =>
-                if !initializeZoom
+                if initializeZoom
+                    onChange()
+                else
                     initializeZoom = true
-                    onChange()

             $scope.$on "$destroy", ->
                 $el.off()


search box should specify launch_approved

--- app/components/project-card-list.cjsx
+++ app/components/project-card-list.cjsx
@@ -111,9 +111,12 @@
           window.location.href = ['/projects', project.slug].join('/')

   searchByName: (value, callback) ->
-
-    if value?.trim().length > 0
-      apiClient.type('projects').get(search: "#{value}", page_size: 10)
+    query =
+      search: "%#{value}%"
+      launch_approved: true unless apiClient.params.admin
+
+    if value?.trim().length > 3
+      apiClient.type('projects').get(query, page_size: 10)
         .then (projects) ->
           opts = projects.map (project) ->
             {
@@ -137,7 +140,7 @@
       value=""
       searchPromptText="Search by name"
       closeAfterClick={true}
-      asyncOptions={debounce(@searchByName, 500)}
+      asyncOptions={debounce(@searchByName, 2000)}
       onChange={@navigateToProject}
       className="search card-search standard-input"
     />
@@ -275,12 +278,12 @@

<Translate pageStart={pageStart} pageEnd={pageEnd} count={@state.project_count} content={showingMessage} />
}
         {if @state.pages>1
            <PageSelector current={@props.page} total={@state.pages} onChange={@handlePageChange} />}
-
+
         <ProjectCardList projects={@state.projects} />

         {if @state.pages>1
            <PageSelector current={@props.page} total={@state.pages} onChange={@handlePageChange} />}
-
+


incorrect variable

incorrect variable

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -70,7 +70,7 @@
       @classification.bind 'change', @onClassificationChange
       @classification.bind 'add-species', @onClassificationAddSpecies
       if ExperimentServer.currentCohort?
-        @classification.metadata.cohort = cohort
+        @classification.metadata.cohort = ExperimentServer.currentCohort
       @html template @classification
       @active = Math.floor @classification.subject.location.standard.length / 2
       @activate @active


Fix slider enemies in tunnel

Fix slider enemies in tunnel

--- app/scripts/lazerscripts/lunch.coffee
+++ app/scripts/lazerscripts/lunch.coffee
@@ -81,13 +81,13 @@
           options:
             grid: new Game.LocationGrid
               x:
-                start: 680
+                start: 1.2
                 steps: 4
-                stepSize: 40
+                stepSize: .2
               y:
-                start: 100
+                start: .3
                 steps: 5
-                stepSize: 50
+                stepSize: .15
         @wait 3000
       )


Fix __dirname for page in asar package

Fix __dirname for page in asar package

Fixes #694.

--- atom/renderer/lib/init.coffee
+++ atom/renderer/lib/init.coffee
@@ -31,7 +31,7 @@
 # Set the __filename to the path of html file if it's file: or asar: protocol.
 if window.location.protocol in ['file:', 'asar:']
   pathname =
-    if process.platform is 'win32'
+    if process.platform is 'win32' and window.location.pathname[0] is '/'
       window.location.pathname.substr 1
     else
       window.location.pathname


Remove excess debugging statements

Remove excess debugging statements

--- controllers/exceptions.coffee
+++ controllers/exceptions.coffee
@@ -54,7 +54,6 @@
       if config.nodeEnv == 'production'
         delete ex.trace

-      console.log ex
       switch ex.type
         when 'json-error'
           res.status ex.status
@@ -63,7 +62,6 @@
             id:      ex.id
             message: ex.message
         else
-          console.log ex.status
           res.status ex.status
           res.render 'error', ex: ex


Added Log, and Handle `kdc foo` legacy

Added Log, and Handle `kdc foo` legacy

`_loadManifest` did not have a proper log reference, that has been
resolved.

Also, `kdc foo` legacy usage was not recognized, but it is now.

--- bin/kdc-plus.coffee
+++ bin/kdc-plus.coffee
@@ -36,7 +36,7 @@
 # Since many of the commands load the manifest, and have to deal with
 # failures/etc in the same manner, this is a little convenience function
 # which handles the grunt work and returns the manifest.
-_loadManifest = (appPath, callback) ->
+_loadManifest = (appPath, log, callback) ->
   loadOpts =
     validate: true

@@ -63,7 +63,7 @@
   appPath ?= process.cwd()
   appPath = path.resolve appPath

-  _loadManifest appPath, (manifest) ->
+  _loadManifest appPath, log, (manifest) ->
     # ## CLI & Manifest Defaults
     # Now that we have a manifest loaded, assign our defaults, letting the
     # CLI opts override everything.
@@ -212,6 +212,12 @@
   # And finally, parse the args
   program.parse argv

+  validArgs = [
+    'command'
+    'install'
+    'outdated'
+  ]
+
   # If there are no args, run compile with legacy kdc args
   # Note: We're using `rawArgs` instead of `args` due to inconsistent
   # behavior with args with the following two commands:
@@ -219,8 +225,15 @@
   #   kdc-plus outdated --production ./
   # `args.length == 2` for the first command, `args.length == 0` for the
   # second command.. which seems odd. So, i am using rawArgs instead.
-  if program.rawArgs.length == 2
+  # I really don't like this whole following section of code, really want to
+  # find a way to handle this neatly
+  if program.rawArgs.length is 2
+    # If `kdc-plus` was called with no args, compile
     compile process.cwd(), [], coffee: true, bare: true, log
+  else if (program.rawArgs.length is 3 and
+      validArgs.indexOf(program.rawArgs[2]) < 0)
+    # If `kdc-plus unknown-arg` was called, assume it is a directory, compile
+    compile program.rawArgs[2], [], coffee: true, bare: true, log

@@ -234,7 +247,7 @@
   appPath ?= process.cwd()
   appPath = path.resolve appPath

-  _loadManifest appPath, (manifest) ->
+  _loadManifest appPath, log, (manifest) ->
     # Get a the package managers that we should tell to install from the
     # manifest. For an examplanation as to why we use this method of
     # identifying which packages to install, see <INSERT LINK HERE..>
@@ -277,7 +290,7 @@
   appPath ?= process.cwd()
   appPath = path.resolve appPath

-  _loadManifest appPath, (manifest) ->
+  _loadManifest appPath, log, (manifest) ->
     # Get a the package managers that we should tell to install from the
     # manifest. For an examplanation as to why we use this method of
     # identifying which packages to install, see <INSERT LINK HERE..>


Enabled Outdated Check

Enabled Outdated Check

--- bin/kdc-plus.coffee
+++ bin/kdc-plus.coffee
@@ -274,9 +274,6 @@
 outdated = (appPath, unknownArgs..., opts={}, log=console.error) ->
   if typeof appPath is 'object' then [opts, appPath] = [appPath, undefined]

-  log 'Not Impelemented'
-  process.exit 1
-
   appPath ?= process.cwd()
   appPath = path.resolve appPath

@@ -294,17 +291,22 @@
     outdaterOpts = {}
     outdaterOpts.node  = packageManagers.node

-    outdater appPath, outdaterOpts, (err, packages) ->
+    outdater appPath, outdaterOpts, (err, outdated, packages) ->
       if err?
         log "Error encountered during outdated: #{err.message}"
         return process.exit 1

       log pack for pack in packages

-
-
-
-
+      if outdated is false
+        log 'No defined packages our outdated!'
+        process.exit 0
+
+
+
+
+
+exports.compile   = compile
 exports.exec      = exec
 exports.install   = install
 exports.outdated  = outdated


better phrasing

better phrasing

--- benchmarks/runner.coffee
+++ benchmarks/runner.coffee
@@ -14,7 +14,7 @@
     for library, {setup, validate, error} of validators

       ## preflight to check for incorrect validation errors
-      ## Currently disabled because z-schema doesn't play well with others.
+      ## Currently disabled because of issues with z-schema.
       #validator = setup(schema)
       #e = error(validate({validator, schema, document}))
       #if e


Save file

Save file

--- bin/heyu-run.coffee
+++ bin/heyu-run.coffee
@@ -24,6 +24,8 @@

 # ---- Read JSON file
 read_json = (fn) ->
+  file = new File(fn)
+  return {} if not file.exists
   load(fn) # Use the JS parser
   for k,v of state_machines
     print '#',k,v
@@ -36,7 +38,7 @@
     appliances[name] = appl
   appliances

-# ---- Write JSON
+# ---- Write JSON from list (turns Array into a Map)
 write_json = (fn,objs) ->
   # Try to write to a file
   file = new File(fn)
@@ -133,9 +135,14 @@
 args = clone(root.arguments)  # don't need to do this, just for fun
 set = parse_opts({test: test},args)
 appliances = read_json(state_db_fn)
-print "# in:",set.event,set.id
-appl = new HeyuAppliance(set.id, states: ['OFF', 'ON'])
-appl.changeState('any',set.event)
-appliances.push appl
+if set.id
+  print "# in:",set.event,set.id
+  appl = new HeyuAppliance(set.id, states: ['OFF', 'ON'])
+  appl.changeState('any',set.event)
+  appliances[appl.name] = appl
 print "# Saving state to",state_db_fn
-write_json(state_db_fn,appliances)
+list = []
+for name,appl2 of appliances
+  appl2.display_state()
+  list.push appl2
+write_json(state_db_fn,list)


don’t allow graphically creating a latex file with paren or quotes etc in filename; also don't allow filenames with / \

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -3,8 +3,6 @@
 # Project page -- browse the files in a project, etc.
 #
 ###############################################################################
-ENABLE_PUBLIC = true
-

 {IS_MOBILE}     = require("feature")
 {top_navbar}    = require('top_navbar')
@@ -47,6 +45,9 @@
 # timeout in seconds when downloading files etc., from web in +New dialog.
 FROM_WEB_TIMEOUT_S = 45

+# for the new file dialog
+BAD_FILENAME_CHARACTERS = '\\/'
+BAD_LATEX_FILENAME_CHARACTERS = '\'"()"~%'

 # How long to cache public paths in this project
 PUBLIC_PATHS_CACHE_TIMEOUT_MS = 1000*60
@@ -1147,6 +1148,10 @@
             name = $.trim(@new_file_tab_input.val())
             if name.length == 0
                 return ''
+            for bad_char in BAD_FILENAME_CHARACTERS
+                if name.indexOf(bad_char) != -1
+                    bootbox.alert("Filenames must not contain the character '#{bad_char}'.")
+                    return ''
             s = $.trim(@new_file_tab.find(".project-new-file-path").text() + name)
             if ext?
                 if misc.filename_extension(s) != ext
@@ -1189,6 +1194,10 @@

         create_file = (ext) =>
             p = path(ext)
+
+            if not p
+                return false
+
             ext = misc.filename_extension(p)

             if ext == 'term'
@@ -1198,6 +1207,12 @@
             if ext in BANNED_FILE_TYPES
                 alert_message(type:"error", message:"Creation of #{ext} files not supported.", timeout:3)
                 return false
+
+            if ext == 'tex'
+                for bad_char in BAD_LATEX_FILENAME_CHARACTERS
+                    if p.indexOf(bad_char) != -1
+                        bootbox.alert("Filenames must not contain the character '#{bad_char}'.")
+                        return false

             if p.length == 0
                 @new_file_tab_input.focus()
@@ -1380,7 +1395,7 @@
             else
                 dialog.find("a[href=#delete-file]").hide()

-            if ENABLE_PUBLIC and not @public_access and not (obj.fullname == '.snapshots' or misc.startswith(obj.fullname,'.snapshots/'))
+            if not @public_access and not (obj.fullname == '.snapshots' or misc.startswith(obj.fullname,'.snapshots/'))
                 @is_path_published
                     path : obj.fullname
                     cb   : (err, pub) =>


admin editing of project settings style broken

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1044,7 +1044,10 @@

                 else
                     for a in quotalist
-                        usage.find(".salvus-" + a).attr("contenteditable", true).css('-webkit-appearance': 'textfield', '-moz-appearance': 'textfield')
+                        usage.find(".salvus-" + a).attr("contenteditable", true).css
+                            '-webkit-appearance' : 'textfield'
+                            '-moz-appearance'    : 'textfield'
+                            'border'             : '1px solid black'
                     @container.find(".project-quota-edit").html('<i class="fa fa-thumbs-up"> </i> Done')
                     usage.find(".project-settings-network-access-checkbox").show()
                     usage.find(".project-settings-unlimited-timeout").show()


Update timer.coffee

Update timer.coffee

--- app/assets/javascripts/timer.coffee
+++ app/assets/javascripts/timer.coffee
@@ -55,7 +55,7 @@

 START_TIMER_ID = 'start-timer'

-SECONDS_UNTIL_ORE_RESPAWN = 12 * 60
+SECONDS_UNTIL_ORE_RESPAWN = 6 * 60

 # Map where the key is the world number and the value is
 # the array of ore numbers it is currently tracking
@@ -365,4 +365,4 @@
     ###

     return
-)+)


use PepperTalk

use PepperTalk

--- www/coffee/peppertalksample.coffee
+++ www/coffee/peppertalksample.coffee
@@ -53,8 +53,8 @@
         }
     )
     @chat = (user) ->
-      return PepperKitSDK.showParticipantsForTopic('NoTopic', 'No Topic') if loginService.userName is user
-      return PepperKitSDK.chatWithParticipant(user, 'NoTopic', 'No Topic')
+      return PepperTalk.showParticipantsForTopic('NoTopic', 'No Topic') if loginService.userName is user
+      return PepperTalk.chatWithParticipant(user, 'NoTopic', 'No Topic')
     #
     return
 ])
@@ -79,11 +79,11 @@
 #
 app.run(($rootScope, $state, $stateParams, loginService) ->
   $rootScope.$on('login_success', ->
-    PepperKitSDK.onAuthRequired = (callback) ->
+    PepperTalk.onAuthRequired = (callback) ->
       loginService.getPepperKitSSO(callback)
       return
-    PepperKitSDK.init()
-    $(PepperKitSDK).on('incoming_message', (event, data) ->
+    PepperTalk.init()
+    $(PepperTalk).on('incoming_message', (event, data) ->
       $rootScope.$evalAsync(->
         $rootScope.$broadcast('incoming_message', data)
       )


Don't format version for windows installer

We're just going to use names like '1.1.0-beta1' that are compatible
with NuGet, so that the versions that squirrel.windows requests match
the names of the github releases

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -6,7 +6,6 @@
 fs = require 'fs-plus'
 GitHub = require 'github-releases'
 request = require 'request'
-{convertVersion} = require 'grunt-electron-installer'

 grunt = null

@@ -79,9 +78,8 @@
         {assetName: 'atom-api.json', sourcePath: 'atom-api.json'}
       ]
     when 'win32'
-      nupkgVersion = convertVersion(version)
       assets = [{assetName: 'atom-windows.zip', sourcePath: appName}]
-      for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{nupkgVersion}-full.nupkg", "atom-#{nupkgVersion}-delta.nupkg"]
+      for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{version}-full.nupkg", "atom-#{version}-delta.nupkg"]
         cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
         assets.push({assetName: squirrelAsset, sourcePath: assetName})
       assets


Remove unused require

Remove unused require

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -3,7 +3,6 @@

 _ = require 'underscore-plus'
 fs = require 'fs-plus'
-GitHub = require 'github-releases'
 request = require 'request'

 grunt = null


social: check if status exists, then check value

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1325,8 +1325,11 @@

   confirmEmail: (callback)->

-    if (@getAt 'status') isnt 'unconfirmed'
-      console.log "ALERT: #{@getAt 'username'} is trying to confirm '#{@getAt 'status'}' email"
+    status = @getAt 'status'
+
+    # for some reason status is sometimes 'undefined', so check for that
+    if status? and status isnt 'unconfirmed'
+      console.log "ALERT: #{@getAt 'username'} is trying to confirm '#{status}' email"
       return callback null

     @update {$set: status: 'confirmed'}, (err, res)=>


social: fix security issue where if a user invited to a team as admin, it becomes admin on koding as well

thanks for fatih for realizing the issue

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -983,7 +983,8 @@
         return callback null  if not group

         roles = ['member']
-        roles.push invitation.role  if invitation?.role
+        if invitation?.role and slug isnt 'koding'
+          roles.push invitation.role
         roles = uniq roles

         group.approveMember account, roles, (err) ->


fix adding new files

fix adding new files

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
@@ -35,7 +35,9 @@
         )
       @__updateContainerView()

-      @dialogApi.fileColl.onAdd.add(@__fileAdded, @__updateContainerView)
+      fileAdded = (f) =>
+        @__fileAdded(f)
+      @dialogApi.fileColl.onAdd.add(fileAdded, @__updateContainerView)
       @dialogApi.fileColl.onRemove.add(@__fileRemoved, @__updateContainerView)
       @dialogApi.fileColl.onReplace.add(@__fileReplaced, @__updateContainerView)


KodingKontrol: createKite: override transportOptions for only klient kite instances

so we can pass empty list of protocols which will let SockJS to choose on its own
if websocket is available then websocket or user fallbacks so on...

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -81,6 +81,19 @@
     return kite

+  createKite: (options)->
+
+    {kite} = options
+
+    # If its trying to create a klient kite instance
+    # allow to use websockets by emptying the protocols_whitelist
+    if kite.name is 'klient'
+      options.transportOptions =
+        protocols_whitelist : []
+
+    super options
+
+
   getKite: (options = {}) ->

     @reauthenticate()  unless @kite?
@@ -89,7 +102,7 @@
     { name, correlationName, region, transportOptions,
       username, environment, queryString } = options

-    # If no `correlationName` is defined assume this kite instance
+    # If no `correlationName` is defined assume this kite instance
     # is a singleton kite instance and keep track of it with this keyword
     correlationName ?= "singleton"


client/kite: revert all changes to KodingKite

--- client/app/lib/kite/kodingkite.coffee
+++ client/app/lib/kite/kodingkite.coffee
@@ -91,12 +91,13 @@

             resolve (@transport?.tell args...

-              .then (res) =>
+              .then (res) ->

                 KiteLogger.success name, rpcMethod
                 return res

               .catch (err) =>
+
                 if _errPromise = @handleKiteError err, args
                   return _errPromise


client: remove expandUrls util

client: remove expandUrls util

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/util/expandUrls.coffee
+++ client/app/lib/util/expandUrls.coffee
@@ -1,36 +0,0 @@
-urlGrabber = require 'app/util/urlGrabber'
-
-module.exports = (text, replaceAndYieldLinks = no) ->
-  return null unless text
-
-  links     = []
-  linkCount = 0
-  urls      = urlGrabber text
-  link      = null
-  # This will change the original string to either a fully replaced version
-  # or a version with temporary replacement strings that will later be replaced
-  # with the expanded html tags
-  urls.forEach (url) ->
-
-    url         = url.trim()
-    originalUrl = url
-    hasProtocol = /\w+\:\/\//
-
-    # remove protocol and trailing path
-    visibleUrl        = url.replace(hasProtocol, '').replace(/\/.*/, '')
-    checkForPostSlash = /\w*(\/\/)+.*\/.+/.test originalUrl # test for // ... / ...
-
-    # url has no protocol
-    url = "//#{url}"  if not hasProtocol.test url
-
-    # Just yield a placeholder string for replacement later on
-    # this is needed if the text should get shortened, add expanded
-    # string to array at corresponding index
-    link = "#{visibleUrl}#{if checkForPostSlash then "/…" else ""}"
-    if replaceAndYieldLinks
-      links.push link
-      link = "[tempLink#{linkCount++}]"
-
-  if replaceAndYieldLinks
-  then { links, text }
-  else link


A user can send a message to itself

A user can send a message to itself

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -185,10 +185,10 @@
             when null
                 @setMessageAsDefault options

-        # remove my address from dests
-        notMe = (dest) -> return dest.address isnt account.address
-        message.to = message.to.filter notMe
-        message.cc = message.cc.filter notMe
+        # # remove my address from dests
+        # notMe = (dest) -> return dest.address isnt account.address
+        # message.to = message.to.filter notMe
+        # message.cc = message.cc.filter notMe

         message


Look under window for precisely specified paths

--- client/lanes/lib/utilFunctions.coffee
+++ client/lanes/lib/utilFunctions.coffee
@@ -6,7 +6,6 @@

 Lanes.warn = (msg...)->
     return unless console
-    console.warn(msg...)
     if msg[0] instanceof Error
         console.warn(msg[0].stack)
     else console.warn(msg...)
@@ -28,10 +27,7 @@
 Lanes.getPath = ( name, ns='Lanes' ) ->
     return name unless _.isString(name)
     ns = "Lanes.#{ns}" if _.isString(ns) && !ns.match("Lanes")
-    if _.isObject(ns)
-        distillTypes(name,ns) || distillTypes(name, Lanes)
-    else
-        distillTypes(name,window) || distillTypes(name, distillTypes(ns, window))
+    distillTypes(name, window) || distillTypes(name, if _.isObject(ns) then ns else distillTypes(ns, window) )

 _.mixin({


make date a bit wider

make date a bit wider

--- client/skr/screens/events/Events.cjsx
+++ client/skr/screens/events/Events.cjsx
@@ -28,9 +28,9 @@
                 <LC.Input sm=6 name='title' model={@event} />
             </BS.Row>
             <BS.Row>
-                <LC.Input sm=8 name='sub_title' model={@event} />
+                <LC.Input sm=7 name='sub_title' model={@event} />
                 <LC.NumberInput sm=1 name='max_qty' model={@event} format='#' />
-                <LC.DateTime sm=3 name="starts_at" model={@event} />
+                <LC.DateTime sm=4 name="starts_at" model={@event} />
             </BS.Row>
             <BS.Row>
                 <LC.TextArea sm=12 name='info' model={@event} />


stuff

stuff

--- client/templates/_partials/nav.coffee
+++ client/templates/_partials/nav.coffee
@@ -20,7 +20,6 @@

 Template.verifiedUserNavLinks.created = ->
   if Meteor.user()?
-    console.log "Are you logged in?"
     $('#projectsLanding').addClass('hidden')

 Template.profileNavLink.helpers(


Client: VMSettings: Deleted unnecessary methods from list item. These methods has been moved to controller

--- client/app/lib/providers/snapshotlistitem.coffee
+++ client/app/lib/providers/snapshotlistitem.coffee
@@ -114,92 +114,7 @@
         """

-  ###*
-   * Show the UI confirmation for snapshot delete, and delete the
-   * snapshot if Yes is chosen.
-  ###
-  confirmDeleteSnapshot: ->
-
-    modal = kd.ModalView.confirm
-      title      : 'Delete snapshot?'
-      ok         :
-        title    : 'Yes'
-        style    : 'solid red medium'
-        callback : =>
-          modal.destroy()
-          @deleteSnapshot()
-      cancel     :
-        style    : 'solid light-grey medium'
-        type     : 'button'
-        callback : -> modal.destroy()
-
-
-  ###*
-   * Delete this snapshot, and destroy this View on success.
-  ###
-  deleteSnapshot: ->
-
-    computeController       = kd.getSingleton 'computeController'
-    kloud                   = computeController.getKloud()
-    { machineId, snapshotId } = @getData()
-
-    kloud.deleteSnapshot { machineId, snapshotId }
-      .then =>
-        listView = @getDelegate()
-        listView.removeItem this
-        listView.emit 'DeleteSnapshot', this
-      .catch (err) -> kd.warn err
-
-
-  ###*
-   * Notify the delegate (listView) to create a vm from this item's
-   * snapshot.
-   *
-   * @emits ListView~NewVmFromSnapshot
-  ###
-  vmFromSnapshot: ->
-
-    listView = @getDelegate()
-    listView.emit 'NewVmFromSnapshot', @getData()
-
-
   partial: ->
-
-
-  ###*
-   * Get the name input value, and emit the RenameSnapshot event with
-   * the proper data
-  ###
-  renameSnapshot: ->
-
-    { JSnapshot }  = remote.api
-    label          = @editInput.getValue()
-    data           = @getData()
-    { snapshotId } = data
-
-    if not label? or label is ''
-      SnapshotListItem.notify 'Name length must be larger than zero'
-      return
-
-    # Called once we have a jSnapshot to work with
-    rename = (snapshot) => snapshot.rename label, (err) =>
-      return kd.warn err  if err
-      @toggleEditable()
-      @setLabel label
-      listView = @getDelegate()
-      listView.emit 'RenameSnapshot', this, label
-
-    # If data is a jsnapshot, we don't need to fetch it
-    if data instanceof JSnapshot
-      rename data
-    else
-      JSnapshot.one snapshotId, (err, snapshot) ->
-        return kd.warn err  if err
-
-        unless snapshot?
-          return kd.warn 'Error: Cannot find snapshotId', snapshotId
-
-        rename snapshot

   ###*


Changed the Option to Required

Changed the Option to Required

--- bin/kdc-plus.coffee
+++ bin/kdc-plus.coffee
@@ -18,7 +18,7 @@

   # Define our opts
   opts.version '@@version'
-  opts.usage '[options] [kdapp directory]'
+  opts.usage '[options] <kdapp directory>'
   opts.parse argv

   #Hack for now


Stub out other methods of Bots

Stub out other methods of Bots

--- botResponder.coffee
+++ botResponder.coffee
@@ -24,6 +24,10 @@
     register: (_name, _fun) ->
       name = _name
       fun = _fun
+    getBot: (_name) ->
+      ->
+    getAllBots: () ->
+      []
   mathjs: require('mathjs')
   _: require('lodash-node')
   $: require('jquery')


correct my name =.=

correct my name =.=

--- constant.cson
+++ constant.cson
@@ -24,7 +24,7 @@
     avatar: 'https://avatars3.githubusercontent.com/u/10855724?v=3&s=460'
     link: 'http://www.weibo.com/1791427467'
   ,
-    name: '吴钩霜月明'
+    name: '吴钩霜雪明'
     avatar: 'https://avatars2.githubusercontent.com/u/6861365?v=3&s=460'
     link: 'http://www.weibo.com/jenningswu'
   ,


add `stringy` function

add `stringy` function

--- lib/CSON/cson.coffee
+++ lib/CSON/cson.coffee
@@ -8,7 +8,41 @@

 coffee = require 'coffee-script'
 vm     = require 'vm'
-# util   = require 'util'
+_      = require 'underscore'

-exports.parse  = parse  = (cson) -> return vm.runInThisContext(coffee.compile(cson, {bare: yes}))
-exports.toJSON = toJSON = (cson, uglify = no) -> JSON.stringify(parse(cson), null, if uglify then null else '  ')+step = '  '
+
+exports.parse = parse  = (cson) -> return vm.runInThisContext(coffee.compile(cson, {bare: yes}))
+exports.stringify = stringify = (obj, prefix = '') ->
+  cson = ""
+  switch typeof obj
+    when "object"
+      if _.isArray(obj)
+        if _.isEmpty(obj)
+          cson += "[]"
+        else
+          cson += "["
+          for val,i in obj
+            cson += ", " unless i == 0
+            cson += stringify(val, prefix)
+          cson += "]"
+      else
+        if _.isEmpty(obj)
+          cson += "{}"
+        else
+          cson += "\n"
+          for key, val of obj
+            key = "\"#{key}\"" unless /^[a-z$][a-z0-9_$]*$/i.test(key)
+            cson += "#{prefix}#{key}: "
+            cson += stringify(val, prefix + step) + '\n'
+    when "string", "number"
+      cson += JSON.stringify(obj)
+    when "null"
+      cson += "null"
+    when "boolean"
+      cson += if obj then "yes" else "no"
+    else
+      throw "Error: unknown type"
+  return cson
+exports.toJSON = toJSON = (cson, uglify = no) -> JSON.stringify(parse(cson), null, if uglify then null else '  ')
+exports.toCSON = toCSON = (json) -> stringify(JSON.parse(json))


Display the correct number of bars on pagination

--- app/assets/javascripts/views/staffplans/index.js.coffee
+++ app/assets/javascripts/views/staffplans/index.js.coffee
@@ -26,7 +26,7 @@
     @startDate.addWeeks(delta)
     StaffPlan.Dispatcher.trigger "date:changed"
       begin: @startDate.getTime()
-      count: 30
+      count: @numberOfBars

   initialize: ->
     @users = @options.users


Fix typo

Fix typo

IE10 tests now passing

--- test/helpers/expect.coffee
+++ test/helpers/expect.coffee
@@ -25,7 +25,7 @@
     html = html.replace(/style="(.+); "/g, 'style="$1;"')   # PhantomJS adds space after last style
     html = html.replace(/[\'\";]/g, '')    # IE8 outerHTML does not have quotes
     html = html.replace(/rgb\((\d+), ?(\d+), ?(\d+)\)/g, "rgb($1, $2, $3)") # IE8 removes spaces between digits
-    html = html.replace(/<img width=\d+/, '<img').replace('/<img height=\d+/', '<img')  # IE10 adds width, height to image tags
+    html = html.replace(/<img width=\d+/g, '<img').replace(/<img height=\d+/g, '<img')  # IE10 adds width, height to image tags
     html = html.toLowerCase()              # IE8 uppercases their tags
     return html
   )


Updated test/lib/test-utils.coffee

Updated test/lib/test-utils.coffee

--- test/lib/test-utils.coffee
+++ test/lib/test-utils.coffee
@@ -53,6 +53,6 @@
     hashFiles options, (err, hash2)->
       throw err if err

-      return hash1 is hash2
+      return cbfn (hash1 is hash2)


fix profile update

fix profile update

--- both/methods/user/user.coffee
+++ both/methods/user/user.coffee
@@ -2,7 +2,7 @@

 	updateProfile: (field, value) ->
 		check field, String
-		check value, String
+		check value, Match.Any

 		set = {}


smart reconnect

smart reconnect

--- browser/coffee/sockets.coffee
+++ browser/coffee/sockets.coffee
@@ -1,8 +1,14 @@

 PREFIX = 'ws://localhost:8886/'
+changes = null

-changes = new WebSocket PREFIX + 'changes'
-changes.onmessage = (message) ->
-    {data} = message
-    settings = JSON.parse data
-    hotkeys.setState {settings}+do open = (delay = 1000)->
+    changes = new WebSocket PREFIX + 'changes'8
+    changes.onmessage = (message) ->
+        {data} = message
+        settings = JSON.parse data
+        hotkeys.setState {settings}
+    changes.onclose = ->
+        setTimeout ->
+            open delay * 1.5
+        , 100


Use npm_server for all npm i. More npm i options.

--- build_detail.coffee
+++ build_detail.coffee
@@ -43,6 +43,13 @@

 plugin_json_path = path.join ROOT, 'assets', 'data', 'plugin.json'
 mirror_json_path = path.join ROOT, 'assets', 'data', 'mirror.json'
+npm_server = (->
+  mirrors = fs.readJsonSync mirror_json_path
+  # Don't want to mess with detecting system language here without window.navigator
+  language = config.get 'poi.language', 'zh-CN'
+  primaryServer = if language == 'zh-CN' then 'taobao' else 'npm'
+  mirrors[config.get "packageManager.mirrorName", primaryServer].server)()
+log "Using npm mirror #{npm_server}"

 theme_list =
   darkly:     'https://bootswatch.com/darkly/bootstrap.css'
@@ -165,11 +172,11 @@
 # Run js script
 runScriptAsync = (script_path, args, options) ->
   new Promise (resolve) ->
-    child_process.fork script_path, args, options
-    .on 'exit', -> resolve()
+    proc = child_process.fork script_path, args, options
+    proc.on 'exit', -> resolve()

 # Run js script, but suppress stdout and stores it into a string used to resolve
-runScriptSilentAsync = (script_path, args, options) ->
+runScriptReturnStdoutAsync = (script_path, args, options) ->
   new Promise (resolve) ->
     proc = child_process.fork script_path, args, Object.assign({silent: true}, options)
     data = ''
@@ -182,7 +189,7 @@
   # Can't use require('npm') module b/c we kept npm2 in node_modules for plugins
   log "Installing npm for #{tgt_dir}"
   fs.ensureDirSync tgt_dir
-  yield runScriptAsync npm_exec_path, ['install'].concat(args),
+  yield runScriptAsync npm_exec_path, ['install', '--registry', npm_server].concat(args),
     cwd: tgt_dir
   log "Finished installing npm for #{tgt_dir}"

@@ -223,7 +230,7 @@
           try
             tgt = compile src
           catch e
-            console.log "Compiling #{src_path} failed: #{e}"
+            log "Compiling #{src_path} failed: #{e}"
             return
           yield fs.writeFileAsync tgt_path, tgt
           yield fs.removeAsync src_path
@@ -314,15 +321,14 @@
     Promise.resolve
       log: "Unsupported platform #{platform}."

-installPluginsTo = async (plugin_names, install_root, tarball_root, server) ->
-  console.log "tarball_root"+tarball_root
+installPluginsTo = async (plugin_names, install_root, tarball_root) ->
   fs.removeSync install_root
   fs.ensureDirSync install_root
   fs.removeSync tarball_root
   fs.ensureDirSync tarball_root

   # Install plugins
-  yield npmInstallAsync install_root, ['--registry', server].concat(plugin_names)
+  yield npmInstallAsync install_root, ['--production', '--prefix', '.'].concat(plugin_names)

   plugins_dir = (for name in plugin_names
     plugin_dir = path.join install_root, 'node_modules', name
@@ -338,7 +344,7 @@
     plugin_dir)

   yield Promise.all (for plugin_dir in plugins_dir
-    npmInstallAsync plugin_dir, ['--registry', server, '--no-bin-links', '--silent'])
+    npmInstallAsync plugin_dir, ['--no-bin-links', '--no-progress', '--production'])

   yield runScriptAsync npm_exec_path, ['pack'].concat(plugins_dir),
     cwd: tarball_root
@@ -349,17 +355,12 @@
   release_dir = path.join build_root, release_dir_name

   packages = fs.readJsonSync plugin_json_path
-  mirror = fs.readJsonSync mirror_json_path
-  # Don't want to mess with detecting system language here without window.navigator
-  language = config.get 'poi.language', 'zh-CN'
-  primaryServer = if language == 'zh-CN' then 'taobao' else 'npm'
-  server = mirror[config.get "packageManager.mirrorName", primaryServer].server

   plugin_names = (n for n of packages)

   install_root = path.join building_root, 'poiplugins_install'
   gzip_root = path.join building_root, 'poiplugins'
-  yield installPluginsTo plugin_names, install_root, gzip_root, server
+  yield installPluginsTo plugin_names, install_root, gzip_root

   d = new Date()
   str_date = "#{d.getUTCFullYear()}-#{d.getUTCMonth()+1}-#{d.getUTCDate()}"
@@ -406,7 +407,7 @@
   fs.ensureDirSync stage2_app

   # Check npm version
-  npm_version = (yield runScriptSilentAsync npm_exec_path, ['--version']).trim()
+  npm_version = (yield runScriptReturnStdoutAsync npm_exec_path, ['--version']).trim()
   log "You are using npm v#{npm_version}"
   if semver.major(npm_version) == 2
     log "*** USING npm 2 TO BUILD poi IS PROHIBITED ***"


Fixed a broken unit test that still used hmachex.

--- test/unit/util.spec.coffee
+++ test/unit/util.spec.coffee
@@ -32,7 +32,7 @@
       expect(util.toHex(expected)).toEqual(result)

     it 'should return a keyed hash as hex digest', ->
-      expected = util.hmachex(key, input)
+      expected = util.hmacHex(key, input)
       expect(expected).toEqual(result)

   describe 'sha256', ->


replace \xA0 on copying and pasting

replace \xA0 on copying and pasting

The char `\xA0` may be contained in a string if we use `y` to yank a line
which contains ` `.

--- lib/clipboard.coffee
+++ lib/clipboard.coffee
@@ -8,7 +8,7 @@
   # http://groups.google.com/group/chromium-extensions/browse_thread/thread/49027e7f3b04f68/f6ab2457dee5bf55
   copy: ({data}) ->
     textArea = @_createTextArea()
-    textArea.value = data
+    textArea.value = data.replace /\xa0/g, " "

     document.body.appendChild(textArea)
     textArea.select()
@@ -22,7 +22,7 @@
     document.execCommand("Paste")
     value = textArea.value
     document.body.removeChild(textArea)
-    value
+    value.replace /\xa0/g, " "

 root = exports ? window


adding readInt8 to Buffer polyfill

adding readInt8 to Buffer polyfill

--- browser/node.coffee
+++ browser/node.coffee
@@ -537,6 +537,7 @@
       @array = new Array obj

   readUInt8: (i) -> @array[i] & 0xFF  # cast to unsigned byte
+  readInt8: (i) -> @array[i]

 # Node's filesystem API, implemented as a wrapper around FSState.
 root.fs =


update cson syntax checking for dobi linter. fixes https://github.com/lessthan3/packages/issues/8775

--- lib/dobi-lint.coffee
+++ lib/dobi-lint.coffee
@@ -122,9 +122,13 @@

   # make sure cson files are parsable
   if ext is 'cson'
-    result = cson.parseSync contents
-    if result.constructor is SyntaxError
-      fail result.location.first_line, result.toString()
+    try
+      cson.readFileSync filename
+    catch err
+      if err.constructor is SyntaxError
+        fail err.location.first_line, err.toString()
+      else
+        fail 'unknown', err

   # coffee single line comments
   # - require a space after the #


Parameterized file server root.

Parameterized file server root.

--- src/main/coffeescript/scxml/test/multi-process-browser/harness.coffee
+++ src/main/coffeescript/scxml/test/multi-process-browser/harness.coffee
@@ -17,6 +17,7 @@
 		windowManager = args['-windowManager'] or 'metacity'
 		forwardX11 = args['-forwardX11'] or (xserver is 'Xephyr')
 		projectSrcDir = args['-projectSrcDir'] or '/home/jacob/workspace/scion'
+		fileServerRoot = args['-fileServerRoot'] or "#{projectSrcDir}/build"

 		console.log "received args",args

@@ -30,6 +31,7 @@
 		console.log "windowManager",windowManager
 		console.log "forwardX11",forwardX11
 		console.log "projectSrcDir",projectSrcDir
+		console.log "fileServerRoot",fileServerRoot

 		serverTestRunnerUrl = "http://#{hostServerHostName}:#{hostServerPort}/runner"

@@ -54,7 +56,6 @@
 				else
 					"text/plain"

-		fileServerRoot = "/home/jacob/workspace/scion/build"	#TODO: customize this

 		#browserWindowIds = null


Fixed deprecation

Fixed deprecation

--- lib/spark-dev.coffee
+++ lib/spark-dev.coffee
@@ -229,10 +229,10 @@
   # Open view in bottom panel
   openPane: (uri) ->
     uri = 'spark-dev://editor/' + uri
-    pane = atom.workspace.paneForUri uri
+    pane = atom.workspace.paneForURI uri

     if pane?
-      pane.activateItemForUri uri
+      pane.activateItemForURI uri
     else
       if atom.workspace.getPanes().length == 1
         pane = atom.workspace.getActivePane().splitDown()


Make isFile second argument

Make isFile second argument

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -313,13 +313,12 @@

   # Get the outline of a system call to the current platform's file manager.
   #
-  # entryType   - Either "file" or "directory" to indicate what the path points
-  #               to.
   # pathToOpen  - Path to a file or directory.
+  # isFile      - True if the path is a file, false otherwise.
   #
   # Returns an object containing a command, a human-readable label, and the
   # arguments.
-  fileManagerCommandForPath: (entryType, pathToOpen) ->
+  fileManagerCommandForPath: (pathToOpen, isFile) ->
     switch process.platform
       when 'darwin'
         command: 'open'
@@ -333,7 +332,7 @@
         # Strip the filename from the path to make sure we pass a directory
         # path. If we pass xdg-open a file path, it will open that file in the
         # most suitable application instead, which is not what we want.
-        pathToOpen =  path.dirname(pathToOpen) if entryType is 'file'
+        pathToOpen =  path.dirname(pathToOpen) if isFile

         command: 'xdg-open'
         label: 'File Manager'
@@ -342,9 +341,8 @@
   showSelectedEntryInFileManager: ->
     entry = @selectedEntry()
     return unless entry
-    entryType = if entry instanceof DirectoryView then 'directory' else 'file'
-
-    {command, args, label} = @fileManagerCommandForPath(entryType, entry.getPath())
+
+    {command, args, label} = @fileManagerCommandForPath(entry.getPath(), entry instanceof FileView)

     errorLines = []
     stderr = (lines) -> errorLines.push(lines)


Renamed variable and uses  instead of hardcoded separator

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -414,8 +414,8 @@
     dialog.attach()

   removeSelectedEntries: ->
-    parents = _.uniq(_.map(@selectedPaths(),  (path) ->
-      return path.substring(0, path.lastIndexOf('/'))
+    parents = _.uniq(_.map(@selectedPaths(),  (selected_path) ->
+      return selected_path.substring(0, selected_path.lastIndexOf(path.sep))
     ))
     if @hasFocus()
       selectedPaths = @selectedPaths()


Fixes #635

Fixes #635

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -475,7 +475,7 @@
   # Returns nothing.
   registerPrefix: (e) ->
     keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
-    name = atom.keymap.keystrokeForKeyboardEvent(keyboardEvent)
+    name = atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent)
     if name.lastIndexOf('shift-', 0) is 0
       name = name.slice(6)
     new Prefixes.Register(name)
@@ -487,7 +487,7 @@
   # Returns nothing.
   repeatPrefix: (e) ->
     keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
-    num = parseInt(atom.keymap.keystrokeForKeyboardEvent(keyboardEvent))
+    num = parseInt(atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent))
     if @topOperation() instanceof Prefixes.Repeat
       @topOperation().addDigit(num)
     else


Changes to implement real marks in vim-mode.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -9,6 +9,7 @@
 Utils = require './utils'
 Panes = require './panes'
 Scroll = require './scroll'
+{$$, Point, Range} = require 'atom'

 module.exports =
 class VimState
@@ -62,6 +63,66 @@
   registerChangeHandler: (buffer) ->
     buffer.on 'changed', ({newRange, newText, oldRange, oldText}) =>
       return unless @setRegister?
+      substr = '\n';
+      numNew = pos = 0
+      numNew++ while pos = 1 + newText.indexOf substr, pos
+      numOld = pos = 0
+      numOld++ while pos = 1 + oldText.indexOf substr, pos
+
+      diff = numNew - numOld
+      nrStart = newRange.start.toArray()
+      nrEnd = newRange.end.toArray()
+
+      orStart = oldRange.start.toArray()
+      orEnd = oldRange.end.toArray()
+
+      if oldRange.containsRange(newRange)
+        insert = false
+        rangeA = oldRange
+        rangeB = newRange
+        text = 'case1'
+      else
+        insert = true
+        rangeA = newRange
+        rangeB = oldRange
+        text = 'case2'
+      if not rangeA.isEmpty() && not rangeB.isEmpty()
+        alert 'both not empty ' + text
+
+      if rangeA.start.compare(rangeA.end) == -1
+        for k,v of @marks
+          #the change is entirely before the mark
+          q = v.toArray();
+          if insert
+            if rangeB.end.compare(v) != 1
+              if rangeB.end.toArray()[0] == q[0]
+                @marks[k] = new Point(q[0] + diff, q[1] - rangeA.start.toArray()[1] + rangeA.end.toArray()[1])
+              else
+                @marks[k] = new Point(q[0] + diff,q[1])
+          else
+            if rangeA.end.compare(v) != 1
+              #the end of the range is on the same line as the mark
+              if rangeA.end.toArray()[0] == q[0]
+                @marks[k] = new Point(q[0] + diff, q[1] - rangeA.end.toArray()[1] + rangeA.start.toArray()[1])
+              else
+                @marks[k] = new Point(q[0] + diff,q[1])
+      else
+        for k,v of @marks
+          #the change is entirely before the mark
+          q = v.toArray();
+          if insert
+            #the end of the range is on the same line as the mark
+            if rangeB.start.compare(v) != 1
+              if rangeB.start.toArray()[0] == q[0]
+                @marks[k] = new Point(q[0] + diff, q[1] - rangeA.end.toArray()[1] + rangeA.start.toArray()[1])
+              else
+                @marks[k] = new Point(q[0] + diff,q[1])
+          else
+            if rangeA.start.compare(v) != 1
+              if rangeA.start.toArray()[0] == q[0]
+                @marks[k] = new Point(q[0] + diff, q[1] - rangeA.start.toArray()[1] + rangeA.end.toArray()[1])
+              else
+                @marks[k] = new Point(q[0] + diff,q[1])

       if newText == ''
         @setRegister('"', text: oldText, type: Utils.copyType(oldText))


debug route reseting

debug route reseting

--- config/initializers/resetproxy.coffee
+++ config/initializers/resetproxy.coffee
@@ -13,7 +13,7 @@
 resetRoutes = ->
     Application.all (err, installedApps) ->
         appDict = {}
-        for installedApp in installedApps
+        for installedApp in installedApps.rows
             appDict[installedApp.name] = installedApp

         haibuClient.get 'drones/running', (err, res, apps) ->
@@ -24,11 +24,12 @@
     app = apps.pop()
     if apps.length > 0
         installedApp = appDict[app.name]
-        if installedApp? and installedApp.port != app.port
+
+        if installedApp? and installedApp.port isnt app.port
             instaledApp.updateAttributes port: app.port, (err) ->
-                updateApps(apps, appDict, callback)
+                updateApps apps, appDict, callback
         else
-            updateApps(apps, appDict, callback)
+            updateApps apps, appDict, callback
     else
         callback()


bomb fix

bomb fix

--- scripts/bomb.coffee
+++ scripts/bomb.coffee
@@ -2,6 +2,8 @@
   robot.respond /(.*) bomb( (\d+))?/i, (msg) ->
     subject = msg.match[1]
     if subject isnt "carson" and subject isnt "corgi" and subject isnt "pug" and subject isnt "maurice" and subject isnt "fierce"
+      if subject is "starving"
+        subject = "puppy"
       count = msg.match[2] || 5
       imageMe msg, count, subject


Fixing docs regex to allow multi string

Fixing docs regex to allow multi string


Add setting goal dialog

Add setting goal dialog

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -9,6 +9,9 @@

 moment = require "moment"
 _ = require "underscore"
+Path = require "path"
+Dialog = require Path.join __dirname, "..", "src", "dialog"
+

 module.exports = (robot) ->

@@ -38,6 +41,33 @@
     "e67e22"
     "e74c3c"
   ]
+
+  robot.respond /目標設定/i, (msg) ->
+    dialog = new Dialog msg, (msg) ->
+      text = msg.envelope.message.text
+      if text.length is 0 or /^(help|ヘルプ|わからん)$/i.test text
+        msg.send "何かおっしゃってください。目標として設定します。" +
+          "「もうない」や「終わり」と言えば目標設定モードを終了します。"
+      else if /^((もう)?ない|(終|お)わり|おしまい)$/i.test text
+        msg.send "ありがとうございました。現在設定中の目標は\n" +
+          (_.map @.get("goals"), (goal, i) ->
+            "#{i+1} : #{goal}"
+          ).join "\n"
+        @.end()
+      else
+        goals = @.get("goals") or []
+        goals.push text
+        @.set "goals", goals
+        msg.send "「#{text}」を目標として設定しました。" +
+          "もうなければ、「ない」とか「終わり」とか言ってくださいね。"
+
+    goals = dialog.get("goals") or []
+    dialog.set "goals", goals
+    msg.send "#{msg.envelope.user.name}さんの目標を教えてください！\n" +
+      "なければ、「ない」とか「終わり」とか言ってくださいね。"
+
+    #Dialog.addDialog dialog
+    Dialog.listen robot

   robot.respond /今日の目標は(?!？)([\s\S]+)/i, (msg) ->
     goal = msg.match[1]


fixed coffeelint errors on workers/social/lib/social/models/computeproviders/computeutils.coffee

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -16,7 +16,7 @@
 # the default value to add, if no storage is defined.
 DEFAULT_STORAGE_USAGE = 3

-reviveProvisioners = (client, provisioners, callback, revive = no)->
+reviveProvisioners = (client, provisioners, callback, revive = no) ->

   if not revive or not provisioners or provisioners.length is 0
     return callback null, provisioners
@@ -26,7 +26,7 @@
   # TODO add multiple provisioner support
   provisioner = provisioners[0]

-  JProvisioner.one$ client, slug: provisioner, (err, provision)->
+  JProvisioner.one$ client, { slug: provisioner }, (err, provision) ->

     if err or not provision?
       console.warn "Requested provisioner: #{provisioner} not found !"
@@ -36,7 +36,7 @@
       callback null, [ provision.slug ]

-reviveCredential = (client, credential, callback)->
+reviveCredential = (client, credential, callback) ->

   [credential, callback] = [callback, credential]  unless callback?

@@ -50,24 +50,24 @@
     JCredential.fetchByIdentifier client, credential, callback

-reviveClient = (client, callback, revive = yes)->
+reviveClient = (client, callback, revive = yes) ->

   return callback null  unless revive

   { connection: { delegate:account }, context: { group } } = client

   JGroup = require '../group'
-  JGroup.one { slug: group }, (err, groupObj)->
+  JGroup.one { slug: group }, (err, groupObj) ->

     return callback err  if err
-    return callback new KodingError "Group not found"  unless groupObj
+    return callback new KodingError 'Group not found'  unless groupObj

     res = { account, group: groupObj }

-    account.fetchUser (err, user)=>
+    account.fetchUser (err, user) ->

       return callback err  if err
-      return callback new KodingError "User not found"  unless user
+      return callback new KodingError 'User not found'  unless user

       res.user = user

@@ -86,8 +86,8 @@

 locks = []

-lockProcess = (client)->
-  {nickname} = client.connection.delegate.profile
+lockProcess = (client) ->
+  { nickname } = client.connection.delegate.profile
   if (locks.indexOf nickname) > -1
     # console.log "[LOCKER] User #{nickname} requested to acquire lock again!"
     return false
@@ -96,8 +96,8 @@
     locks.push nickname
     return yes

-unlockProcess = (client)->
-  {nickname} = client.connection.delegate.profile
+unlockProcess = (client) ->
+  { nickname } = client.connection.delegate.profile
   t = locks.indexOf nickname
   if t > -1
     # console.log "[UNLOCKER] User #{nickname} unlocked."
@@ -106,7 +106,8 @@
   #   console.log "[UNLOCKER] User #{nickname} was not locked, nothing to do."

-revive = do -> ({
+revive = do -> (
+  {
     shouldReviveClient
     shouldPassCredential
     shouldReviveProvider
@@ -125,16 +126,16 @@
       options = {}

     unless typeof _callback is 'function'
-      _callback = (err)->
-        console.error "Unhandled error:", err?.message or err
+      _callback = (err) ->
+        console.error 'Unhandled error:', err?.message or err

     if shouldLockProcess

       unless lockProcess client
         return _callback new KodingError \
-          "There is a process on-going, try again later.", "Busy"
-
-      callback = (rest...)->
+          'There is a process on-going, try again later.', 'Busy'
+
+      callback = (rest...) ->
         unlockProcess client
         _callback rest...

@@ -144,16 +145,16 @@

     shouldReviveProvider ?= yes

-    {provider, credential, provisioners} = options
+    { provider, credential, provisioners } = options

     if shouldReviveProvider
       if not provider or not provider_ = PROVIDERS[provider]
-        return callback new KodingError "No such provider.", "ProviderNotFound"
+        return callback new KodingError 'No such provider.', 'ProviderNotFound'
       else
         provider_.slug   = provider
         options.provider = provider_

-    reviveClient client, (err, revivedClient)=>
+    reviveClient client, (err, revivedClient) =>

       return callback err       if err
       client.r = revivedClient  if revivedClient?
@@ -180,20 +181,20 @@
       if shouldPassCredential and not credential?
         unless provider in ['koding', 'managed']
           return callback new KodingError \
-            "Credential is required.", "MissingCredential"
-
-      reviveCredential client, credential, (err, cred)=>
+            'Credential is required.', 'MissingCredential'
+
+      reviveCredential client, credential, (err, cred) =>

         if err then return callback err

         if shouldPassCredential and not cred?
           unless provider in ['koding', 'managed']
             return callback \
-              new KodingError "Credential failed.", "AccessDenied"
+              new KodingError 'Credential failed.', 'AccessDenied'
         else
           options.credential = cred.identifier  if cred?.identifier

-        reviveProvisioners client, provisioners, (err, provisioners)=>
+        reviveProvisioners client, provisioners, (err, provisioners) =>

           options.provisioners = provisioners  if provisioners?

@@ -208,21 +209,21 @@

 checkTemplateUsage = (template, account, callback) ->

-  {Relationship} = require 'jraphical'
+  { Relationship } = require 'jraphical'
   Relationship.count
     targetId   : template.getId()
-    targetName : "JStackTemplate"
+    targetName : 'JStackTemplate'
     sourceId   : account.getId()
   , (err, count) ->

     if err or count > 0
-    then callback new KodingError "Template in use", "InUse", err
+    then callback new KodingError 'Template in use', 'InUse', err
     else callback null

 fetchGroupStackTemplate = (client, callback) ->

-  reviveClient client, (err, res)->
+  reviveClient client, (err, res) ->

     return callback err  if err

@@ -233,39 +234,39 @@

       # This is a fallback mechanism and valid only for production. ~ GG
       if group.slug is 'koding'
-        {ObjectId} = require 'bongo'
-        group.stackTemplates = [ObjectId "53fe557af052f8e9435a04fa"]
-        console.error "[critical] Koding group template is not set!"
+        { ObjectId } = require 'bongo'
+        group.stackTemplates = [ObjectId '53fe557af052f8e9435a04fa']
+        console.error '[critical] Koding group template is not set!'
       else
-        return callback new KodingError "Template not set", "NotFound"
+        return callback new KodingError 'Template not set', 'NotFound'

     # TODO Make this works with multiple stacks ~ gg
     stackTemplateId = group.stackTemplates[0]

     # TODO make all these in seperate functions
-    JStackTemplate = require "./stacktemplate"
-    JStackTemplate.one { _id: stackTemplateId }, (err, template)->
+    JStackTemplate = require './stacktemplate'
+    JStackTemplate.one { _id: stackTemplateId }, (err, template) ->

       if err
         console.warn "Failed to fetch stack template for #{group.slug} group"
         console.warn "Failed to create stack for #{user.username} !!"
-        return callback new KodingError "Template not set", "NotFound", err
+        return callback new KodingError 'Template not set', 'NotFound', err

       if not template?
         console.warn "Stack template is not exists for #{group.slug} group"
         console.warn "Failed to create stack for #{user.username} !!"
-        return callback new KodingError "Template not found", "NotFound", err
-
-      checkTemplateUsage template, account, (err)->
+        return callback new KodingError 'Template not found', 'NotFound', err
+
+      checkTemplateUsage template, account, (err) ->
         return callback err  if err?

         res.template = template
         callback null, res

-guessNextLabel = (options, callback)->
-
-  {user, group, provider, label} = options
+guessNextLabel = (options, callback) ->
+
+  { user, group, provider, label } = options

   return callback null, label  if label?

@@ -277,16 +278,23 @@
   #  - user and group must be same and user must be owner of the machine
   #  - label needs to start with provider name and ends with "-vm-{0-9}" ~ GG
   selector   =
-    provider : provider
-    users    : $elemMatch: id: user.getId(), sudo: yes, owner: yes
-    groups   : $elemMatch: id: group.getId()
-    label    : ///^#{provider}-vm-[0-9]*$///
+    provider     : provider
+    users        :
+      $elemMatch :
+        id       : user.getId()
+        sudo     : yes
+        owner    : yes
+    groups       :
+      $elemMatch :
+        id       : group.getId()
+    label        : ///^#{provider}-vm-[0-9]*$///

   options    =
-    limit    : 1
-    sort     : createdAt : -1
-
-  JMachine.one selector, options, (err, machine)->
+    limit       : 1
+    sort        :
+      createdAt : -1
+
+  JMachine.one selector, options, (err, machine) ->

     return callback err  if err?
     unless machine?
@@ -297,11 +305,11 @@
       callback null, "#{provider}-vm-#{index+1}"

-fetchUserPlan = (client, callback)->
-
-  {clone} = require 'underscore'
-  Payment = require '../payment'
-  Payment.subscriptions client, {}, (err, subscription)=>
+fetchUserPlan = (client, callback) ->
+
+  { clone } = require 'underscore'
+  Payment   = require '../payment'
+  Payment.subscriptions client, {}, (err, subscription) ->

     if err? or not subscription?
     then plan = 'free'
@@ -318,7 +326,7 @@
       type     : 'disk'
       originId : client.r.account.getId()

-    , (err, amount)->
+    , (err, amount) ->

       amount = 0  if err
       planData.storage += Math.floor amount / 1000
@@ -326,7 +334,7 @@
       callback err, planData

-checkUsage = (usage, plan, storage)->
+checkUsage = (usage, plan, storage) ->

   err = null
   if usage.total + 1 > plan.total
@@ -337,7 +345,7 @@
   if err then return new KodingError err

-fetchUsage = (client, options, callback)->
+fetchUsage = (client, options, callback) ->

   JMachine  = require './machine'
   JSnapshot = require './snapshot'
@@ -346,22 +354,26 @@
   { provider }    = options

   selector         = { provider }
-  selector.users   = $elemMatch: id: user.getId(), sudo: yes, owner: yes
+  selector.users   =
+    $elemMatch :
+      id       : user.getId()
+      sudo     : yes
+      owner    : yes
   snapshotSelector = { originId: account.getId() }

-  JSnapshot.some snapshotSelector, {}, (err, snapshots = [])->
+  JSnapshot.some snapshotSelector, {}, (err, snapshots = []) ->
     return callback err  if err?

     snapshots = snapshots.length

-    JMachine.some selector, limit: 30, (err, machines)->
+    JMachine.some selector, { limit: 30 }, (err, machines) ->
       return callback err  if err?

       total    = machines.length
       alwaysOn = 0
       storage  = 0

-      machines.forEach (machine)->
+      machines.forEach (machine) ->
         alwaysOn++  if machine.meta?.alwaysOn
         storage += machine.meta?.storage_size ? DEFAULT_STORAGE_USAGE

@@ -374,3 +386,5 @@
   revive, reviveClient, reviveCredential
   checkTemplateUsage
 }
+
+


EDSC-376: Extend ZoomControl instead of Control

--- app/assets/javascripts/modules/map/leaflet-plugins/zoom_home.js.coffee
+++ app/assets/javascripts/modules/map/leaflet-plugins/zoom_home.js.coffee
@@ -3,7 +3,7 @@
 ns.ZoomHome = do (L, currentPage = window.edsc.models.page.current) ->
   DomUtil = L.DomUtil

-  ZoomHome = L.Control.extend
+  ZoomHome = L.Control.Zoom.extend
     options:
       position: 'topright'
       zoomInText: '+'
@@ -11,67 +11,28 @@
       zoomOutText: '-'
       zoomOutTitle: 'Zoom out'
       zoomHomeText: '<i class="fa fa-home" style="line-height:1.65;"></i>'
-      zoomHomeTitle: 'Zome home'
+      zoomHomeTitle: 'Zoom home'

     onAdd: (map) ->
-      controlName = 'leaflet-control-zoom'
-      container = DomUtil.create('div', controlName + ' leaflet-bar')
+      container = L.Control.Zoom.prototype.onAdd.call(this, map)
+
       options = @options
-      @_zoomInButton = @_createButton(options.zoomInText, options.zoomInTitle, controlName + '-in', container, @_zoomIn)
-      @_zoomHomeButton = @_createButton(options.zoomHomeText, options.zoomHomeTitle, controlName + '-home', container, @_zoomHome)
-      @_zoomOutButton = @_createButton(options.zoomOutText, options.zoomOutTitle, controlName + '-out', container, @_zoomOut)

-      @_updateDisabled()
-      map.on('zoomend zoomlevelschange', @_updateDisabled, this)
+      home = @_createButton(options.zoomHomeText, options.zoomHomeTitle, 'leaflet-control-zoom-home', container, (e) => @_zoomHome(e))
+      container.insertBefore(home, @_zoomOutButton)

       container

-    onRemove: (map) ->
-      map.off('zoomend zoomlevelschange', @_updateDisabled, this)
-
-    _zoomIn: (e) ->
-      @_map.zoomIn(e.shiftKey ? 3 : 1)
-
-    _zoomOut: (e) ->
-      @_map.zoomOut(e.shiftKey ? 3 : 1)
-
     _zoomHome: (e) ->
       spatial = currentPage.query.spatial()
-      if spatial?
+      if spatial?.length > 0
         points = spatial.split(':')
-        if points[0] == 'point'
-          p1 = points[1].split(',').reverse()
-          p2 = p1
-        else if points[0] == 'bounding_box'
-          p1 = points[1].split(',').reverse()
-          p2 = points[2].split(',').reverse()
-        @_map.fitBounds([p1, p2])
+        points.shift()
+        bounds = new L.LatLngBounds()
+        for point in points
+          bounds.extend(point.split(',').reverse())
+        @_map.fitBounds(bounds)
       else
         @_map.setView([0, 0], 2)

-    _createButton: (html, title, className, container, fn) ->
-      link = DomUtil.create('a', className, container)
-      link.innerHTML = html
-      link.href = '#'
-      link.title = title
-
-      L.DomEvent.on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
-        .on(link, 'click', L.DomEvent.stop)
-        .on(link, 'click', fn, this)
-        .on(link, 'click', @_refocusOnMap, this)
-
-      link
-
-    _updateDisabled: ->
-      map = @_map
-      className = 'leaflet-disabled'
-
-      DomUtil.removeClass(@_zoomInButton, className)
-      DomUtil.removeClass(@_zoomOutButton, className)
-
-      if map._zoom == map.getMinZoom()
-        DomUtil.addClass(@_zoomOutButton, className)
-      if map._zoom == map.getMaxZoom()
-        DomUtil.addClass(@_zoomInButton, className)
-
   exports = ZoomHome


timeshift fix try

timeshift fix try

--- scripts/timeshift.coffee
+++ scripts/timeshift.coffee
@@ -7,13 +7,13 @@

 module.exports = (robot) ->
   robot.respond /timeshift/i, (msg) ->
-    asia = new time.Date
-    asia.setTimezone 'Asia/Ho_Chi_Minh'
-    germany  = new time.Date
-    germany.setTimezone 'Europe/Berlin'
+    florida = new time.Date
+    florida.setTimezone 'US/Eastern'
+    munich  = new time.Date
+    munich.setTimezone 'Europe/Berlin'

     reply = """
-    It's #{asia.toLocaleTimeString()} in Thailand and Vietnam.
-    It's #{germany.toLocaleTimeString()} in Germany.
+    It's #{florida.toLocaleTimeString()} in Palm Beach.
+    It's #{munich.toLocaleTimeString()} in Munich.
     """
     msg.send reply


test: make geojsonwriter test executable


Fixed: the previous and next tasks were not set

--- client/app/collections/tasks.coffee
+++ client/app/collections/tasks.coffee
@@ -196,6 +196,9 @@
         else
             newNextTask = @at(newIndex)

+        task.setPreviousTask newPreviousTask
+        task.setNextTask newNextTask
+
         @remove task
         @add task,
             at: newIndex


lines association doesn't change

lines association doesn't change

--- client/skr/models/SalesOrder.coffee
+++ client/skr/models/SalesOrder.coffee
@@ -19,13 +19,13 @@
         notes:              "string"
         options:            "any"

-    # optional from details view
+    # optional attributes from details view
     session:
         customer_code:      {type:"string"}
         order_total:        {type:"bigdec"}

     derived:
-        total: deps: ['order_total', 'lines' ], fn: ->
+        total: deps: ['order_total'], fn: ->
             @order_total or @lines.reduce( (t, l) ->
                 t.plus(l.total)
             , _.bigDecimal(0))
@@ -52,10 +52,11 @@
     constructor: ->
         super
         @on("change:customer", @onCustomerChange)
-        @listenTo @lines, 'change:total', ->
+        @lines.on('change:total', ->
             @trigger('change', @, {})
             delete this._cache.total
             @unset('order_total')
+        , this)

     onCustomerChange: ->
         return unless @isNew()


Calendar bug fixed; use seconds, not milliseconds for epoch.

--- js/datetime/calendar/calendar.coffee
+++ js/datetime/calendar/calendar.coffee
@@ -276,7 +276,7 @@
             ELEM.addClassName( _colElem, 'calendar_weeks_week_col_no' )
           else
             ELEM.addClassName( _colElem, 'calendar_weeks_week_col_yes' )
-          ELEM.setAttr( _colElem, 'href', "javascript:HSystem.views[#{@viewId}].setValue(#{_colMs})" )
+          ELEM.setAttr( _colElem, 'href', "javascript:HSystem.views[#{@viewId}].setValue(#{_colSecs})" )
           _left = (_col*_colWidth+_leftPlus)
           ELEM.setStyle( _colElem, 'left', _left+'px' )
           ELEM.setStyle( _colElem, 'width', (_colWidth-1)+'px' )


use .render as fallback for detecting react

React-Lite (used by API), doesn't set the isReactComponent

--- client/lanes/lib/dom.coffee
+++ client/lanes/lib/dom.coffee
@@ -66,7 +66,7 @@
         throw new TypeError("Selector / DOM node is not present")
     else if _.isElement(unknown)
         unknown
-    else if unknown.isReactComponent
+    else if unknown.isReactComponent or unknown.render
         Lanes.Vendor.ReactDOM.findDOMNode(unknown)
     else if unknown.nodeType is 9 # body tag
         unknown


Remove log line

Remove log line

--- client/view/proposal.coffee
+++ client/view/proposal.coffee
@@ -18,7 +18,6 @@
       return
     if typeArr.length == 1
       type = typeArr[0].id
-    console.log(speakerIds)
     @proposal.update(editing: false, title: title, abstract: abstract, type: type, speaker_ids: speakerIds)

 Template.proposal.proposal = -> @proposal


No caching for housekeeping

No caching for housekeeping

--- app/assets/javascripts/cachecontrols.js.coffee
+++ app/assets/javascripts/cachecontrols.js.coffee
@@ -45,6 +45,7 @@
       type: 'GET'
       url:  '/housekeeping/' + $(this).val(),
       dataType: 'json',
+      cache: false,
     }).done (json) ->
       $('#cleanResult').text('')
       $('#cleanResult').append('> ' + line + '\n') for line in json.out


purge kalendae

purge kalendae

--- app/assets/javascripts/droom/widgets.js.coffee
+++ app/assets/javascripts/droom/widgets.js.coffee
@@ -24,7 +24,6 @@
         @_dom = @_container.find('span.dom')
         @_year = @_container.find('span.year')
       initial_date = @getDate()
-      console.log 'datepicker', @_container, @_field
       @_container.DatePicker
         calendars: 1
         date: initial_date
@@ -798,10 +797,9 @@
           old.remove()
           @init()

-    # This use of moment depends on the loading of Kalendae, which we use for the date-picker widget above.
-    #
     monthName: () =>
-      Kalendae.moment.months[@_month-1]
+      months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+      months[@_month-1]

     searchForm: =>
       @_form ?= $('#suggestions')


small captive form fixes

small captive form fixes

--- app/assets/javascripts/droom/widgets.js.coffee
+++ app/assets/javascripts/droom/widgets.js.coffee
@@ -419,13 +419,10 @@

     prepare: (xhr, settings) =>
       @_container.fadeTo "fast", 0.2
-      @_request?.abort()
-      @_request = xhr

     capture: (e, data, status, xhr) =>
       @_cache[@serialize()] = data
       @display(data)
-      @_request = null
       @saveState(data) if @_historical

     display: (results) =>
@@ -435,10 +432,7 @@
       replacement.find('a.cancel').click(@revert)

     revert: (e) =>
-      if @_historical
-        @restoreState(@_original_qs)
-      else
-        @display(@_original_content)
+      @display(@_original_content)

     saveState: (results, qs) =>
       qs ?= @serialize()


scroll directive modified: calculation of 'background-position-y' based on the spied element, no on the document

--- app/scripts/directives/scroll-directive.coffee
+++ app/scripts/directives/scroll-directive.coffee
@@ -44,7 +44,6 @@
         headerHeight = $header.height()

         # set a dynamic 'background-position-y' to '.background-y-position-scroll' elements when document is scrolled
-
         for $element in $backgroundYPositionSpiedElements
           cssStyle = ''
           elementTop = $element.position().top


unicode-aware word-ish splitter

unicode-aware word-ish splitter

--- chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
+++ chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
@@ -58,7 +58,7 @@
     return safe

   words: (str) ->
-    return (@clean(word) for word in Zotero.Utilities.XRegExp.matchChain(@innerText(str), [XRegExp("[\\p{Alphabetic}\\p{Nd}\\{Pc}\\p{M}]+", "g")]) when word != '')
+    return (@clean(word) for word in Zotero.Utilities.XRegExp.matchChain(@innerText(str), [Zotero.Utilities.XRegExp("[\\p{L}\\p{Nd}\\{Pc}\\p{M}]+", "g")]) when word != '')

   # three-letter month abbreviations. I assume these are the same ones that the
   # docs say are defined in some appendix of the LaTeX book. (i don't have the


Fix vas exists logic

Fix vas exists logic

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -81,9 +81,9 @@
       groupSlug  = KD.singletons.groupsController.getGroupSlug()
       groupSlug ?= 'koding'
       @appStorage.fetchValue "mountedVM", (vms)->
-        vms or= {}
+        vms            or= {}
         vms[groupSlug] or= []
-        if vms[groupSlug]
+        if vms[groupSlug].length > 0
           mountVms vms[groupSlug]
         else
           KD.remote.api.JVM.fetchVmsByContext {}, (err, vms)->


fixed a big typo

fixed a big typo

--- components/FromPorts.coffee
+++ components/FromPorts.coffee
@@ -1,9 +1,6 @@
 querystring = require 'querystring'
 _ = require 'underscore'
 noflo = require 'noflo'
-
-accept_all = {}
-accept_all.Access-Control-Allow-Origin = "*"

 class FromPorts extends noflo.Component
   constructor: ->
@@ -11,7 +8,7 @@

     @inPorts =
       status: new noflo.Port 'string'
-      headers: new noflo.Port 'object' || accept_all
+      headers: new noflo.Port 'object'
       body: new noflo.Port 'string'
       request: new noflo.Port 'object'
     @outPorts =


Found some simplify bugs in power - test cases.

--- coffeequate/tests/spec/nodes.coffee
+++ coffeequate/tests/spec/nodes.coffee
@@ -146,6 +146,10 @@
 				expect(pow.simplify().toString()).toBe("1")
 				pow = parse.stringToExpression("(x ** -1 * x ** 1")
 				expect(pow.simplify().toString()).toBe("1")
+				pow = parse.stringToExpression("(x ** 2) ** 0.5")
+				expect(pow.simplify().toString()).toBe("x")
+				pow = parse.stringToExpression("(x ** 2) ** y")
+				expect(pow.simplify().toString()).toBe("(x ** (2 * y))")

 		it "can be formed into a tree", ->


Fix #916

Fix #916

--- server/users/User.coffee
+++ server/users/User.coffee
@@ -18,7 +18,6 @@
   for prop, sch of jsonschema.properties
     continue if prop is 'emails' # defaults may change, so don't carry them over just yet
     @set(prop, sch.default) if sch.default?
-  @set('permissions', ['admin']) if not isProduction
   next()
 )

@@ -108,6 +107,7 @@
     @set('password', undefined)
   if @get('email') and @get('anonymous')
     @set('anonymous', false)
+    @set('permissions', ['admin']) if not isProduction
     data =
       email_id: sendwithus.templates.welcome_email
       recipient:


instantiate Agda when opening new file, or when changin file name

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -17,11 +17,17 @@

     instantiateCore: (editor) =>
-        if isAgdaFile editor
-            editor.core = new Core editor
-            # register editor events
-            editor.onDidDestroy => editor.core.emit 'destroy'

+        instantiate = =>
+            if isAgdaFile editor
+                editor.core = new Core editor
+                ev = editor.onDidDestroy =>
+                    editor.core.emit 'destroy'
+                    ev.dispose()
+            else if editor.core
+                editor.core.emit 'destroy'
+        instantiate()
+        editor.onDidChangePath => instantiate()

     # editor active/inactive event register, fuck Atom's event clusterfuck
     registerEditorActivation: ->


Taking the upload file as the POST body instead of multipart

--- src/api.coffee
+++ src/api.coffee
@@ -13,7 +13,7 @@
   res.end JSON.stringify {error: {message: message}}

-sendFileToSegmenter = (path,callback) ->
+sendFileToSegmenter = (stream,callback) ->
   callback_url = "http://192.168.201.196:3000/internal/add_segment"
   post_options =
     host: 'vbox'
@@ -21,14 +21,17 @@
     path: "/segment_ts/?callback_url=#{encodeURIComponent(callback_url)}"
     method: 'POST'

+  #post_options.host = 'localhost'
+  #post_options.port = 3000

-  console.log "sending file at path #{path} to:"
+
+  console.log "sending file to segment service:"
   console.log post_options
   post_request = http.request post_options, (res) ->
     if callback? then callback()

   # Pipe file into the post request
-  fs.createReadStream(path).pipe post_request
+  stream.pipe post_request

 module.exports = (app) ->
@@ -65,13 +68,9 @@
     livestream_id = req.params.id
     ls = livestream.getLivestream(livestream_id)

-    req.form.complete (err,fields,files) ->
-      if err
-        res.end err
-      else
-        # Forward along the file to the segmenter app
-        sendFileToSegmenter files.segment.path, ->
-          res.end "ok"
+    # Use the stream of the body data to send to the segmenter service
+    sendFileToSegmenter req, ->
+      res.end "ok"

   app.post '/internal/add_segment', (req,res) ->
     stream_id = req.param('stream')
@@ -84,23 +83,26 @@
     destdir = "#{__dirname}/../public/segmented/"
     filename = "#{stream_id}-#{Math.floor(Math.random()*1000000)}.ts"
     dest = destdir + filename
+    url = "/segmented/#{filename}"

     # Write out the file
-    console.log 'pipe'
-    console.log 'endpipe'
-    console.log dest
     f = fs.createWriteStream dest
     f.on 'close', ->
       console.log "Wrote file #{dest}"

       # Add link to the live stream
-      url = "/segmented/#{filename}"
       ls.addTs url

-      # Close the stream
+      # Close the request
       res.end 'ok'

     # Write out file
+    console.log "Writing to #{dest}"
     req.pipe(f)

+  # DEBUG: A pretend segment handler
+  app.post '/segment_ts/', (req,res) ->
+    console.log "got post request with callback: #{req.param('callback_url')}"
+    setTimeout (-> res.end()), 3000
+


remove old relationship model

remove old relationship model

--- src/app.coffee
+++ src/app.coffee
@@ -126,7 +126,6 @@
       @state
   )

-  Relationship = Backbone.Model.extend(urlRoot: "/relationships")

   Place = Backbone.Model.extend


reset provisioning progress to null when supervisor starts

--- src/app.coffee
+++ src/app.coffee
@@ -43,6 +43,8 @@
 			api_port: config.listenPort
 			api_secret: secret
 			supervisor_version: utils.supervisorVersion
+			provisioning_progress: null
+			provisioning_state: ''
 			# Retry the device info update every 5s until it finally succeeds.
 			5000
 		)


Improve condition for VCS status update

Improve condition for VCS status update

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -64,7 +64,7 @@
     return unless repo?

     @subscriptions.add repo.onDidChangeStatus (event) =>
-      @updateStatus(repo) if event.path.indexOf("#{@realPath}#{path.sep}") is 0
+      @updateStatus(repo) if @contains(event.path)
     @subscriptions.add repo.onDidChangeStatuses =>
       @updateStatus(repo)


Emit an "error" event only for the case where we have an invalid roomid. All types of error that might be considered a connection error emit a "disconnected" event, instead. If there are no listeners for this event an error will be throw.

--- src/bot.coffee
+++ src/bot.coffee
@@ -83,7 +83,7 @@
       url  = "ws://#{host}:#{port}/socket.io/websocket"
       @ws = new WebSocket(url)
       @ws.onmessage = @onMessage.bind(@)
-      @ws.onerror = @onError.bind(@)
+      @ws.onerror = @disconnect.bind(@)
       @ws.onclose = @onClose.bind(@)

   whichServer: (roomid, callback) ->
@@ -105,10 +105,10 @@
           callback.call(@, host, port)
         else
           @log "Failed to determine which server to use: #{dataStr}"
-          @onError new Error 'Error parsing server response'
+          @disconnect new Error 'Error parsing server response'
     .on 'error', (e) =>
       @log "whichServer error: #{e}"
-      @onError e
+      @disconnect e

   setTmpSong: (data) ->
@@ -118,8 +118,13 @@
       success : true

-  onError: (data) ->
-    @emit 'error', data
+  disconnect: (err) ->
+    @_isAuthenticated = false
+    @_isConnected = false
+    if @listeners('disconnected').length
+      @emit 'disconnected', err
+    else
+      throw err

   onClose: ->
@@ -164,10 +169,8 @@
     else
       activity = @lastActivity
     if @_isConnected and (Date.now() - activity) > @disconnectInterval
-      @_isAuthenticated = false
-      @_isConnected = false
       @log 'No response from server; is there a proxy/firewall problem?'
-      @onError new Error 'No response from server'
+      @disconnect new Error 'No response from server'
     else
       @updatePresence()


Refactor Node::walk.

Refactor Node::walk.

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -48,6 +48,23 @@
 Node::right = -> @children[1]
 Node::last  = -> @children[@children.length-1]

+# `walk()`
+# Traverses down a node and it's children.
+Node::walk  = (options, fn, parent=null) ->
+  fn parent, @  if parent
+
+  if options.last
+    @last().walk options, fn, @  if @last()?
+
+  @thenPart.walk options, fn, @  if @thenPart?
+  @elsePart.walk options, fn, @  if @elsePart?
+
+  if @cases
+    _.each @cases, (item) ->
+      item.statements.walk options, fn, item
+
+# `clone()`
+# Clones a given node.
 Node::clone = (hash) ->
   for i of @
     continue  if i in ['tokenizer', 'length', 'filename']
@@ -55,7 +72,7 @@

   new Node(@tokenizer, hash)

-# `inspect()`
+# `toHash()` + `inspect()`
 # For debugging
 Node::toHash = ->
   hash = {}
@@ -676,21 +693,8 @@
         @expression.type = Typenames['call_statement']

   'function': ->
-    walk = (node, fn, parent=null) ->
-      return  unless node
-
-      fn parent, node  if parent
-
-      walk node.last(), fn, node
-      walk node.thenPart, fn, node
-      walk node.elsePart, fn, node
-
-      if node.cases
-        _.each node.cases, (item) ->
-          walk item.statements, fn, item
-
     # *Unwrap the `return`s.*
-    walk @body, (parent, node) ->
+    @body.walk last: true, (parent, node) ->
       if node.isA('return') and node.value
         parent.children[parent.children.length-1] = node.value


add stylus support

add stylus support

Stylus is supported by highlightJS
(https://github.com/isagalaev/highlight.js/pull/555)

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -22,7 +22,7 @@
     singleLineComment: ['//']
     ignorePrefix:      '}'
     foldPrefix:        '^'
-
+
   CSS:
     nameMatchers:      ['.css']
     pygmentsLexer:     'css'
@@ -138,7 +138,7 @@
     multiLineComment:  ['<!--', '', '-->']
     ignorePrefix:      '}'
     foldPrefix:        '^'
-
+
   Jade:
     nameMatchers:      ['.jade']
     pygmentsLexer:     'jade'
@@ -310,6 +310,14 @@
     ignorePrefix:      '}'
     foldPrefix:        '^'

+  Stylus:
+    nameMatchers:      ['.styl']
+    highlightJS:       'stylus'
+    multiLineComment:  ['/*', '*', '*/']
+    singleLineComment: ['//']
+    ignorePrefix:      '}'
+    foldPrefix:        '^'
+
   Swift:
     nameMatchers:      ['.swift']
     pygmentsLexer:     'swift'


Display gutter markers only for valid violations

--- lib/lint-view.coffee
+++ lib/lint-view.coffee
@@ -66,6 +66,10 @@
     while view = @violationViews.shift()
       view.remove()

+  getValidViolationViews: ->
+    @violationViews.filter (violationView) ->
+      violationView.isValid
+
   updateGutterMarkers: ->
     return unless @gutterView.isVisible()

@@ -74,7 +78,7 @@

     return if @violationViews.length == 0

-    for violationView in @violationViews
+    for violationView in @getValidViolationViews()
       line = violationView.getCurrentBufferStartPosition().row
       klass = "lint-#{violationView.violation.severity}"
       @gutterView.addClassToLine(line, klass)
@@ -100,7 +104,7 @@
     currentCursorPosition = @editor.getCursor().getScreenPosition()

     # OPTIMIZE: Consider using binary search.
-    neighborViolationView = _[enumerationMethod] @violationViews, (violationView) ->
+    neighborViolationView = _[enumerationMethod] @getValidViolationViews(), (violationView) ->
       violationPosition = violationView.screenStartPosition
       violationPosition[comparingMethod](currentCursorPosition)


use Delete operator inside Change execute

No need for copypasta when this can be cleaner and more logical.

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -93,7 +93,8 @@
   complete: null
   vimState: null

-  constructor: (@editor, @vimState) ->
+  constructor: (@editor, @vimState, motion) ->
+    @motion = motion
     @complete = false

   isComplete: -> @complete
@@ -147,17 +148,8 @@
   #
   # Returns nothing.
   execute: (count=1) ->
-    _.times count, =>
-      if _.last(@motion.select())
-        @editor.getSelection().delete()
-
-      # FIXME: This should be fixed by atom/vim-mode#2
-      {row, column} = @editor.getCursorScreenPosition()
-      rowLength = @editor.getCursor().getCurrentBufferLine().length
-      @editor.moveCursorLeft() if column == rowLength
-
-    if @motion.isLinewise?()
-      @editor.setCursorScreenPosition([@editor.getCursor().getScreenRow(), 0])
+    operator = new Delete(@editor, @vimState, @motion)
+    operator.execute(count)

     @vimState.activateInsertMode()


removed commented out and debug code. add 'startedBy' to instance document.

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -28,12 +28,6 @@
   overallState = stateF doc, services
   overallState = 'failing' unless overallState
   Instances.update doc._id, $set: state: overallState
-
-  if overallState is 'undesired'
-    console.log '\n\nWHAAAAAAA\n\n'
-    console.log services.map (s) -> s.state
-    console.log '\n\n'
-  console.log 'OVERALL_STATE', overallState

   for serviceName, service of doc.services
     if service.state is 'running'
@@ -73,12 +67,12 @@
         (appVersion = labels['bigboat/application/version'])
           updateDoc.app = name: appName, version: appVersion

+      updateDoc.startedBy = startedBy if startedBy = labels['bigboat/startedBy']
+
       updateDoc['agent.url'] = val if val = labels['bigboat/agent/url']

       service = labels['bigboat/service/name']
       search = {name: name}
-      # if mappedState is 'running'
-      #   search["services.#{service}.state"] = $ne: 'stopping'

       updateDoc["services.#{service}.state"] = mappedState


Add install script

Add install script

--- src/gen.coffee
+++ src/gen.coffee
@@ -79,10 +79,18 @@
           g++
       # if parsed.global
     fs.appendFileSync @con, @cd
+    # Install
     if parsed.build != undefined
       # body...
       @build = parsed.build
-      fs.appendFileSync @con, 'echo Building Web-app...\n'
+      @install = parsed.build
+      fs.appendFileSync @con, 'echo Building Web-app...\necho Installing dependencies...\n'
+      inc = 0
+      while inc < install.length
+        fs.appendFileSync @con, 'echo '+build.script[b]+@enter
+        fs.appendFileSync @con, build.script[b]+@enter
+        b++
+      fs.appendFileSync @con, 'echo Building...\n'
       b = 0
       while b < build.script.length
         fs.appendFileSync @con, build.script[b]+@enter
@@ -111,7 +119,7 @@
       else if parsed.python == '3'
         console.log 'WARN: You are using Python 3.0. All python cmd commands must be ran using "python3"'
         fs.appendFileSync @docker, @from+'python3.0\n'
-  @dockerfile = 'COPY ./instances/web'+dirw+'\nCOPY .tubs/tub'+@id+'/start.sh /container/start.sh\nEXPOSE '+parsed.port+'\nCMD cd /container && sh ./start.sh'
+  @dockerfile = 'COPY '+dirw+' /container/app\nCOPY .tubs/tub'+@id+'/start.sh /container/start.sh\nEXPOSE '+parsed.port+'\nCMD cd /container && sh ./start.sh'
   fs.appendFileSync @docker, @dockerfile
   console.log 'Preparing to start...'
   run('sh', ['~/.web/tubs/build.sh', '.tubs/tub'+@id+'/Dockerfile', 'webos/tub'+@id, parsed.public+':'+parsed.port, 'webos/tub'+@id])


Removed duplicity

Removed duplicity

--- lib/somewhere.coffee
+++ lib/somewhere.coffee
@@ -9,7 +9,7 @@
     do @connect if @databasePath

   connect: ->
-    if @databasePath and fs.existsSync @databasePath
+    if fs.existsSync @databasePath
       @database = JSON.parse fs.readFileSync @databasePath, 'utf-8'

   clear: ->


Added xterm.js color support

Added xterm.js color support

--- lib/term-view.coffee
+++ lib/term-view.coffee
@@ -22,6 +22,30 @@
     _template.split(///\{\{\s*#{key}\s*\}\}///)
       .join data[key]
   , template.toString()
+
+createColorsStyleSheet = (parent, termID, colors) ->
+  termHTMLID = "term3-term-#{termID}"
+  title = "#{termHTMLID}-colors"
+  ss = document.querySelector("style[title=\"#{title}\"]")
+  if ss != null
+    ssEl.parentElement.removeChild(ssEl)
+  styles = "\##{termHTMLID} { color: red; }"
+  stylePrefix = "\##{termHTMLID} "
+  styles = []
+  addStyle = (s) -> styles.push(stylePrefix + s)
+
+  i = 0
+  for c in colors.slice(0, 16)
+    addStyle(".xterm-color-#{i} { color: #{c}; }")
+    addStyle(".xterm-bg-color-#{i} { background-color: #{c}; }")
+    i += 1
+  addStyle(".terminal { background: #{colors[16]}; color: #{colors[17]}}")
+  addStyle(".xterm-viewport { background: #{colors[16]}; color: #{colors[17]}}")
+
+  ssEl = document.createElement('style')
+  ssEl.title = title
+  ssEl.innerHTML = styles.join("\n")
+  parent.appendChild(ssEl)

 class TermView extends View
   constructor: (@opts={})->
@@ -79,6 +103,10 @@
     {cols, rows, cwd, shell, shellArguments, shellOverride, runCommand, colors, cursorBlink, scrollback} = @opts
     args = shellArguments.split(/\s+/g).filter (arg) -> arg

+    parent = this.get(0)
+
+    createColorsStyleSheet(parent, this.id, colors)
+
     @term = term = new Terminal {
       colors, cursorBlink, scrollback
     }
@@ -112,7 +140,10 @@
     term.on "focus", =>
       @emitter.emit "focus"

-    term.open this.get(0)
+    term.open parent
+
+    term.element.id = "term3-term-#{this.id}"
+
     term.fit()
     { cols, rows } = @getDimensions