fix segment symbol placement

fix segment symbol placement

--- src/ui/features.coffee
+++ src/ui/features.coffee
@@ -1,3 +1,37 @@
+segmentLength = (segment, save=false) ->
+  points = segment.geometry.coordinates
+  sum = 0
+  prev = points[0]
+  if save then prev.push(sum)
+
+  for point in points.slice(1)
+    dx = point[0] - prev[0]
+    dy = point[1] - prev[1]
+    delta = Math.sqrt(dx*dx + dy*dy)
+    if save then point.push(sum += delta)
+    prev = point
+
+  if save then segment.properties.length = sum
+  return sum
+
+
+along = (segment, distance) ->
+  points = segment.geometry.coordinates
+  prev = points[0]
+  for point in points.slice(1)
+    if point[3] >= distance
+      f = (distance - prev[3]) / (point[3] - prev[3])
+      dx = point[0] - prev[0]
+      dy = point[1] - prev[1]
+      target = [prev[0] + dx * f, prev[1] + dy * f]
+      return {
+        geometry: {
+          type: 'Point'
+          coordinates: target
+        }
+      }
+
+
 app.features = (options) ->
   map = options.map
   db = map.db
@@ -9,6 +43,8 @@

   segmentLayer = topojson.feature(db.topo, db.topo.objects.segments).features
   segmentMap = app.index(segmentLayer)
+  for segment in segmentLayer
+    segmentLength(segment, true)

   poiLayer = topojson.feature(db.topo, db.topo.objects.poi).features
   riversLayer = topojson.feature(db.topo, db.topo.objects.rivers).features
@@ -49,13 +85,12 @@
   renderSymbols = ->
     symbols.selectAll('.symbol').remove()

-    interval = 80 / map.sc / 1000
+    interval = 80 / map.sc
     segmentSymbols = []
     for segment in segmentLayer
-      continue
-      length = turf.lineDistance(segment, 'kilometers')
+      length = segment.properties.length
       for n in d3.range(0, d3.round(length / interval))
-        point = turf.along(segment, interval * (n + 0.5), 'kilometers')
+        point = along(segment, interval * (n + 0.5))
         point.properties = {symbols: segment.properties.symbols}
         [x, y] = map.projection(point.geometry.coordinates)
         if app.inside([x, y], [0, 0, map.width, map.height])


Updating src comments

Updating src comments

--- src/underthebus.coffee
+++ src/underthebus.coffee
@@ -1,15 +1,8 @@
 # Description
 #   Sometimes you need to quickly redirect blame
 #
-# Configuration:
-#   LIST_OF_ENV_VARS_TO_SET
-#
 # Commands:
-#   hubot hello - <what the respond trigger does>
-#   orly - <what the hear trigger does>
-#
-# Notes:
-#   <optional notes required for the script>
+#   under the bus - responds with image
 #
 # Author:
 #   Nick Peelman <nick@peelman.us>


:lipstick: Combine += into single interpolated string

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -33,18 +33,17 @@

 parseOptions = (args=[]) ->
   options = optimist(args)
-  usage = """
+  options.usage """

     apm - Atom Package Manager powered by https://atom.io

     Usage: apm <command>

     where <command> is one of:
+    #{wordwrap(4, 80)(Object.keys(commands).sort().join(', '))}.

+    Run `apm help <command>` to see the more details about a specific command.
   """
-  usage += wordwrap(4, 80)(Object.keys(commands).sort().join(', '))
-  usage += ".\n\nRun `apm help <command>` to see the more details about a specific command."
-  options.usage(usage)
   options.alias('v', 'version').describe('version', 'Print the apm version')
   options.alias('h', 'help').describe('help', 'Print this usage message')
   options.boolean('color').default('color', true).describe('color', 'Enable colored output')


fix merge issue with user admin missing

fix merge issue with user admin missing

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -61,6 +61,7 @@
   "doubtfire.units"
   "doubtfire.tasks"
   "doubtfire.projects"
+  "doubtfire.users"
 ])
 .config(($urlRouterProvider, $httpProvider) ->


Simplification

Simplification

--- src/core/kb.coffee
+++ src/core/kb.coffee
@@ -156,7 +156,7 @@
     # observable or lifecycle managed
     if ko.isObservable(obj) and _.isArray(array = kb.peek(obj))
       return obj.destroy() if obj.__kb_is_co or (obj.__kb_is_o and (obj.valueType() is kb.TYPE_COLLECTION))
-      ((array[index] = null; kb.release(value)) if kb.isReleaseable(value)) for index, value of array
+      (array[index] = null; kb.release(value)) for index, value of array when kb.isReleaseable(value)
       obj.dispose() if (typeof(obj.dispose) is 'function')
       return


Fix typo.

Fix typo.

--- src/cute-me.coffee
+++ src/cute-me.coffee
@@ -9,7 +9,7 @@
 #
 # Commands:
 #   hubot cute me - Receive a cute thing
-#   unicorn chaser - Receieve a cute thing
+#   unicorn chaser - Receive a cute thing
 module.exports = (robot) ->
   robot.respond /cute me/, (msg) ->
     cuteMe(msg)


add support for non-local docker daemon host

--- src/decking.coffee
+++ src/decking.coffee
@@ -12,7 +12,9 @@

 MultiplexStream = require "./multiplex_stream"

-docker = new Docker socketPath: "/var/run/docker.sock"
+host = process.env.DOCKER_HOST || "/var/run/docker.sock"
+
+docker = new Docker socketPath: host

 log = Logger.log
 logStream = (name, data) -> log "#{Table.padName(name, "(", ")")} #{data}"


Fix return type in documentation.

Fix return type in documentation.

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -212,7 +212,7 @@
   # @param head [Array]
   # @param tail [Array]
   # @param strengthAndWeight [Array]
-  # return [String]
+  # @return [String]
   #
   linearConstraint: (head, tail, strengthAndWeight) ->
     firstExpression = head


Bug fix.

Bug fix.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -120,7 +120,8 @@
       # Updates the value.
       value = values[namespace]
       ctx = {
-        value: if value is undefined then init else value
+        # TODO: test the $_.clone
+        value: if value is undefined then $_.clone init else value
       }
       changed = if event is 'enter'
         fn.call ctx, values_, []


Add --production option to apm install

Add --production option to apm install

It is just a pass through to npm install

Closes #270

--- src/install.coffee
+++ src/install.coffee
@@ -49,6 +49,7 @@
     options.boolean('check').describe('check', 'Check that native build tools are installed')
     options.boolean('verbose').default('verbose', false).describe('verbose', 'Show verbose debug information')
     options.string('packages-file').describe('packages-file', 'A text file containing the packages to install')
+    options.boolean('production').describe('production', 'Do not install dev depedencies')

   installNode: (callback) =>
     installNodeArgs = ['install']
@@ -107,6 +108,7 @@
     installArgs.push("--arch=#{config.getNodeArch()}")
     installArgs.push('--silent') if options.argv.silent
     installArgs.push('--quiet') if options.argv.quiet
+    installArgs.push('--production') if options.argv.production

     if vsArgs = @getVisualStudioFlags()
       installArgs.push(vsArgs)
@@ -168,6 +170,7 @@
     installArgs.push("--arch=#{config.getNodeArch()}")
     installArgs.push('--silent') if options.argv.silent
     installArgs.push('--quiet') if options.argv.quiet
+    installArgs.push('--production') if options.argv.production

     if vsArgs = @getVisualStudioFlags()
       installArgs.push(vsArgs)


Fixed error in docs for keep alive.

Fixed error in docs for keep alive.

--- src/scripts/keep-alive.coffee
+++ src/scripts/keep-alive.coffee
@@ -2,7 +2,7 @@
 # This is specifically to keep certain heroku apps from going to sleep
 #
 # keep `http://ninjas-20.herokuapp.com` alive - Add inputted url to the collection of urls set to be pinged
-# keep `http://ninjas-20.herokuapp.com` alive - Remove inputted url to the collection of urls set to be pinged
+# don't keep `http://ninjas-20.herokuapp.com` alive - Remove inputted url to the collection of urls set to be pinged
 # what are you keeping alive - Show list of urls being kept alive


fixed wrong indent

fixed wrong indent

--- client/crypto.coffee
+++ client/crypto.coffee
@@ -166,7 +166,7 @@
         self.busy = false
         self.flush()

-       done: (result) ->
+      done: (result) ->
         self.current?.onDone? null, result

       error: (error) ->


preserve dialog body template

preserve dialog body template

--- client/images.coffee
+++ client/images.coffee
@@ -3,6 +3,9 @@
 my_uploader = null # variable local to this script
 uploader_state_changed = new Deps.Dependency
 upload_progress_dialog = null
+
+# global
+window.image_upload_template = null

 # ---- Template.binary_data_from_id_block -------------

@@ -200,7 +203,10 @@
       save_label: "Upload"
       render_complete: (parent_template) ->
         parent_template.$("#modal-dialog-save").on("click", (event) ->
-          insert_image_save_func(parent_template,
+          template = window.image_upload_template
+          if !template?
+            return
+          insert_image_save_func(template,
                send_coll, my_id, "images")
         )

@@ -288,7 +294,12 @@
     else
       handle_file_step_two( chosen_file, template )

+Template.InsertImageDialog.destroyed = ->
+  window.image_upload_template = null
+
 Template.InsertImageDialog.created = ->
+  window.image_upload_template = this
+
   @selected_files = new ReactiveVar()
   @selected_files.set([])


make assignment operators a single token


commenting coffee-script.coffee for documentation

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -1,4 +1,12 @@
-# Set up for both the browser and the server.
+# CoffeeScript can be used both on the server, as a command-line compiler based
+# on Node.js/V8, or to run CoffeeScripts directly in the browser. This module
+# contains the main entry functions for tokenzing, parsing, and compiling source
+# CoffeeScript into JavaScript.
+#
+# If included on a webpage, it will automatically sniff out, compile, and
+# execute all scripts present in `text/coffeescript` tags.
+
+# Set up dependencies correctly for both the server and the browser.
 if process?
   process.mixin require 'nodes'
   path:         require 'path'
@@ -9,7 +17,27 @@
   parser: exports.parser
   this.exports: this.CoffeeScript: {}

-# Thin wrapper for Jison compatibility around the real lexer.
+# The current CoffeeScript version number.
+exports.VERSION: '0.5.4'
+
+# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
+# compiler.
+exports.compile: (code, options) ->
+  (parser.parse lexer.tokenize code).compile options
+
+# Tokenize a string of CoffeeScript code, and return the array of tokens.
+exports.tokens: (code) ->
+  lexer.tokenize code
+
+# Tokenize and parse a string of CoffeeScript code, and return the AST. You can
+# then compile it by calling `.compile()` on the root, or traverse it by using
+# `.traverse()` with a callback.
+exports.nodes: (code) ->
+  parser.parse lexer.tokenize code
+
+# The real Lexer produces a generic stream of tokens. This object provides a
+# thin wrapper around it, compatible with the Jison API. We can then pass it
+# directly as a "Jison lexer".
 parser.lexer: {
   lex: ->
     token: @tokens[@pos] or [""]
@@ -24,22 +52,10 @@
   showPosition: -> @pos
 }

-exports.VERSION: '0.5.4'
-
-# Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
-exports.compile: (code, options) ->
-  (parser.parse lexer.tokenize code).compile(options)
-
-# Just the tokens.
-exports.tokens: (code) ->
-  lexer.tokenize code
-
-# Just the nodes.
-exports.nodes: (code) ->
-  parser.parse lexer.tokenize code
-
-# Activate CoffeeScript in the browser by having it compile and eval
-# all script tags with a content-type of text/coffeescript.
+# Activate CoffeeScript in the browser by having it compile and evaluate
+# all script tags with a content-type of `text/coffeescript`. This happens
+# on page load. Unfortunately, the text contents of remote scripts cannot be
+# accessed from the browser, so only inline script tags will work.
 if document? and document.getElementsByTagName
   process_scripts: ->
     for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'


Strip UTF-8 BOM when require'ing .coffee modules.

Allows people to author their .coffee files with UTF-8 BOMs at the start, because sometimes that happens. Fixes #798.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -12,10 +12,13 @@
 {parser}         = require './parser'
 vm               = require 'vm'

+stripBOM = (content) ->
+  if content.charCodeAt 0 is 0xFEFF then content.substring 1 else content
+
 # TODO: Remove registerExtension when fully deprecated.
 if require.extensions
   require.extensions['.coffee'] = (module, filename) ->
-    content = compile fs.readFileSync(filename, 'utf8'), {filename}
+    content = compile stripBOM fs.readFileSync(filename, 'utf8'), {filename}
     module._compile content, filename
 else if require.registerExtension
   require.registerExtension '.coffee', (content) -> compile content


config提交为最原始状态

config提交为最原始状态

--- config/config.coffee
+++ config/config.coffee
@@ -12,6 +12,6 @@
   #数据库密码
   dbPassword:''
   #是否初始化过
-  isFirst: true
-
+  isFirst: false
+
 module.exports = config


Slider: Added 'onDragstart' parameter.

Slider: Added 'onDragstart' parameter.

--- src/elements/Slider/Slider.coffee
+++ src/elements/Slider/Slider.coffee
@@ -16,6 +16,8 @@
 				default: 100
 				check: (v) ->
 					isInteger(v)
+			onDragstart:
+				check: Function
 			onDragging:
 				check: Function
 			onDrop:
@@ -57,6 +59,7 @@
 				@addClass('cui-slider--dragging')
 				@__last_diff_x = 0
 				@__start_value = @__value
+				@_onDragstart?(@, @__start_value)
 			dragstop: =>
 				@setValue(@__start_value)
 				@removeClass('cui-slider--dragging')


Added a few lines of German translations (#5302)

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -54,9 +54,9 @@
 #    for_leaders_subblurb3: "Whether you want to build a Middle School coding elective, a CTE pathway, or an AP Computer Science Principles class, CodeCombat is tailored to suit your needs."
 #    for_leaders_subtitle4: "Real-World Skills"
 #    for_leaders_subblurb4: "Students build grit and develop a growth mindset through coding challenges that prepare them for the 500K+ open computing jobs."
-#    for_teachers_title: "For Teachers"
+    for_teachers_title: "Für Lehrer"
 #    for_teachers_blurb: "Tools to Unlock Student Potential"
-#    for_teachers_subtitle1: "Project-Based Learning"
+    for_teachers_subtitle1: "Projektbasiertes Lernen"
 #    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
 #    for_teachers_subtitle2: "Teacher Dashboard"
 #    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
@@ -68,16 +68,16 @@
 #    get_started: "Get started"
 #    global_title: "Join Our Global Community of Learners and Educators"
 #    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
-#    global_subtitle3: "Teachers"
-#    global_subtitle4: "Countries"
+    global_subtitle2: "Codezeilen"
+    global_subtitle3: "Lehrer"
+    global_subtitle4: "Länder"
 #    go_to_my_classes: "Go to my classes"
 #    go_to_my_courses: "Go to my courses"
 #    quotes_quote1: "Name any program online, I’ve tried it. None of them match up to CodeCombat. Any teacher who wants their students to learn how to code... start here!"
 #    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
 #    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
 #    quotes_quote4: "The feedback … has been so positive that we are structuring a computer science class around CodeCombat. The program really engages the students with a gaming style platform that is entertaining and instructional at the same time. Keep up the good work, CodeCombat!"
-#    see_example: "See example"
+    see_example: "Beispiel ansehen"
     slogan: "Das beste Spiel um Programmieren zu lernen." # {change}
 #    teach_cs1_free: "Teach CS1 Free"
 #    teachers_love_codecombat_title: "Teachers Love CodeCombat"
@@ -133,7 +133,7 @@
     get_started_title: "Erstellen Sie noch heute ihre Klasse"
     get_started_subtitle: "Erstellen Sie eine Klasse, fügen Sie Schüler hinzu und verfolgen Sie ihren Fortschritt während sie Informatik lernen."
     request_demo: "Demo anfragen"
-#    request_quote: "Request a Quote"
+    request_quote: "Zitat anfragen"
     setup_a_class: "Eine Klasse erstellen"
     have_an_account: "Account vorhanden?"
     logged_in_as: "Sie sind zurzeit eingeloggt als"
@@ -154,8 +154,8 @@

   nav:
 #    educators: "Educators"
-#    follow_us: "Follow Us"
-#    general: "General"
+    follow_us: "Folge Uns"
+    general: "Hauptmenü"
     map: "Karte"
     play: "Spielen" # The top nav bar entry where players choose which levels to play
     community: "Community"


Run copy-i18n-tags

Run copy-i18n-tags

--- app/locale/rot13.coffee
+++ app/locale/rot13.coffee
@@ -1654,8 +1654,8 @@
     certificate_btn_print: "Cevag"
     certificate_btn_toggle: "Gbttyr"
     ask_next_course: "Jnag gb cynl zber? Nfx lbhe grnpure sbe npprff gb gur arkg pbhefr."
-    set_start_locked_level: "Nffvta hc gb yriry" # {punatr}
-    no_level_limit: "--" # {punatr}
+    set_start_locked_level: "Nffvta hc gb yriry"
+    no_level_limit: "--"

   project_gallery:
     no_projects_published: "Or gur svefg gb choyvfu n cebwrpg va guvf pbhefr!"


Add feature spec to verify original error messages from command handlers can be accessed when communicating over sockets

--- src/feature.spec.coffee
+++ src/feature.spec.coffee
@@ -42,7 +42,8 @@

       exampleContext.addCommandHandlers
         CommandWhichRejects: ->
-          throw new Error 'The error message'
+          error = throw new Error 'The error message'
+          error.customProperty = 'customValue'
         DoSomething: doSomethingStub

       exampleContext.initialize()
@@ -51,22 +52,20 @@
         exampleRemote.setClient socketIORemoteClient

-    it 'should send error instances serialized with "name" and "message" properties', (done) ->
+    it 'should be possible to access the original error message of an error from a command handler', ->
       exampleRemote.command 'CommandWhichRejects'
       .catch (error) ->
         expect(error instanceof Error).to.be.true
-        expect(error.name).to.equal 'Error'
         expect(error.message).to.contain 'The error message'
-        done()
+        expect(error.originalErrorMessage).to.equal 'The error message'

-    it 'should be possible to receive and execute commands', (done) ->
+    it 'should be possible to receive and execute commands', ->
       exampleRemote.command 'CreateSomething'
       .then (aggregateId) ->
         exampleRemote.command 'DoSomething', aggregateId: aggregateId
-        .then ->
-          expect(doSomethingStub).to.have.been.calledOnce
-          done()
+      .then ->
+        expect(doSomethingStub).to.have.been.calledOnce

     it 'should be possible to subscribe handlers to domain events', (done) ->


Minor updates.

Minor updates.

--- src/fibers-utils.coffee
+++ src/fibers-utils.coffee
@@ -45,6 +45,7 @@

   (args...) ->
     fiber = $fiber.current
+    throw new Error 'not running in a fiber' unless fiber?

     args.push (error, result) ->
       if error?
@@ -60,6 +61,7 @@
 # Note: if the *error* event is emitted, this function will throw.
 $waitEvent = (emitter, event) ->
   fiber = $fiber.current
+  throw new Error 'not running in a fiber' unless fiber?

   errorHandler = null
   handler = (args...) ->
@@ -77,7 +79,6 @@
 # Waits for a promise or a thunk to end.
 $wait = (value) ->
   fiber = $fiber.current
-
   throw new Error 'not running in a fiber' unless fiber?

   if $isPromise value
@@ -93,6 +94,8 @@
       else
         fibre.run result
   else
+    # TODO: handle array and object of promises/thunks.
+
     # No idea what is it, just forwards.
     return value


refactor

refactor

--- app/models/Level.coffee
+++ app/models/Level.coffee
@@ -104,31 +104,28 @@
       if isHero and placeholderComponent = placeholders[defaultThangComponent.original]
         placeholdersUsed[placeholderComponent.original] = true
         placeholderConfig = placeholderComponent.config ? {}
+        levelThangComponent.config ?= {}
+        config = levelThangComponent.config
         if placeholderConfig.pos  # Pull in Physical pos x and y
-          levelThangComponent.config ?= {}
-          levelThangComponent.config.pos ?= {}
-          levelThangComponent.config.pos.x = placeholderConfig.pos.x
-          levelThangComponent.config.pos.y = placeholderConfig.pos.y
-          levelThangComponent.config.rotation = placeholderConfig.rotation
+          config.pos ?= {}
+          config.pos.x = placeholderConfig.pos.x
+          config.pos.y = placeholderConfig.pos.y
+          config.rotation = placeholderConfig.rotation
         else if placeholderConfig.team  # Pull in Allied team
-          levelThangComponent.config ?= {}
-          levelThangComponent.config.team = placeholderConfig.team
+          config.team = placeholderConfig.team
         else if placeholderConfig.significantProperty  # For levels where we cheat on what counts as an enemy
-          levelThangComponent.config ?= {}
-          levelThangComponent.config.significantProperty = placeholderConfig.significantProperty
+          config.significantProperty = placeholderConfig.significantProperty
         else if placeholderConfig.programmableMethods
           # Take the ThangType default Programmable and merge level-specific Component config into it
           copy = $.extend true, {}, placeholderConfig
-          programmableProperties = levelThangComponent.config?.programmableProperties ? []
+          programmableProperties = config?.programmableProperties ? []
           copy.programmableProperties = _.union programmableProperties, copy.programmableProperties ? []
-          levelThangComponent.config = _.merge copy, levelThangComponent.config
+          config = _.merge copy, config
         else if placeholderConfig.extraHUDProperties
-          levelThangComponent.config ?= {}
-          levelThangComponent.config.extraHUDProperties = _.union(levelThangComponent.config.extraHUDProperties ? [], placeholderConfig.extraHUDProperties)
+          config.extraHUDProperties = _.union(config.extraHUDProperties ? [], placeholderConfig.extraHUDProperties)
         else if placeholderConfig.voiceRange  # Pull in voiceRange
-          levelThangComponent.config ?= {}
-          levelThangComponent.config.voiceRange = placeholderConfig.voiceRange
-          levelThangComponent.config.cooldown = placeholderConfig.cooldown
+          config.voiceRange = placeholderConfig.voiceRange
+          config.cooldown = placeholderConfig.cooldown

     if isHero
       if equips = _.find levelThang.components, {original: LevelComponent.EquipsID}


Removed bot username condition. Controlling via env var

--- src/jirabot.coffee
+++ src/jirabot.coffee
@@ -35,8 +35,8 @@
     jiraIgnoreUsers = "jira|github"

   robot.hear /.*\b(([A-Za-z]+)-[\d]+)\b.*/, (msg) ->
-    # ignore if the user exists in 'jiraIgnoreUsers' or if the use is a bot (we don't want bots talking to each other. Bot fights are ugly)
-    return if msg.message.user.name.match(new RegExp(jiraIgnoreUsers, "gi")) || msg.message.user.name.toLowerCase().indexOf("bot") > -1
+    # ignore if the user exists in 'jiraIgnoreUsers'
+    return if msg.message.user.name.match(new RegExp(jiraIgnoreUsers, "gi"))
     getIssues(msg)

   getIssues = (msg) ->


project/memory calc: no need to add rss_huge, and total_cache.

--- src/smc-project/kucalc.coffee
+++ src/smc-project/kucalc.coffee
@@ -161,8 +161,10 @@

     }, (err, res) ->
         kib = 1024 # convert to kibibyte
-        status.memory.rss  += (res.memory.total_rss ? 0 + stats.total_rss_huge ? 0) / kib
-        status.memory.cache = (res.memory.cache ? 0) / kib
+        # total_rss includes total_rss_huge
+        # Ref: https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
+        status.memory.rss  += (res.memory.total_rss ? 0) / kib
+        status.memory.cache = (res.memory.total_cache ? 0) / kib
         status.memory.limit = (res.memory.hierarchical_memory_limit ? 0) / kib
         status.cpu.usage    = res.cpu
         status.oom_kills    = res.oom


webapp/account settings: add toggle button for jupyter_classic editor setting

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -751,6 +751,7 @@
     show_trailing_whitespace  : 'show spaces at ends of lines'
     spaces_instead_of_tabs    : 'send 4 spaces when the tab key is pressed'
     extra_button_bar          : 'more editing functions (mainly in Sage worksheets)'
+    jupyter_classic           : 'use legacy Jupyter notebook implementation'

 EditorSettingsCheckboxes = rclass
     displayName : 'Account-EditorSettingsCheckboxes'


Validators: new custom validator for custom preferences per group

--- workers/social/lib/social/models/group/validators.coffee
+++ workers/social/lib/social/models/group/validators.coffee
@@ -103,3 +103,11 @@
         as         : { $in: [ 'owner', 'admin' ] }

       Relationship.count relSelector, createExistenceCallback callback
+
+    custom: (customPermission) ->
+
+      (client, group, rest..., callback) ->
+
+        Validators.group.admin client, group, rest..., (err, isAdmin) ->
+          callback null, yes  if isAdmin
+          callback null, !!group.getAt "customize.#{customPermission}"


social: return username after password recovery so client can login  user

--- workers/social/lib/social/models/passwordrecovery.coffee
+++ workers/social/lib/social/models/passwordrecovery.coffee
@@ -197,7 +197,8 @@
           return callback err  if err
           user.changePassword newPassword, (err)->
             return callback err  if err
-            JPasswordRecovery.invalidate {username}, callback
+            JPasswordRecovery.invalidate {username}, (err)->
+              callback err, username

   @resetPassword$ = secure (client, token, newPassword, callback)->


If the GitHub API request fails, log it and show the error page (instead of crashing the app).

--- viewer/app.coffee
+++ viewer/app.coffee
@@ -1,4 +1,5 @@
 cli                 = require 'cli'
+sys                 = require 'sys'

 { htmlResponse }    = require '../src/http_utils'
 renderer            = require '../src/renderer'
@@ -9,6 +10,10 @@
 getGist = (url, cb) ->
     GIST_API = 'https://api.github.com/gists'
     post_req = rest.get(GIST_API + url)
+    post_req.on 'error', (err, response) ->
+        sys.puts('getGist error:')
+        sys.puts(err)
+        sys.puts(response)
     post_req.on 'complete', (data, response) ->
         cb(data, response)

@@ -87,7 +92,7 @@
             url = url.substring(0, url.length - 1)

         getGist url, (data, github_response) ->
-            if github_response.statusCode is 200 and validGist(data.files)
+            if github_response?.statusCode is 200 and validGist(data.files)
                 content = renderer
                     style       : data.files['style.styl'].content
                     script      : data.files['script.coffee'].content
@@ -96,7 +101,7 @@
                     extra_body  : protoDisplayTag(url, data)
                 htmlResponse(response, content)
             else
-                raw_response = github_response.raw.toString()
+                raw_response = github_response?.raw.toString()
                 try
                     github_response_content = JSON.stringify((JSON.parse(raw_response)), null, 4)
                 catch e
@@ -110,7 +115,7 @@

                     GET https://api.github.com/gists#{ url }
-                    #{ github_response.statusCode }
+                    #{ github_response?.statusCode }

                     #{ github_response_content }


Fix typo in dom#on method

Fix typo in dom#on method

--- src/lib/dom.coffee
+++ src/lib/dom.coffee
@@ -124,11 +124,11 @@
   on: (eventName, listener) ->
     @node.addEventListener(eventName, (event) ->
       arg = if lastKeyEvent and (eventName == 'keydown' or eventName == 'keyup') then lastKeyEvent else event
-      propogate = listener(arg)
-      unless propogate
+      propagate = listener(arg)
+      unless propagate
         event.preventDefault()
         event.stopPropagation()
-      return propogate
+      return propagate
     )
     return this


log in user with trust token

log in user with trust token

--- apps/auth/routes.coffee
+++ apps/auth/routes.coffee
@@ -13,7 +13,7 @@
   res.redirect '/'

 @redirectAfterLogin = (req, res) ->
-  res.redirect req.body['redirect-to'] or req.session.signupReferrer or '/'
+  res.redirect req.body['redirect-to'] or req.get('Referrer') or '/'

 @loginWithTrustToken = (req, res) ->
   request.post(SECURE_ARTSY_URL + '/oauth2/access_token').send(
@@ -23,6 +23,6 @@
     code          : req.query['token']
     ).end((err, response) ->
       (err or response?.body.error_description)
-      new CurrentUser(accessToken: response?.body.access_token)
-      res.redirect req.query['redirect-to'] or req.session.signupReferrer or '/'
+      req.login new CurrentUser(accessToken: token), ->
+        res.redirect req.query['redirect-to'] or req.get('Referrer') or '/'
   )


more caching

more caching

--- apps/home/routes.coffee
+++ apps/home/routes.coffee
@@ -13,7 +13,9 @@
   indices = new Indices []

   Q.all [
-    blocks.fetch data: per: 10
+    blocks.fetch
+      data: per: 10
+      cache: true
     contracts.fetch()
     indices.fetch()
   ]


Tweak font settings in Atom

Tweak font settings in Atom


fix for mail job complete dev URLs again

--- noddy/service/oabutton/mail.coffee
+++ noddy/service/oabutton/mail.coffee
@@ -6,15 +6,15 @@
   if not job.email and job.user
     usr = API.accounts.retrieve job.user
     job.email = usr.emails[0].address
+  tmpl = API.mail.template 'bulk_complete.html'
+  sub = API.service.oab.substitute tmpl.content, {_id: job._id, useremail: job.email, jobname: job.name ? job._id}
   API.mail.send
     service: 'openaccessbutton'
-    template: 'bulk_complete.html'
-    from: API.settings.service.openaccessbutton.mail.from
+    html: sub.content
+    subject: sub.subject
+    from: sub.from ? API.settings.service.openaccessbutton.mail.from
     to: job.email
-    vars:
-      _id: job._id,
-      useremail: job.email
-      jobname: job.name ? job._id
+

 API.service.oab.dnr = (email,add,refuse) ->
   return oab_dnr.search('*')?.hits?.hits if not email? and not add?


'neft:style' attr now is immutable

'neft:style' attr now is immutable

--- style.coffee
+++ style.coffee
@@ -39,13 +39,7 @@
 			@updateText()

 	attrsChangeListener = (name, oldValue) ->
-		if name is 'neft:style'
-			@reloadItem()
-			if @file.isRendered
-				@render()
-				@findItemParent()
-				@findItemIndex()
-		else if name is 'href' and @isLink()
+		if name is 'href' and @isLink()
 			@item?.linkUri = @getLinkUri()

 		if @attrs?[name]
@@ -423,23 +417,7 @@
 		unless utils.isClient
 			return

-		if @item and @isAutoParent
-			@item.parent = null
-
-		if @item
-			while elem = @textWatchingNodes.pop()
-				if 'onTextChange' of elem
-					elem.onTextChange.disconnect textChangeListener, @
-				if 'onChildrenChange' of elem
-					elem.onChildrenChange.disconnect textChangeListener, @
-				if 'onVisibleChange' of elem
-					elem.onVisibleChange.disconnect textChangeListener, @
-
-			if @attrs
-				@attrsClass.disable()
-				@attrsClass.target = null
-
-		wasAutoParent = @isAutoParent
+		assert.notOk @item

 		id = @node.attrs.get 'neft:style'
 		assert.isString id
@@ -469,8 +447,7 @@
 					loop
 						scope = parent?.scope or windowStyle
 						if (scope is windowStyle and file is 'view') or (parent and parent.node.attrs.get('neft:style') is parentId)
-							@item = scope?.objects[subid]# or scope?.item.$?[subid]
-						# @item ?= scope?.?(id)
+							@item = scope?.objects[subid]
 						if @item or ((not parent or not (parent = parent.parent)) and scope is windowStyle)
 							break


removes start conditional, we're not using it for anything now

--- start.coffee
+++ start.coffee
@@ -5,5 +5,4 @@

 console.log("[NODE_ENV] " + process.env.NODE_ENV)

-if (process.env.NODE_ENV == "development")
-  runCommand("gulp", ['server'])
+runCommand("gulp", ['server'])


close popup on click

close popup on click

--- app/assets/javascripts/helpers/map_search_widget.coffee
+++ app/assets/javascripts/helpers/map_search_widget.coffee
@@ -14,7 +14,7 @@
       accessToken: @keys.mapboxPublicToken,
       minZoom: 2,
       maxZoom: 8
-      closePopupOnClick: false
+      closePopupOnClick: true
     @map.setView [40.0, -20.0], 2

   attachMapToModelChange: ->


[improvement] Reset text and tags but not customer when submitting a feedback (4m)

--- app/assets/javascripts/houston/support_form/view.coffee
+++ app/assets/javascripts/houston/support_form/view.coffee
@@ -36,12 +36,14 @@
     params.tags = $('#new_feedback_tags').selectedTags()
     $.post "/feedback/by_project/#{@project.slug}", params
       .success =>
-        @clearComment()
         alertify.success "Comment created"
+        $('#new_feedback_tags').val('')
+        $('#new_feedback_text').val('').focus()
       .error ->
         console.log 'error', arguments

   clearComment: (e)->
     e.preventDefault() if e
     $('#new_feedback_form').reset()
+    $('#new_feedback_customer').focus().select()


one jasmine test failing

one jasmine test failing

--- app/assets/javascripts/spec/piousbox/cities_spec.coffee
+++ app/assets/javascripts/spec/piousbox/cities_spec.coffee
@@ -15,7 +15,13 @@
     $('body').append( div_left_container.append(div_inner.append(div_calendar_container)) )
     $('body').append( div_map_container )
     $('body').append( div_reports_show )
-
+
+    right_menu = $('<ul>').append($('<li>').addClass('galleries_link'))
+    $('body').append( right_menu )
+
+    ns = $('<div>').addClass('ns').attr('n_reports', 0)
+    $('body').append( ns )
+
   afterEach ->
     $("#main").remove()
     $(".map").remove()
@@ -59,11 +65,13 @@
       expect( n_active_li_2 ).toBe( 0 )

     it 'disables links for galleries if there are no galleries', ->
+      rio = new Models.City('rio')
       $(".ns").attr("n_galleries", 0)
       $("li.galleries_link").css('display', 'block')
+      expect( $('li.galleries_link').css('display') ).toBe( 'block' )
       U.views.cities_right_manu = new Views.Cities.RightMenu( model: rio )
       expect( $("li.galleries_link").css('display') ).toBe( 'hide' )
-
+
     it 'click galleries, click venues', ->
       rio = new Models.City('rio')
       U.views.cities.right_menu = new Views.Cities.RightMenu( model: rio )


conditional kanban joyride step if the user has creation perms

--- app/modules/components/joy-ride/joy-ride.service.coffee
+++ app/modules/components/joy-ride/joy-ride.service.coffee
@@ -80,7 +80,7 @@
                       }
                   }
               ]
-
+
               if @checkPermissionsService.check('add_us')
                   steps.push({
                       element: '.new-us',
@@ -94,7 +94,7 @@
               return steps

            kanban: () =>
-              return [
+              steps = [
                   {
                       element: '.kanban-table-inner',
                       position: 'bottom',
@@ -110,19 +110,23 @@
                           title: @translate.instant('JOYRIDE.KANBAN.STEP2.TITLE')
                           text: @translate.instant('JOYRIDE.KANBAN.STEP2.TEXT')
                       }
-                  },
-                  {
-                      element: '.icon-plus',
-                      position: 'bottom',
-                      joyride: {
-                          title: @translate.instant('JOYRIDE.KANBAN.STEP3.TITLE')
-                          text: [
-                              @translate.instant('JOYRIDE.KANBAN.STEP3.TEXT1'),
-                              @translate.instant('JOYRIDE.KANBAN.STEP3.TEXT2'),
-                          ]
-                      }
                   }
               ]
+
+              if @checkPermissionsService.check('add_us')
+                  steps.push({
+                        element: '.icon-plus',
+                        position: 'bottom',
+                        joyride: {
+                            title: @translate.instant('JOYRIDE.KANBAN.STEP3.TITLE')
+                            text: [
+                                @translate.instant('JOYRIDE.KANBAN.STEP3.TEXT1'),
+                                @translate.instant('JOYRIDE.KANBAN.STEP3.TEXT2'),
+                            ]
+                        }
+                    })
+
+              return steps
       }

     get: (name) ->


enable document service for web maps

enable document service for web maps

--- app/scripts/components/localApi/document_service.coffee
+++ app/scripts/components/localApi/document_service.coffee
@@ -39,7 +39,7 @@
     return [] unless currentJson?.data
     indexes = _.map history, (historyEntry, idx) =>
       snippetJson = @searchSnippet(historyEntry.document, snippetModelId)
-      if snippetJson?.data && !@_isEqualChoropleth(snippetJson.data, currentJson.data)
+      if snippetJson?.data && !@_isEqual(snippetJson.identifier, snippetJson.data, currentJson.data)
         return idx
       else
         return -1
@@ -213,6 +213,13 @@
     return searchedSnippetJson

-  _isEqualChoropleth: (a, b) ->
-    _.every ['projection', 'data', 'map', 'mappingPropertyOnMap', 'valueProperty', 'colorScheme', 'colorSteps'], (definingProperty) ->
-      _.isEqual(a[definingProperty], b[definingProperty])
+  _isEqual: (type, a, b) ->
+    if type == 'livingmaps.choropleth'
+      _.every ['projection', 'data', 'map', 'mappingPropertyOnMap', 'valueProperty', 'colorScheme', 'colorSteps'], (definingProperty) ->
+        _.isEqual(a[definingProperty], b[definingProperty])
+    else if type == 'livingmaps.map'
+      _.every ['center', 'tiles', 'markers'], (definingProperty) ->
+        _.isEqual(a[definingProperty], b[definingProperty])
+    else
+      log.debug "Getting History for possibly unsupported snippet type #{type}"
+      _.isEqual(a, b)


it's enough to just log

it's enough to just log

--- lib/Logger.coffee
+++ lib/Logger.coffee
@@ -7,7 +7,6 @@
     fs.appendFile @logFile, str, (err) ->
       if err
         throw err
-      console.log "Logged", str

   formatCommand: (cmd) ->
     utils.formatObj cmd


Replace to valid method

Replace to valid method

Replace from Ripper.isContainsLocationData to
Ripper.isEqualsLocationData

--- lib/Ripper.coffee
+++ lib/Ripper.coffee
@@ -55,7 +55,10 @@
           target = child.index
           return false
       else if child instanceof Literal
-        if @isContainsLocationData child, targetLocationData #TODO use @isEqualsLocationData()
+        # if @isContainsLocationData child, targetLocationData #TODO use @isEqualsLocationData()
+        #   unless @isEqualsLocationData child.locationData, targetLocationData
+        #     console.log inspect child
+        if @isEqualsLocationData child.locationData, targetLocationData
           target = child
           return false


Associate Facebook request ID with our internal ID

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -176,6 +176,7 @@
         postMethod(carrotResponse.fb_data,
           (fbResponse) =>
             if fbResponse
+              @ajaxPost("#{@scheme}://posts.gocarrot.com/#{carrotResponse.request_id}/ids", {platform_id: fbResponse.request})
               for receivingUser in fbResponse.to
                 @ajaxPost("#{@scheme}://parsnip.gocarrot.com/request_send", {platform_id: carrotResponse.request_id, posting_user_id: @udid, user_id: receivingUser})


Allow sendRequest API to take optional data that is passed directly to the underlying facebook call

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -173,7 +173,8 @@
       }
       @postSignedRequest("/me/request.json", params, (jqXHR) =>
         carrotResponse = $.parseJSON(jqXHR.responseText)
-        postMethod(carrotResponse.fb_data,
+        fb_data = $.extend({}, response.fb_data, opts)
+        postMethod(fb_data,
           (fbResponse) =>
             if fbResponse
               @ajaxPost("#{@scheme}://posts.gocarrot.com/#{carrotResponse.request_id}/ids", {platform_id: fbResponse.request})


fix visibilitychange on iOS7

fix visibilitychange on iOS7

--- src/Events.coffee
+++ src/Events.coffee
@@ -37,6 +37,13 @@

   @extend: (options={}, callbacks={}) ->
+
+    # Mark initialVisibility and focuslisteners components always ready on iOS
+    unless navigator.userAgent.match(/Android/i)
+      steroids.markComponentReady("Events.initialVisibility")
+      steroids.markComponentReady("Events.focuslisteners")
+      return
+
     @initializeVisibilityState()
     @checkInitialVisibility()


make multiplier work

make multiplier work

--- src/marktab.coffee
+++ src/marktab.coffee
@@ -18,7 +18,8 @@
 	slideDownPattern = /\\/
 	chordPattern = /\([0-9\s:]+\)/
 	riffPattern = /\[.*\]/
-	multiplierPattern = /[\[\(].*[\]\)]\s*x[0-9]+/
+	multiplierLinePattern = /[\[\(].*[\]\)]\s*x[0-9]+/
+	multiplierPattern = /x[0-9]+/
 	ignorePattern = /[\s\n]/
 	setVariablePattern = /[\w0-9\-]+:\s*[\(\[].*[\)\]]/
 	variablePattern = /[\(\[].*[\)\]]/
@@ -97,9 +98,9 @@
 				i += part.length
 				tabMapPart[lastString] = []
 				tabMapPart[lastString][i] = parseInt(part, 10)
-			else if part.search(multiplierPattern) is 0
+			else if part.search(multiplierLinePattern) is 0
 				# multiplier
-				part = part.match(multiplierPattern)
+				part = part.match(multiplierLinePattern)
 				i += part.length
 				tabMapPart = this.parseMultiplier(part)
 			else if part.search(riffPattern) is 0
@@ -151,10 +152,11 @@
 		max

 	addTabMaps: (a, b) ->
-		result = this.normalizeTabMap(a)
+		result = this.cloneTabMap(a)
 		aLen = this.longestString(a)
 		for strNum, strNotesArr of b
 			for fret, i in strNotesArr
+				result[strNum] ?= []
 				result[strNum][aLen + i] = fret
 		result

@@ -234,13 +236,36 @@
 		customVars[variableName]

 	parseMultiplier: (input) ->
-		# TODOs:
-		# - separate riff from multiplier
-		# - parse riff
+		result = {}
+
+		# parse either a riff or a chord. TODO: make this work without code duplication
+		if input.search(chordPattern) is 0
+			part = input.match(chordPattern)[0]
+			tabToMultiply = this.parseChord(part)
+		else if input.search(riffPattern) is 0
+			part = input.match(riffPattern)[0]
+			tabToMultiply = this.parseRiff(part)
+		else
+			throw "cannot set invalid multiplied part: " + part
+
+		part = input.substr(part.length, input.length-part.length)# get rest of line
+		# advance past ignored characters
+		while part.search(ignorePattern) is 0
+			part = part.substr(1, part.length-1)
+
 		# - parse multiplier
+		multiplier = part.match(multiplierPattern)[0]
+		times = multiplier.substr(1, multiplier.length-1)# advance past "x" character
+		times = parseInt(times, 10)
+
 		# - make riff happen x times
-		# - return tab map
-		{}
+		i = 1
+		while i <= times
+			result = this.addTabMaps(result, tabToMultiply)
+			i++
+
+		# return tab map
+		result

 	# makes each string's notes array the same length
 	normalizeTabMap: (tabMap = {}) ->


Fix typo

Fix typo

--- src/widgets/queryinput.coffee
+++ src/widgets/queryinput.coffee
@@ -1,7 +1,7 @@
 extend = require "extend"
 Widget = require "./widget"
 Thing = require "../util/thing"
-error = require "../util/errors"
+errors = require "../util/errors"
 $ = require "../util/dfdom"

@@ -49,7 +49,7 @@
   setElement: (elements) ->
     @element = ($ elements).filter (elem) =>
       if elem.doofinderQueryInput?
-        throw error.error "(#{elem.id}) was registered in another QueryInput", elem
+        throw errors.error "(#{elem.id}) was registered in another QueryInput", elem
       else
         elem.doofinderQueryInput = @


Removed unused package

Removed unused package

--- client/views/storage/storage.coffee
+++ client/views/storage/storage.coffee
@@ -1,6 +1,5 @@
 Storage = require '/imports/ui/meteor/storage.cjsx'
 pretty = require 'prettysize'
-_ = require 'lodash'

 Template.storage.helpers
   Storage: -> Storage


wip groups

wip groups

--- core/KDObject.coffee
+++ core/KDObject.coffee
@@ -25,6 +25,9 @@
   bound: Bongo.bound

   lazyBound: (method, rest...)-> @[method].bind this, rest...
+
+  forwardEvent:(target, event, prefix="")->
+    target.on event, @lazyBound 'emit', prefix + event

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


Comment moved

Comment moved

--- frontend/views/root.coffee
+++ frontend/views/root.coffee
@@ -24,8 +24,7 @@
       @user_location = [coords.latitude, coords.longitude]

       @map = new Here.map.Display (document.getElementById "mapContainer"),
-        # Centred on Lat/Long for Glasgow
-        center:      [55.858, -4.2590]
+        center:      [55.858, -4.2590] # Centred on Lat/Long for Glasgow
         zoomLevel:   12
         components:  [new Here.map.component.Behavior()] # Map Pan/Zoom


updating language selection widget for a subdomain

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -6,7 +6,7 @@
 require './shared'

-DashHeader = ReactiveComponent
+window.DashHeader = ReactiveComponent
   displayName: 'DashHeader'

   render : ->
@@ -31,7 +31,9 @@
             fontSize: 28
             padding: '20px 0'
             fontWeight: 400
-          @props.name
+          TRANSLATE
+            id: "admin.dash_header.#{@props.name}"
+            @props.name

 ImportDataDash = ReactiveComponent
   displayName: 'ImportDataDash'
@@ -230,7 +232,7 @@
               placeholder: 'A link to the main project\'s homepage, if any.'

           DIV className: 'input_group',
-            LABEL htmlFor: 'lang', 'Interface Language'
+            LABEL htmlFor: 'lang', 'Primary Language'
             SELECT
               id: 'lang'
               type: 'text'
@@ -241,37 +243,36 @@
                 marginLeft: 12
                 display: 'inline-block'

-              OPTION
-                value: 'en'
-                'English'
-
-              OPTION
-                value: 'es'
-                'Spanish'
-
-              OPTION
-                value: 'fr'
-                'French'
-
-              OPTION
-                value: 'pt'
-                'Portuguese'
-
-              OPTION
-                value: 'cs'
-                "Czech"
-
-              OPTION
-                value: 'aeb'
-                'Tunisian arabic'
-
+              for abbrev, label of (fetch('/translations').available_languages or {})
+
+                OPTION
+                  value: abbrev
+                  label
+
+            if subdomain.lang? && subdomain.lang != 'en'
+              DIV
+                style:
+                  fontSize: 12
+
+                TRANSLATE
+                  id: "i18n.translations_link"
+                  percent_complete: Math.round(translation_progress(subdomain.lang) * 100)
+                  language: (fetch('/translations').available_languages or {})[subdomain.lang]
+                  link:
+                    component: A
+                    args:
+                      href: "/dashboard/translations"
+                      style:
+                        textDecoration: 'underline'
+                        color: focus_color()
+                        fontWeight: 700
+                  "Translations for {language} are {percent_complete}% completed. Help improve the translations <link>here</link>."

             DIV
               style:
-                fontStyle: 'italic'
                 fontSize: 12
-              "Your language not available? Email us at hello@consider.it to help us create a translation."
+              "Is your preferred language not available? Email us at hello@consider.it to help us create a translation."


ace: Make macKey param optional for addKeyCombo method.

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -54,17 +54,17 @@
     @editor.getSession().selection.on 'changeCursor', (cursor)=>
       @emit "ace.change.cursor", @editor.getSession().getSelection().getCursor()

-    @addKeyCombo "save", "Ctrl-S", "Command-S", @bound "requestSave"
-
-    @addKeyCombo "saveAs", "Ctrl-Shift-S", "Command-Shift-S", @bound "requestSaveAs"
-
-    @addKeyCombo "find", "Ctrl-F", "Command-F", => @showFindReplaceView no
-
-    @addKeyCombo "replace", "Ctrl-Shift-F", "Command-Shift-F", => @showFindReplaceView yes
-
-    @addKeyCombo "compileAndRun", "Ctrl-Shift-C", "Command-Shift-C", => @getDelegate().compileAndRun()
-
-    @addKeyCombo "preview", "Ctrl-Shift-P", "Command-Shift-P", => @getDelegate().preview()
+    @addKeyCombo "save", "Ctrl-S", @bound "requestSave"
+
+    @addKeyCombo "saveAs", "Ctrl-Shift-S", @bound "requestSaveAs"
+
+    @addKeyCombo "find", "Ctrl-F", => @showFindReplaceView no
+
+    @addKeyCombo "replace", "Ctrl-Shift-F", => @showFindReplaceView yes
+
+    @addKeyCombo "compileAndRun", "Ctrl-Shift-C", => @getDelegate().compileAndRun()
+
+    @addKeyCombo "preview", "Ctrl-Shift-P", => @getDelegate().preview()

     @getSingleton('windowController').on "keydown", (e) =>
       {findAndReplaceView} = @getDelegate()
@@ -87,7 +87,8 @@
     findAndReplaceView.setTextIntoFindInput selectedText
     findAndReplaceView.on "FindAndReplaceViewClosed", => @focus()

-  addKeyCombo: (name, winKey, macKey, callback) ->
+  addKeyCombo: (name, winKey, callback, macKey) ->
+    macKey or= winKey.replace "Ctrl", "Command"
     @editor.commands.addCommand
       name    : name
       bindKey :


subscription tests fixed

subscription tests fixed

--- services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee
+++ services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee
@@ -68,7 +68,9 @@
 			"./LimitationsManager": @LimitationsManager
 			"../../infrastructure/GeoIpLookup":@GeoIpLookup
 			'./RecurlyWrapper': @RecurlyWrapper
-			"logger-sharelatex": log:->
+			"logger-sharelatex":
+				log:->
+				warn:->
 			"settings-sharelatex": @settings
 			"./SubscriptionDomainHandler":@SubscriptionDomainHandler

@@ -273,7 +275,7 @@
 	describe "userCustomSubscriptionPage", ->
 		beforeEach (done) ->
 			@res.callback = done
-			@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true)
+			@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true, {})
 			@SubscriptionController.userCustomSubscriptionPage @req, @res

 		it "should render the page", (done)->


Fix deprecated call remote require "dialog"

--- lib/dialog.coffee
+++ lib/dialog.coffee
@@ -1,6 +1,6 @@
 SelectListView = require 'atom-select-list'
 remote = require "remote"
-dialog = remote.require "dialog"
+dialog = remote.dialog

 class PgHoffDialog
     constructor: (serializedState) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -126,7 +126,7 @@
   @name meter
   @methodOf Metered#
   @param {Object} options the amount to shift the x position
-  @param {Number} yOffset the amount to shift the y position
+
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -135,7 +135,7 @@
     height - Height of the meter
     position - An x, y object representing the position of the meter
     radius - Border radius of the meter
-    showName - Boolean to toggle whether or not to show the
+    showName - Boolean to toggle whether or not to show the attribute associated with the
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -130,7 +130,7 @@
   @param {Number} height Height of the meter
   @param {Object} position An x, y object representing the position of the meter
   @param {Number} radius Border radius of the meter
-  @param {Boolean} show Boolean to
+  @param {Boolean} show Boolean to to
   @param {Boolean} showName Boolean to toggle whether or not to show the attribute associated with the meter
   @param {String} text A String to display over the meter. Overrides default name attribute
   @param {Number} width How wide the meter is


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -152,8 +152,8 @@
       I[name] = 100

     if not I["max#{name.capitalize()}"]
-      if I[oname]
-        I["max#{options.name.capitalize()}"] = I[options.name]
+      if I[name]
+        I["max#{name.capitalize()}"] = I[options.name]
       else
         I["max#{options.name.capitalize()}"] = 100


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -123,7 +123,7 @@
   @param {String} color Color of the meter's inner rectangle
   @param {String} nameColor Color of the property name displayed above the meter
   @param {String} font Size and style of the meter's font
-  @param {N} height - Height of the meter
+  @param {Number} height Height of the meter
     position - An x, y object representing the position of the meter
     radius - Border radius of the meter
     showName - Boolean to toggle whether or not to show the attribute associated with the meter


Turned file, document and layout into a class system

--- lib/docpad.coffee
+++ lib/docpad.coffee
@@ -22,7 +22,6 @@
 		formatter:
 			module: module

-
 # -------------------------------------
 # Prototypes

@@ -56,8 +55,111 @@
 String::finishesWith = (suffix) ->
 	return @indexOf(suffix) is @length-1

+
 # -------------------------------------
-# Main
+# Models
+
+class File
+	# Auto
+	id: null
+	basename: null
+	extensions: []
+	extension: null
+	filename: null
+	fullPath: null
+	relativePath: null
+	relativeBase: null
+	content: null
+	contentSrc: null
+	contentRaw: null
+
+	# User
+	title: null
+	date: null
+	slug: null
+	url: null
+	ignore: false
+	tags: []
+	relatedDocuments: []
+
+	# Constructor
+	constructor: (fullPath,relativePath) ->
+		@fullPath = fullPath
+		@relativePath = relativePath
+		@tags = []
+		@relatedDocuments = []
+
+	# Load
+	# next(err)
+	load: (next) ->
+		# Async
+		tasks = new util.Group (err) =>
+			if err
+				logger.log 'err', "Failed to read the file #{@relativePath}"
+			else
+				logger.log 'debug', "Read the file #{@relativePath}"
+			next err
+		tasks.total = 2
+
+		# Stat the file
+		fs.stat @fullPath, (err,fileStat) =>
+			return next err  if err
+			@date = fileStat.ctime
+			tasks.complete()
+
+		# Read the file
+		fs.readFile @fullPath, (err,data) =>
+			return next err  if err
+
+			# Handle data
+			fileData = data.toString()
+			fileSplit = fileData.split '---'
+			fileMeta = {}
+			if fileSplit.length >= 3 and !fileSplit[0]
+				# Extract parts
+				fileHead = fileSplit[1].replace(/\t/g,'    ').replace(/\s+$/m,'').replace(/\r\n?/g,'\n')+'\n'
+				fileBody = fileSplit.slice(2).join('---')
+				fileMeta = yaml.eval fileHead
+			else
+				# Extract part
+				fileBody = fileData
+
+			# Extensions
+			@basename = path.basename @fullPath
+			@extensions = @basename.split /\./g
+			@extensions.shift()
+			@extension = @extensions[0]
+			@filename = @basename.replace(/\..*/, '')
+
+			# Temp
+			fullDirPath = path.dirname @fullPath
+			relativeDirPath = path.dirname(@relativePath).replace(/^\.$/,'')
+
+			# Update Meta
+			@content = fileBody
+			@contentSrc = fileBody
+			@contentRaw = fileData
+			@relativeBase = (if relativeDirPath.length then relativeDirPath+'/' else '')+@filename
+			@title = @title || path.basename(@fullPath)
+			@date = new Date(@date)  if @date
+			@slug = util.generateSlugSync @relativeBase
+			@id = @slug
+			@url = '/'+@relativeBase+'.'+@extension
+
+			# Apply User Meta
+			for own key, value of fileMeta
+				@[key] = value
+
+			# Complete
+			tasks.complete()
+
+class Layout extends File
+
+class Document extends File
+
+
+# -------------------------------------
+# Docpad

 class Docpad

@@ -77,32 +179,8 @@
 	port: 9778

 	# Models
-	Layout: class
-		layout: ''
-		fullPath: ''
-		relativePath: ''
-		relativeBase: ''
-		body: ''
-		contentRaw: ''
-		content: ''
-		date: new Date()
-		title: ''
-	Document: class
-		layout: ''
-		fullPath: ''
-		relativePath: ''
-		relativeBase: ''
-		url: ''
-		tags: []
-		relatedDocuments: []
-		body: ''
-		contentRaw: ''
-		content: ''
-		contentRendered: ''
-		date: new Date()
-		title: ''
-		slug: ''
-		ignore: false
+	Layout: Layout
+	Document: Document
 	Layouts: {}
 	Documents: {}

@@ -184,7 +262,6 @@
 				continue  unless plugin
 				++count
 				tasks.push ((plugin,data,tasks) -> ->
-					console.log plugin
 					plugin.renderFile.apply plugin, [data,tasks.completer()]
 				)(plugin,data,tasks)
 		else
@@ -261,6 +338,7 @@
 			logger.log 'info', 'Finished loading'
 			@loading = false

+
 	# Clean Models
 	cleanModels: (next) ->
 		Layouts = @Layouts = new queryEngine.Collection
@@ -271,6 +349,7 @@
 			Documents[@id] = @
 		next()  if next

+
 	# Handle
 	action: (action) ->
 		switch action
@@ -305,6 +384,7 @@
 								throw err  if err
 								logger.log 'info', 'DocPad is is now watching and serving you...'

+
 	# Clean the database
 	generateClean: (next) ->
 		# Prepare
@@ -314,26 +394,30 @@
 		@cleanModels()

 		# Async
-		util.parallel \
-			# Tasks
-			[
-				(next) =>
-					@Layouts.remove {}, (err) ->
-						unless err
-							logger.log 'debug', 'Cleaned layouts'
-						next err
-
-				(next) =>
-					@Documents.remove {}, (err) ->
-						unless err
-							logger.log 'debug', 'Cleaned documents'
-						next err
-			],
-			# Completed
-			(err) ->
-				unless err
-					logger.log 'debug', 'Cleaning finished'
-				next err
+		tasks = new util.Group (err) ->
+			logger.log 'debug', 'Cleaning finished'  unless err
+			next err
+		tasks.total = 2
+
+		# Layouts
+		@Layouts.remove {}, (err) ->
+			logger.log 'debug', 'Cleaned layouts'  unless err
+			tasks.complete err
+
+		# Documents
+		@Documents.remove {}, (err) ->
+			logger.log 'debug', 'Cleaned documents'  unless err
+			tasks.complete err
+
+
+	# Check if the file path is ignored
+	# next(err,ignore)
+	filePathIgnored: (fileFullPath,next) ->
+		if path.basename(fileFullPath).startsWith('.') or path.basename(fileFullPath).finishesWith('~')
+			next null, true
+		else
+			next null, false
+

 	# Parse the files
 	generateParse: (nextTask) ->
@@ -354,165 +438,62 @@
 		layoutsSrcPath = @srcPath+'/layouts'
 		documentsSrcPath = @srcPath+'/documents'

-		# File Parser
-		parseFile = (fileFullPath,fileRelativePath,fileStat,next) ->
-			# Prepare
-			fileMeta = {}
-			fileData = ''
-			fileSplit = []
-			fileHead = ''
-			fileBody = ''
-
-			# Log
-			logger.log 'debug', 'Parsing the file ['+fileRelativePath+']'
-
-			# Read the file
-			fs.readFile fileFullPath, (err,data) ->
-				return next err  if err
-
-				# Handle data
-				fileData = data.toString()
-				fileSplit = fileData.split '---'
-				if fileSplit.length >= 3 and !fileSplit[0]
-					# Extract parts
-					fileHead = fileSplit[1].replace(/\t/g,'    ').replace(/\s+$/m,'').replace(/\r\n?/g,'\n')+'\n'
-					fileBody = fileSplit.slice(2).join('---')
-					fileMeta = yaml.eval fileHead
-				else
-					# Extract part
-					fileBody = fileData
-
-				# Extensions
-				fileMeta.basename = path.basename fileFullPath
-				fileMeta.extensions = fileMeta.basename.split /\./g
-				fileMeta.extensions.shift()
-				fileMeta.extension = fileMeta.extensions[0]
-				fileMeta.filename = fileMeta.basename.replace(/\..*/, '')
-
-				# Temp
-				fullDirPath = path.dirname fileFullPath
-				relativeDirPath = path.dirname(fileRelativePath).replace(/^\.$/,'')
-
-				# Update Meta
-				fileMeta.content = fileBody
-				fileMeta.contentSrc = fileBody
-				fileMeta.contentRaw = fileData
-				fileMeta.fullPath = fileFullPath
-				fileMeta.relativePath = fileRelativePath
-				fileMeta.relativeBase = (if relativeDirPath.length then relativeDirPath+'/' else '')+fileMeta.filename
-				fileMeta.body = fileBody
-				fileMeta.title = fileMeta.title || path.basename(fileFullPath)
-				fileMeta.date = new Date(fileMeta.date || fileStat.ctime)
-				fileMeta.slug = util.generateSlugSync fileMeta.relativeBase
-				fileMeta.id = fileMeta.slug
-				fileMeta.url = '/'+fileMeta.relativeBase+'.'+fileMeta.extension
-
-				# Plugins
-				docpad.triggerEvent 'parseFileFinished', {fileMeta}, (err) ->
-					# Forward
-					return next err, fileMeta
-
-		# Files Parser
-		parseFiles = (fullPath,callback,nextTask) ->
-			util.scandir(
-				# Path
-				fullPath,
-
-				# File Action
-				(fileFullPath,fileRelativePath,nextFile) ->
-					# Ignore hidden files
-					if path.basename(fileFullPath).startsWith('.') or path.basename(fileFullPath).finishesWith('~')
-						logger.log 'info', 'Ignored hidden document:', fileRelativePath
-						return nextFile()
-
-					# Stat file
-					fs.stat fileFullPath, (err,fileStat) ->
-						# Check error
+		# Async
+		tasks = new util.Group (err) ->
+			logger.log 'debug', 'Parsed files'  unless err
+			nextTask err
+		tasks.total = 2
+
+		# Layouts
+		util.scandir(
+			# Path
+			layoutsSrcPath,
+
+			# File Action
+			(fileFullPath,fileRelativePath,nextFile) ->
+				# Ignore?
+				docpad.filePathIgnored fileFullPath, (err,ignore) ->
+					return nextFile(err)  if err or ignore
+					layout = new Layout fileFullPath, fileRelativePath
+					layout.load (err) ->
 						return nextFile err  if err
-
-						# Parse file
-						parseFile fileFullPath,fileRelativePath,fileStat, (err,fileMeta) ->
-							return nextFile err  if err
-
-							# Were we ignored?
-							if fileMeta.ignore
-								logger.log 'info', 'Ignored document:', fileRelativePath
-								return nextFile()
-
-							# We are cared about! Yay!
-							else
-								callback fileMeta, nextFile
+						layout.save()
+						nextFile err

-				# Dir Action
-				false,
-
-				# Next
-				(err) ->
-					if err
-						logger.log 'warn', 'Failed to parse the directory:', fullPath, err
-					nextTask err
-			)
-
-		# Parse Files
-		util.parallel \
-			# Tasks
-			[
-				# Layouts
-				(taskCompleted) -> parseFiles(
-					# Full Path
-					layoutsSrcPath,
-					# Callback: Each File
-					(fileMeta,nextFile) ->
-						# Prepare
-						layout = new Layout()
-
-						# Apply
-						for own key, fileMetaValue of fileMeta
-							layout[key] = fileMetaValue
-
-						# Save
-						layout.save()
-						logger.log 'debug', 'Parsed layout:', layout.relativeBase
-						nextFile()
-					,
-					# All Parsed
-					(err) ->
-						unless err
-							logger.log 'debug', 'Parsed layouts'
-						taskCompleted err
-				),
-				# Documents
-				(taskCompleted) -> parseFiles(
-					# Full Path
-					documentsSrcPath,
-					# One Parsed
-					(fileMeta,nextFile) ->
-						# Prepare
-						document = new Document()
-
-						# Apply
-						for own key, fileMetaValue of fileMeta
-							document[key] = fileMetaValue
-
-						# Save
+			# Dir Action
+			false,
+
+			# Next
+			(err) ->
+				logger.log 'warn', 'Failed to parse layouts', err  if err
+				tasks.complete err
+		)
+
+		# Documents
+		util.scandir(
+			# Path
+			documentsSrcPath,
+
+			# File Action
+			(fileFullPath,fileRelativePath,nextFile) ->
+				# Ignore?
+				docpad.filePathIgnored fileFullPath, (err,ignore) ->
+					return nextFile(err)  if err or ignore
+					document = new Document fileFullPath, fileRelativePath
+					document.load (err) ->
+						return nextFile err  if err
 						document.save()
-						logger.log 'debug', 'Parsed document:', document.relativeBase
-						nextFile()
-					,
-					# All Parsed
-					(err) ->
-						unless err
-							logger.log 'debug', 'Parsing documents finished'
-						else
-							logger.log 'err', 'Parsing documents failed', err
-						taskCompleted err
-				)
-			],
-			# Completed
+						nextFile err
+
+			# Dir Action
+			false,
+
+			# Next
 			(err) ->
-				unless err
-					logger.log 'debug', 'Parsed files'
-				nextTask err
+				logger.log 'warn', 'Failed to parse documents', err  if err
+				tasks.complete err
+		)
+

 	# Generate render
 	generateRender: (next) ->
@@ -599,6 +580,7 @@
 					tasks.completer()
 				)

+
 	# Write files
 	generateWriteFiles: (next) ->
 		logger.log 'debug', 'Copying files'
@@ -614,6 +596,7 @@
 				next err
 		)

+
 	# Write documents
 	generateWriteDocuments: (next) ->
 		Documents = @Documents
@@ -634,7 +617,6 @@
 			documents.forEach (document) ->
 				# Generate path
 				fileFullPath = outPath+'/'+document.url #relativeBase+'.html'
-
 				# Ensure path
 				util.ensurePath path.dirname(fileFullPath), (err) ->
 					# Error
@@ -643,6 +625,7 @@
 					# Write document
 					fs.writeFile fileFullPath, document.contentRendered, (err) ->
 						tasks.complete err
+

 	# Write
 	generateWrite: (next) ->
@@ -669,6 +652,7 @@
 				unless err
 					logger.log 'debug', 'Wrote files'
 				next err
+

 	# Generate
 	generateAction: (next) ->
@@ -746,6 +730,7 @@
 													docpad.generating = false
 												next err

+
 	# Watch
 	watchAction: (next) ->
 		# Requires
@@ -775,6 +760,7 @@
 		# Next
 		next()

+
 	# Skeleton
 	skeletonAction: (next) ->
 		# Prepare
@@ -796,6 +782,7 @@
 						logger.log 'info', 'Copied the skeleton'
 					next err

+
 	# Server
 	serverAction: (next) ->
 		# Requires
@@ -830,6 +817,7 @@
 			# Forward
 			next err

+
 # API
 docpad =
 	Docpad: Docpad


Switch to using interpretation property for color

--- src/components/learning-guide/progress-bar.cjsx
+++ src/components/learning-guide/progress-bar.cjsx
@@ -15,11 +15,9 @@

     bar = if section.current_level
       percent = Math.round((section.current_level / 1) * 100)
-      color = switch
-        when percent >  75 then 'high'
-        when percent >= 50 then 'medium'
-        else 'low'
-      <BS.ProgressBar className={color} now={percent} />
+      console.log section
+      # always show at least 5% of bar, otherwise it just looks empty
+      <BS.ProgressBar className={section.interpretation} now={percent} />
     else
       Practice section


changing the way saved is listened to

changing the way saved is listened to

--- src/components/task-plan/plan-mixin.coffee
+++ src/components/task-plan/plan-mixin.coffee
@@ -53,17 +53,16 @@
     # The logic here is this way because we need to be able to add an invalid
     # state to the form.  Blame @fredasaurus
     if (saveable)
-      TaskPlanStore.addChangeListener(@saved)
+      TaskPlanActions.saved.addListener(@saved)
       TaskPlanActions.save(id)
     else
       @setState({invalid: true})

   saved: ->
-    if TaskPlanStore.isLoaded(@props.id)
-      courseId = @props.courseId
-      TaskPlanStore.removeChangeListener(@saved)
-      TaskPlanStore.isLoading(@props.id)
-      @context.router.transitionTo('taskplans', {courseId})
+    courseId = @props.courseId
+    TaskPlanStore.removeChangeListener(@saved)
+    TaskPlanStore.isLoading(@props.id)
+    @context.router.transitionTo('taskplans', {courseId})

   cancel: ->
     {id} = @props


refactor(editable): moved delayedsave functionality from main file to editable.coffee

--- src/lib/editable.coffee
+++ src/lib/editable.coffee
@@ -1,11 +1,16 @@
-Editable = (self, property, change) ->
+Editable = (self, property, callback) ->
   editing_property = 'editing_' + property

   Observable self, property
+
+  if typeof self[property] != 'function'
+    console.error 'Editable field "' + property + '" is not a valid Observable'
+    return false
+
   Observable self, editing_property, (self[property]() == '' || !self[property]()?)

   self[property].subscribe (new_value) =>
-    r = change new_value, property
+    r = callback new_value, property

     unless r == true
       self[editing_property] false
@@ -14,15 +19,37 @@

   return self

-DelayedSave = (value, prop, delay = 250) ->
-  clearTimeout(window.timeoutEditor)
-  window.timeoutEditor = setTimeout(=>
+DelayedSave = (options, self) ->
+  (value, prop, delay = 250) =>
+    if self.id?
+      window.timeoutEditor = {} unless window.timeoutEditor?
+      window.timeoutEditor[options.api + self.id] = {} unless window.timeoutEditor[options.api + self.id]?

-    if (type == 'content')
-      @article().content window.editor.serialize().post_content.value
-    else if (type == 'title')
-      @article().title window.title_editor.serialize().post_title.value
-    else if (type == 'pretext')
-      @article().pretext window.pretext_editor.serialize().post_pretext.value
-
-  , delay)+      clearTimeout(window.timeoutEditor[options.api + self.id][prop])
+      window.timeoutEditor[options.api + self.id][prop] = setTimeout(=>
+        edit_value = {}
+        edit_value[prop] = value
+        self.update edit_value
+      , delay)
+
+EncryptedDelayedSave = (options, self) ->
+  (value, prop, delay = 250) =>
+    if typeof self[options.encrypted_container] != 'object' || self[options.encrypted_container] == null
+      self[options.encrypted_container] = {}
+
+      for index, name of options.encrypted_editable
+        self[options.encrypted_container][name] = self[name]()
+
+    self[options.encrypted_container][prop] = value
+
+    if self.id?
+      window.timeoutEditor = {} unless window.timeoutEditor?
+      clearTimeout(window.timeoutEditor[options.api + self.id])
+      window.timeoutEditor[options.api + self.id] = setTimeout(=>
+        self.save_encrypted_container()
+
+        ## and for verification, save it unencrypted too
+        edit_value = {}
+        edit_value[prop] = value
+        self.update edit_value
+      , delay)


check grammar has name. see #82

check grammar has name. see #82

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -318,6 +318,8 @@
     getGrammarForLanguage: (language) ->
         matchingGrammars = atom.grammars.getGrammars().filter (grammar) ->
             grammar != atom.grammars.nullGrammar and
+                grammar.name? and
+                grammar.name.toLowerCase? and
                 grammar.name.toLowerCase() == language

         return matchingGrammars[0]


:fire: Remove redundant comment from legacy

--- lib/legacy.coffee
+++ lib/legacy.coffee
@@ -13,8 +13,6 @@
       type: typeMap[level] ? level
       message: message
       file: filePath
-      # These are all 0-indexed, but the `Position` needs a range where
-      # everything is 1-indexed
       position: [
         [ startLine, startCol],
         [ endLine, endCol]


Use _atomPackage key from main metadata

Use _atomPackage key from main metadata

--- src/package.coffee
+++ src/package.coffee
@@ -14,8 +14,8 @@
 ScopedProperties = require './scoped-properties'

 try
-  packagesCache = require('../package.json')
-catch
+  packagesCache = require('../package.json')?._atomPackages ? {}
+catch error
   packagesCache = {}

 # Loads and activates a package's main module and resources such as


Show notifications on load/activate errors

--- src/package.coffee
+++ src/package.coffee
@@ -126,9 +126,8 @@
         @loadStylesheets()
         @settingsPromise = @loadSettings()
         @requireMainModule() unless @hasActivationCommands()
-
       catch error
-        console.warn "Failed to load package named '#{@name}'", error.stack ? error
+        @handleError("Failed to load the #{@name} package", error)
     this

   reset: ->
@@ -148,8 +147,12 @@
         if @hasActivationCommands()
           try
             @subscribeToActivationCommands()
-          catch e
-            console.warn "Failed to subscribe to activation commands for package '#{@name}'", e.stack
+          catch error
+            if error.code is 'EBADSELECTOR'
+              metadataPath = path.join(@path, 'package.json')
+              error.message += " in #{metadataPath}"
+            error.stack += "\n  at #{metadataPath}:1:1"
+            @handleError("Failed to activate the #{@name} package", error)
         else
           @activateNow()

@@ -163,8 +166,8 @@
         @mainModule.activate?(atom.packages.getPackageState(@name) ? {})
         @mainActivated = true
         @activateServices()
-    catch e
-      console.warn "Failed to activate package named '#{@name}'", e.stack
+    catch error
+      @handleError("Failed to activate the #{@name} package", error)

     @activationDeferred?.resolve()

@@ -531,3 +534,18 @@
       @compatible = @incompatibleModules.length is 0
     else
       @compatible = true
+
+  handleError: (message, error) ->
+    if error.filename and error.location and (error instanceof SyntaxError)
+      location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}"
+      detail = "#{error.message} in #{location}"
+      stack = """
+        SyntaxError: #{error.message}
+          at #{location}
+      """
+    else
+      detail = error.message
+      stack = error.stack ? error
+
+    console.trace()
+    atom.notifications.addFatalError(message, {stack, detail, dismissable: true})


Fix all biscoto warnings

Fix all biscoto warnings

--- tasks/docs-task.coffee
+++ tasks/docs-task.coffee
@@ -10,7 +10,7 @@
     grunt.task.run('markdown:guides')

     done = @async()
-    args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/']
+    args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/', 'vendor/telepath/lib/']
     grunt.util.spawn({cmd, args, opts}, done)

   grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', ->


fix(draft-list): Don't render html string in draft subject

--- internal_packages/draft-list/lib/draft-list-columns.cjsx
+++ internal_packages/draft-list/lib/draft-list-columns.cjsx
@@ -16,7 +16,7 @@
   if (subj ? "").trim().length is 0
     return (No Subject)
   else
-    return subj
+    return text = Utils.extractTextFromHtml(subj)

 ParticipantsColumn = new ListTabular.Column
   name: "Participants"


:lipstick: Just set ivar in constructor

:lipstick: Just set ivar in constructor

--- lib/bookmarks.coffee
+++ lib/bookmarks.coffee
@@ -14,10 +14,9 @@
     atom.workspaceView.eachEditorView (editor) ->
       new Bookmarks(editor) if editor.attached and editor.getPane()?

-  editor: null
-
   constructor: (editorView) ->
     {@editor, @gutter} = editorView
+
     editorView.on 'editor:display-updated', @renderBookmarkMarkers

     editorView.command 'bookmarks:toggle-bookmark', @toggleBookmark


Minor: fix comment and improve code clarity

--- src/resources/forms/orbeon/builder/resources/section-grid-repeat/section-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section-grid-repeat/section-editor.coffee
@@ -133,15 +133,15 @@
             section = Builder.findInCache sectionsCache, interceptorOffset.top
             labelAnchor = f$.find '.fr-section-label:first a', section.el
             if (f$.text labelAnchor) == ''
-                enterTitleOutput = f$.children '.fb-enter-section-title-label', sectionEditor
-                enterTitleValue = Controls.getCurrentValue enterTitleOutput[0]
-                f$.text enterTitleValue, clickInterceptor
+                outputWithHintMessage = sectionEditor.children('.fb-enter-section-title-label')
+                hintMessage = Controls.getCurrentValue(outputWithHintMessage.get(0))
+                clickInterceptor.text(hintMessage)

-        # Handle click
+        # Create an position click interceptors
         do ->
             labelClickInterceptors = []
             Builder.onOffsetMayHaveChanged ->
-                sections = $ '.xbl-fr-section'
+                sections = $('.xbl-fr-section')
                 # Create interceptor divs, so we have enough to cover all the sections
                 _.each _.range(sections.length - labelClickInterceptors.length), ->
                     container = $ '<div class="fb-section-label-editor-click-interceptor">'


Remove unsued properties

Remove unsued properties

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -37,7 +37,6 @@
     relations: [{
       type: Backbone.Many
       key: 'contents'
-      parse: true
       relatedModel: Toc
     }]

@@ -62,7 +61,7 @@

     toJSON: () ->
       currentPage = @get('currentPage').toJSON()
-      toc = @get('toc')?.toJSON()
+      toc = @get('toc').toJSON()
       json = super()
       json.currentPage = currentPage
       json.toc = toc


Update restricted key bindings for atom-shell@0.6.3

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -61,10 +61,8 @@
       for binding in bindings
         continue if binding.indexOf(' ') != -1

-        # Currently being implemented in atom-shell
-        continue if binding.indexOf('-=') != -1
-        continue if binding.indexOf('-up') != -1
-        continue if binding.indexOf('-down') != -1
+        # To be fixed in atom-shell
+        continue if binding.indexOf('f2') != -1
         continue if binding.indexOf('tab') != -1

         filtered[key] ?= []


Check that label exists when comparing to other items

Previously separators would be removed when merging menus that
contained multiple separators.

Refs atom/find-and-replace#159

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -92,10 +92,10 @@
   merge: (menu, item) ->
     item = _.deepClone(item)

-    if item.submenu? and match = _.find(menu, (i) => i.submenu? and @normalizeLabel(i.label) == @normalizeLabel(item.label))
+    if item.submenu? and match = _.find(menu, ({label, submenu}) => submenu? and label and @normalizeLabel(label) is @normalizeLabel(item.label))
       @merge(match.submenu, i) for i in item.submenu
     else
-      menu.push(item) unless _.find(menu, (i) => @normalizeLabel(i.label) == @normalizeLabel(item.label))
+      menu.push(item) unless _.find(menu, ({label}) => label and @normalizeLabel(label) is @normalizeLabel(item.label))

   # OSX can't handle displaying accelerators for multiple keystrokes.
   # If they are sent across, it will stop processing accelerators for the rest


Fix assignment issue in setConfig

Fix assignment issue in setConfig

--- lib/clog/clog.coffee
+++ lib/clog/clog.coffee
@@ -27,7 +27,7 @@
     @logWriter = output

   setConfig: (source, logLvl, formatter, output) ->
-    @source = source
+    @global["source"] = source
     @logLvl = @_validateLogLvl(logLvl)
     @formatter = formatter
     @logWriter = output


fix error with empty files not having a final \n

--- lib/rules/insert_final_newline.coffee
+++ lib/rules/insert_final_newline.coffee
@@ -27,6 +27,7 @@

   infer: ->
     @file.read(encoding:'utf8').then((data) =>
+      if data is '' then return true # empty files don't need final newlines
       @FINAL_NEWLINE.test(data)
     )


Add a trailing newline to the package.json

--- src/publish.coffee
+++ src/publish.coffee
@@ -233,8 +233,8 @@

   saveMetadata: (pack) ->
     metadataPath = path.resolve('package.json')
-    out = JSON.stringify(pack, null, 2)
-    fs.writeFileSync(metadataPath, out)
+    metadataJson = JSON.stringify(pack, null, 2)
+    fs.writeFileSync(metadataPath, "#{metadataJson}\n")

   loadRepository: ->
     currentDirectory = process.cwd()


promisification

promisification

--- chrome/content/zotero-better-bibtex/debug-bridge.coffee
+++ chrome/content/zotero-better-bibtex/debug-bridge.coffee
@@ -105,7 +105,7 @@
     if err
       deferred.reject(err)
     else
-      deferred.fulfill(result)
+      deferred.fulfill(JSON.parse(result))
   )
   return deferred.promise


Add a special HTTP header to login page, so home can detect logout

--- server/controllers/authentication.coffee
+++ server/controllers/authentication.coffee
@@ -86,6 +86,7 @@
             next new Error err
         else
             return res.redirect '/register' unless env.username
+            res.set 'X-Cozy-Login-Page', 'true'
             res.render 'index', env: env


Changes classes info

Changes classes info

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -1447,13 +1447,11 @@
     label("""

Лицей 40

Только для учеников лицея 40

-
Занятия проходят по средам в 15:00 в КЦ.

+
Занятия проходят по средам в 14:30 в КЦ.

Новичкам (кто не ходил ни разу и не решал задач заочно)
-            прежде чем приходить на занятие, необходимо зарегистрироваться на сайте и решить хотя бы одну задачу.
-            Исключение — занятия для новичков, которые объявляются особо (обычно второе занятие года),
-            туда можно приходить всем новичкам вообще.

-            29 сентября будет занятие для новичков, куда могут приходить все желающие лицеисты.

+            рекомендуется, прежде чем приходить на занятие, зарегистрироваться на сайте и решить хотя бы одну задачу.
+

Те, кто уже был хотя бы один раз, или решил в курсе хотя бы одну задачу заочно,
             могут приходить каждую среду.


need to call KDRouter#listen when we're ready

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -19,11 +19,12 @@
     KD.registerSingleton "contentDisplayController", new ContentDisplayController
     KD.registerSingleton "notificationController", new NotificationController

-    KD.registerSingleton 'router', new KodingRouter location.pathname
+    router = new KodingRouter location.pathname
+    KD.registerSingleton 'router', router
     KD.registerSingleton "groupsController", new GroupsController

-
     @appReady =>
+      router.listen()
       KD.registerSingleton "activityController", new ActivityController
       KD.registerSingleton "kodingAppsController", new KodingAppsController
       #KD.registerSingleton "bottomPanelController", new BottomPanelController


Kinda wrong branch, but just adding coffeesub. as default

--- cs/background.coffee
+++ cs/background.coffee
@@ -68,7 +68,6 @@

   # Set default choices and open options page after install
   if ls.everConnected is undefined
-

     if ls.first_bus is undefined
       ls.showBus = 'true'
@@ -93,6 +92,8 @@
       ls.coffeePots = 0
     if ls.showNotifications is undefined
       ls.showNotifications = 'true'
+    if ls.coffeeSubscription is undefined
+      ls.coffeeSubscription = 'true'
     if ls.openChatter is undefined
       ls.openChatter = 'false'
     if ls.useInfoscreen is undefined


Add a timestamp for deploy purposes

Add a timestamp for deploy purposes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -86,8 +86,7 @@

             generate:
                 options:
-                    timestamp: false
-                    hash: true
+                    timestamp: true
                 src: [
                     'css/**'
                     'bower_components/**'


Change test result output to test_tap_result.txt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -85,6 +85,7 @@
           require: [
             'should',
           ],
+          captureFile: 'test_tap_result.txt'
         src: [
           './tests/unit-tests/t*-test.js'
         ]


disable uglify mangling

disable uglify mangling

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -173,6 +173,8 @@

     uglify:
       simditor:
+        options:
+          mangle: false
         files:
           'package/scripts/module.min.js': 'package/scripts/module.js'
           'package/scripts/uploader.min.js': 'package/scripts/uploader.js'


dont leave instrumented quill.exposed.js

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -33,4 +33,4 @@
   grunt.registerTask('test:unit', ['karma:test'])
   grunt.registerTask('test:unit:remote', ['karma:remote-mac', 'karma:remote-windows', 'karma:remote-linux', 'karma:remote-mobile', 'karma:remote-legacy'])

-  grunt.registerTask('test:coverage', ['coffee:coverage', 'shell:instrument', 'browserify:quill', 'karma:coverage', 'clean:coverage'])
+  grunt.registerTask('test:coverage', ['coffee:coverage', 'shell:instrument', 'browserify:quill', 'karma:coverage', 'clean:coverage', 'browserify:quill'])


- Disable mangle, as it breaks adapters once initialization

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,6 +24,8 @@
 					'dist/playlist.js': playlist
 				]
 		uglify:
+			options:
+				mangle: false
 			compile:
 				files:
 					'dist/playlist.min.js' : ['dist/playlist.js']


Add a more descriptive comment to Gruntfile.coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -63,7 +63,7 @@
       stylus:
         files: ["./src/styl/*.styl"]
         tasks: ["stylus"]
-      server: # Watch the .livereload file so that it triggers a reload
+      server: # Watch the .livereload file so that livereload triggers when the file is modified
         files: ["./.livereload", "./views/*.jade"]
         tasks: [] # No need to run a task here
     concurrent:


Stubs Exclusion

Stubs Exclusion

In theory *(assuming by blog pattern exclusion is correct)*, the stubs
directory is now excluded from the coffee-compiler. Ensuring that we
don't have any unexpected files in our stubs directories.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,6 +29,7 @@
           'lib/**/*.coffee'
           'runtime/**/*.coffee'
           'test/**/*.coffee'
+          '!test/stubs/**/*.coffee'
         ]

     copy:


CoffeeScript transpilation renders grunt task obsolete.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,7 +3,7 @@
     pkg: grunt.file.readJSON('package.json')

     clean:
-      derived: ['javascripts', 'stylesheets', 'fonts', 'html']
+      derived: ['public']
       options:
         force: true

@@ -20,8 +20,6 @@
         cwd: 'src/'
         src: ['**/*.ts']
         out: 'public/app.ts'
-        # See the pug extDot comment.
-        extDot: 'last'
       tslint:
         options:
           sourceMap: false
@@ -36,14 +34,6 @@
     typings:
       install: {}

-    coffee:
-      default:
-        expand: true
-        ext: '.js'
-        cwd: 'coffeescripts/'
-        src: ['**/*.coffee']
-        dest: 'javascripts'
-
     pug:
       options:
         pretty: true
@@ -55,7 +45,7 @@
         cwd: 'src/'
         ext: '.html'
         src: ['*.pug', '*/**.pug', '!**include/*.pug']
-        dest: 'html'
+        dest: 'public/html'

     markdown:
       default:
@@ -63,7 +53,7 @@
         ext: '.html'
         cwd: 'src/'
         src: ['**/*.md']
-        dest: 'html'
+        dest: 'public/html'

     stylus:
       options:
@@ -73,15 +63,14 @@
         ]
       default:
         src: ['stylus/app.styl']
-        dest: 'stylesheets/app.css'
+        dest: 'public/stylesheets/app.css'

     copy:
-      # The native applicaton JavaScript files.
-      js:
-        expand: true
-        cwd: 'coffeescripts/'
-        src: ['**/*.js']
-        dest: 'javascripts/'
+      static:
+        expand: true
+        cwd: 'static/'
+        src: '**/*'
+        dest: 'public/'

       # Note: this task is only used to copy CSS map files. The
       # CSS style files themselves are copied to the destination
@@ -95,7 +84,7 @@
         flatten: true
         cwd: 'node_modules/'
         src: ['bootstrap/dist/css/bootstrap.css.map']
-        dest: 'stylesheets/'
+        dest: 'public/stylesheets/'

       fonts:
         expand: true
@@ -105,7 +94,7 @@
         #   e.g. 4.0.4/index.html, can't be excluded
         #   (cf. https://github.com/gruntjs/grunt-contrib-copy/issues/13).
         src: ['bootstrap/dist/fonts/*', 'font-awesome/fonts/*']
-        dest: 'fonts/'
+        dest: 'public/fonts/'

     concat:
       css:
@@ -113,21 +102,21 @@
           'node_modules/bootstrap/dist/css/bootstrap.css'
           'node_modules/font-awesome/css/font-awesome.css'
         ]
-        dest: 'stylesheets/vendor.css'
+        dest: 'public/stylesheets/vendor.css'

     concurrent:
       options:
         logConcurrentOutput: true
       compile:
-        tasks: ['typings', 'coffee', 'pug', 'markdown', 'stylus']
+        tasks: ['typings', 'pug', 'markdown', 'stylus']

     cssmin:
       options:
         'nomunge': true
         'line-break': 80
       files:
-        src: ['stylesheets/app.css']
-        dest: 'stylesheets/app.min.css'
+        src: ['public/stylesheets/app.css']
+        dest: 'public/stylesheets/app.min.css'

     browserSync:
       options:
@@ -140,7 +129,7 @@
     jspm:
       dist:
         files:
-          "public/javascripts/app.js": "build/javascripts/app.js"
+          "public/app.js": "build/app.js"

     karma:
       options:


add integration tests to test target and add tests to dist target

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1316,6 +1316,7 @@
   'test_core'
   'test_ui'
   'test_chrome'
+  'integration_test'
 ]

 taskManager.add 'build', [
@@ -1334,6 +1335,7 @@
 taskManager.add 'dist', [
   'build'
   'lint'
+  'test'
   'copy:dist'
   'jpm:xpi'
 ]


allow mocha timeout to be specified on grunt command line

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -32,6 +32,7 @@
 				options:
 					reporter: grunt.option('reporter') or 'spec'
 					grep: grunt.option("grep")
+					timeout: grunt.option("timeout")
 				src: ["test/unit/js/**/*.js"]

 	grunt.loadNpmTasks 'grunt-contrib-coffee'


Add build grunt task

Add build grunt task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -77,7 +77,7 @@
     clean:
       tmp: '.tmp'

-  grunt.registerTask 'default', [
+  grunt.registerTask 'build', [
     'coffeelint'
     'coffee'
     'uglify'
@@ -90,3 +90,8 @@
     'jasmine'
     'clean'
   ]
+
+  grunt.registerTask 'default', [
+    'test'
+    'build'
+  ]


Add exception for running wd tests on safari

Until we can figure out how to type into contenteditable... some sources
claim there's a limitation in SafariDriver

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -36,10 +36,8 @@
   ])

   _.each(browsers, (config, browser) ->
-    grunt.registerTask("test:#{browser}", [
-      'connect:server'
-      "karma:#{browser}"
-      "protractor:wd-#{browser}"
-      "protractor:e2e-#{browser}"
-    ])
+    tasks = ['connect:server', "karma:#{browser}"]
+    unless browser == 'iphone' or browser == 'ipad' or browser == 'mac-safari'
+      tasks.push("protractor:wd-#{browser}", "protractor:e2e-#{browser}")
+    grunt.registerTask("test:#{browser}", tasks)
   )


fix($Gruntfile): Test latest Firefox

fix($Gruntfile): Test latest Firefox

If you don't explicitly specify the version of the browser you are using,
then saucelabs will run the earliest version of that browser. In this case,
it was Firefox 11. Now testing Firefox 21.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -103,6 +103,7 @@
         {
             browserName: 'firefox'
             platform: 'Windows 7'
+            version: '21'
         }
     ]


debug( gruntfile ): change clean task to be more broad

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -489,7 +489,7 @@
     return

   grunt.registerTask "test", [
-    "clean:server"
+    "clean"
     "concurrent:test"
     #"autoprefixer"
     "coffee:dist"


fix bug in environments names

fix bug in environments names

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -220,11 +220,11 @@
   grunt.registerTask 'postprocessor', [
     'concat', 'mocha', 'uglify', 'code_inliner', 'shell:gzip_js_files', 'copy:dist' ]

-  grunt.registerTask 'compile_develop',   [ 'preprocessor', 'copy:config_development', 'postprocessor' ]
-  grunt.registerTask 'compile_staging',   [ 'preprocessor', 'copy:config_staging',     'postprocessor' ]
-  grunt.registerTask 'compile_production',[ 'preprocessor', 'copy:config_production',  'postprocessor' ]
-
-  grunt.registerTask 'default', ['compile_develop']
+  grunt.registerTask 'compile_development', [ 'preprocessor', 'copy:config_development', 'postprocessor' ]
+  grunt.registerTask 'compile_staging',     [ 'preprocessor', 'copy:config_staging',     'postprocessor' ]
+  grunt.registerTask 'compile_production',  [ 'preprocessor', 'copy:config_production',  'postprocessor' ]
+
+  grunt.registerTask 'default', ['compile_development']

   grunt.loadNpmTasks 'grunt-contrib-sass'
   grunt.loadNpmTasks 'grunt-contrib-uglify'


add model.daemon to trigger showing healthcheck/lb options in ui

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -34,6 +34,7 @@
         data.inCooldown = data.state is 'SYSTEM_COOLDOWN'

         data.hasActiveDeploy = data.activeDeploy? or data.requestDeployState?.activeDeploy?
+        data.daemon = data.type in ['WORKER', 'SERVICE']
         data.canBeRunNow = data.state is 'ACTIVE' and data.type in ['SCHEDULED', 'ON_DEMAND'] and data.hasActiveDeploy
         data.canBeBounced = data.state in ['ACTIVE', 'SYSTEM_COOLDOWN'] and data.type in ['WORKER', 'SERVICE']
         data.canBeScaled = data.state in ['ACTIVE', 'SYSTEM_COOLDOWN'] and data.hasActiveDeploy and data.type in ['WORKER', 'SERVICE']


Give medium priority to a request in fuzzy search if the request contains but doesn't start with the search term

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -57,6 +57,8 @@
     adjustedScore: (filter, req) ->
         if req.original.id.toLowerCase().startsWith(filter.toLowerCase())
             req.score * 10
+        else if req.original.id.toLowerCase().indexOf(filter.toLowerCase()) > -1
+            req.score * 5
         else
             req.score


Set default Feeder controllers to use startWithLazyLoader true by default

--- client/app/Applications/Feeder.kdapplication/Controllers/FeederResultsController.coffee
+++ client/app/Applications/Feeder.kdapplication/Controllers/FeederResultsController.coffee
@@ -32,12 +32,13 @@
     tabView = @getView()

     @listControllers[name] = listController = new listControllerClass
-      lazyLoadThreshold : .75
-      viewOptions       :
-        cssClass        : listCssClass
-        subItemClass    : subItemClass
-        type            : name
-
+      lazyLoadThreshold   : .75
+      startWithLazyLoader : yes
+      viewOptions         :
+        cssClass          : listCssClass
+        subItemClass      : subItemClass
+        type              : name
+
     listController.registerListener
       KDEventTypes  : 'LazyLoadThresholdReached'
       listener      : @


Move the profileOrder setter for bot

Move the profileOrder setter for bot

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -60,9 +60,6 @@
         recentlyReceivedKudosu: @props.extras.recentlyReceivedKudosu
         showMorePagination: {}

-      if @props.user.is_bot
-        @state.profileOrder = ['me']
-
       for own elem, perPage of @props.perPage
         @state.showMorePagination[elem] ?= {}
         @state.showMorePagination[elem].hasMore = @state[elem].length > perPage
@@ -126,7 +123,11 @@
   render: =>
     withMePage = @state.userPage.initialRaw.trim() != '' || @props.withEdit

-    profileOrder = @state.profileOrder.slice()
+    if @props.user.is_bot
+      profileOrder = ['me']
+    else
+      profileOrder = @state.profileOrder.slice()
+
     profileOrder.push 'account_standing' if !_.isEmpty @state.user.account_history

     extraPageParams =


less random import order

less random import order

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -1,19 +1,19 @@
 # Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
 # See the LICENCE file in the repository root for full licence text.

-import AccountStanding from 'profile-page/account-standing'
 import { ExtraTab } from './extra-tab'
-import Beatmapsets from 'profile-page/beatmapsets'
 import { Header } from './header'
 import { Historical } from './historical'
-import Kudosu from 'profile-page/kudosu'
 import { Medals } from './medals'
-import RecentActivity from 'profile-page/recent-activity'
 import { TopRanks } from './top-ranks'
 import { UserPage } from './user-page'
 import { BlockButton } from 'block-button'
 import { NotificationBanner } from 'notification-banner'
 import core from 'osu-core-singleton'
+import AccountStanding from 'profile-page/account-standing'
+import Beatmapsets from 'profile-page/beatmapsets'
+import Kudosu from 'profile-page/kudosu'
+import RecentActivity from 'profile-page/recent-activity'
 import * as React from 'react'
 import { a, button, div, i, li, span, ul } from 'react-dom-factories'
 import UserProfileContainer from 'user-profile-container'