Update es-419.coffee (#6402)

Update es-419.coffee (#6402)

Spanish Latin America Translation

--- app/locale/es-419.coco.coffee
+++ app/locale/es-419.coco.coffee
@@ -26,7 +26,7 @@
     century_skills_subtitle3: "Colaboración"
     century_skills_subblurb3: "A lo largo del juego hay oportunidades para que los estudiantes colaboren cuando se atasquen y trabajen juntos utilizando nuestra guía de programación en pareja."
     century_skills_quote4: "Siempre he tenido aspiraciones de diseñar videojuegos y aprender a programar ... esto me está dando un gran punto de partida."
-#    century_skills_quote4_author: "Joey, 10th Grade"
+    century_skills_quote4_author: "Joey, décimo grado"
     century_skills_subtitle4: "Comunicación"
     century_skills_subblurb4: "La programación requiere que los niños practiquen nuevas formas de comunicación, incluida la comunicación con la computadora y la transmisión de sus ideas utilizando el código más eficiente."
     classroom_in_box_title: "Nos esforzamos por:"
@@ -34,7 +34,7 @@
     classroom_in_box_blurb2: "Apoderar a cualquier educador para que se sienta seguro al enseñar programación."
     classroom_in_box_blurb3: "Inspirar a todos los líderes escolares para crear un programa de Ciencias de la Computación de clase mundial."
 #    classroom_in_box_blurb4: ""
-#    click_here: "Click here"
+    click_here: "Click aquí"
     creativity_rigor_title: "Donde la creatividad alcanza el rigor"
     creativity_rigor_subtitle1: "Haga que la programación sea divertida y enseñe habilidades del mundo real"
     creativity_rigor_blurb1: "Los estudiantes escriben Python y JavaScript real mientras juegan juegos que fomentan el ensayo y error, el pensamiento crítico y la creatividad. Luego, los estudiantes aplican las habilidades de programación que han aprendido al desarrollar sus propios juegos y sitios web en cursos basados en proyectos."
@@ -83,24 +83,24 @@
     quotes_quote2: " Me sorprendió lo fácil e intuitivo que CodeCombat hace que aprendan Ciencias de la Computación. Las calificaciones en el examen AP fueron mucho más altas de lo que esperaba y creo que CodeCombat es la razón por la que este fue el caso."
     quotes_quote3: "CodeCombat ha sido lo más beneficioso para enseñar a mis alumnos las capacidades de programación de la vida real. Mi esposo es ingeniero de software y ha probado todos mis programas. Puso esto como su mejor opción."
     quotes_quote4: "Los comentarios ... han sido tan positivos que estamos estructurando una clase de informática en torno a CodeCombat. El programa realmente involucra a los estudiantes con una plataforma estilo juego que es entretenida e instructiva al mismo tiempo. ¡Sigan con el buen trabajo, CodeCombat!"
-#    quotes_quote5: "Even though the class starts every Saturday at 7am, my son is so excited that he wakes up before me! CodeCombat creates a pathway for my son to advance his coding skills."
-#    quotes_quote5_author: "Latthaphon Pohpon, Parent"
+    quotes_quote5: "Aunque la clase comienza todos los sábados a las 7 am, ¡mi hijo está tan emocionado que se despierta antes que yo! CodeCombat crea un camino para que mi hijo avance en sus habilidades de codificación."
+    quotes_quote5_author: "Latthaphon Pohpon, Padre"
     see_example: "Ver ejemplo"
     slogan: "El juego más interesante para aprender programación." # {change}
-#    slogan_power_of_play: "Learn to Code Through the Power of Play"
+    slogan_power_of_play: "Aprenda a codificar a través del poder del juego"
     teach_cs1_free: "Enseña programación gratis"
     teachers_love_codecombat_title: "A los maestros les encanta CodeCombat"
     teachers_love_codecombat_blurb1: "Informan que sus estudiantes disfrutan usando CodeCombat para aprender a programar"
     teachers_love_codecombat_blurb2: "Recomendarían CodeCombat a otros maestros de Ciencias de la Computación"
     teachers_love_codecombat_blurb3: "Dicen que CodeCombat los ayuda a mejorar las habilidades de resolución de problemas de sus estudiantes"
     teachers_love_codecombat_subblurb: "En asociación con McREL International, líder en orientación basada en investigación y evaluaciones de tecnología educativa."
-#    top_banner_blurb: "Parents, give your child the gift of coding and personalized instruction with our live teachers!"
-#    top_banner_summer_camp: "Enrollment now open for our summer coding camps–ask us about our week-long virtual sessions starting at just $199."
+    top_banner_blurb: "¡Padres, denle a su hijo el regalo de la codificación y la instrucción personalizada con nuestros maestros en vivo!"
+    top_banner_summer_camp: "La inscripción ya está abierta para nuestros campamentos de programación de verano; pregúntenos acerca de nuestras sesiones virtuales de una semana a partir de solo $ 199dls."
     try_the_game: "Prueba el juego"
     classroom_edition: "Versión para el aula:"
     learn_to_code: "Aprende a programar:"
     play_now: "Jugar Ahora"
-#    im_a_parent: "I'm a Parent"
+    im_a_parent: "Soy Padre/Madre"
     im_an_educator: "Soy un Educador"
     im_a_teacher: "Soy un Maestro"
     im_a_student: "Soy un Estudiante"
@@ -163,37 +163,37 @@
     want_coco: "¿Quisiera CodeCombat en su escuela?"
     educator: "Maestro"
     student: "Estudiante"
-#    our_coding_programs: "Our Coding Programs"
+    our_coding_programs: "Nuestros programas de codificación"
 #    codecombat: "CodeCombat"
 #    ozaria: "Ozaria"
-#    codecombat_blurb: "Our original coding game. Recommended for parents, individuals, educators, and students who want to experience one of the most-loved coding games in the world."
-#    ozaria_blurb: "An adventure game and Computer Science program where students master the lost magic of coding to save their world. Recommended for educators and students."
-#    try_codecombat: "Try CodeCombat"
-#    try_ozaria: "Try Ozaria"
-#    explore_codecombat: "Explore CodeCombat"
-#    explore_ai_league: "Explore AI League"
-#    explore_ozaria: "Explore Ozaria"
-#    explore_online_classes: "Explore Online Classes"
-#    explore_pd: "Explore Professional Development"
-#    new_adventure_game_blurb: "Ozaria is our brand new adventure game and your turnkey solution for teaching Computer science. Our student-facing __slides__ and teacher-facing notes make planning and delivering lessons easier and faster."
-#    lesson_slides: "lesson slides"
-#    pd_blurb: "Learn the skills to effectively teach computer science with our self-directed, CSTA-accredited professional development course. Earn up to 40 credit hours any time, from any device. Pairs well with Ozaria Classroom."
-#    ai_league_blurb: "Competitive coding has never been so epic with this educational esports league, uniquely both an AI battle simulator and game engine for learning real code."
-#    codecombat_live_online_classes: "CodeCombat Live Online Classes"
-#    learning_technology_blurb: "Our original game teaches real-world skills through the power of play. The scaffolded curriculum systematically builds on student’s experiences and knowledge as they progress."
-#    learning_technology_blurb_short: "Our innovative game-based learning technology has transformed the way students learn to code."
-#    online_classes_blurb: "Our online coding classes combine the power of gameplay and personalized instruction for an experience your child will love. With both private or group options available, this is remote learning that works."
-#    for_educators: "For Educators"
-#    for_parents: "For Parents"
-#    for_everyone: "For Everyone"
-#    what_our_customers_are_saying: "What Our Customers Are Saying"
-#    game_based_learning: "Game-Based Learning"
-#    unique_approach_blurb: "With our unique approach, students embrace learning as they play and write code from the very start of their adventure, promoting active learning and a growth mindset."
-#    text_based_coding: "Text-Based Coding"
-#    custom_code_engine_blurb: "Our custom code engine and interpreter is designed for beginners, teaching true Python, JavaScript, and C++ programming languages using human, beginner-friendly terms."
-#    student_impact: "Student Impact"
-#    help_enjoy_learning_blurb: "Our products have helped over 20 million students enjoy learning Computer Science, teaching them to be critical, confident, and creative learners. We engage all students, regardless of experience, helping them to realize a pathway to success in Computer Science."
-#    global_community: "Join Our Global Community"
+    codecombat_blurb: "Nuestro juego de codificación original. Recomendado para padres, individuos, educadores y estudiantes que quieran experimentar uno de los juegos de codificación más queridos del mundo."
+    ozaria_blurb: "Un juego de aventuras y un programa de informática donde los estudiantes dominan la magia perdida de la codificación para salvar su mundo. Recomendado para educadores y estudiantes."
+    try_codecombat: "Prueba CodeCombat"
+    try_ozaria: "Prueba Ozaria"
+    explore_codecombat: "Explora CodeCombat"
+    explore_ai_league: "Explora AI League"
+    explore_ozaria: "Explora Ozaria"
+    explore_online_classes: "Explore las clases en línea"
+    explore_pd: "Explore el desarrollo profesional"
+    new_adventure_game_blurb: "Ozaria es nuestro nuevo juego de aventuras y su solución llave en mano para la enseñanza de la informática. Nuestras __ diapositivas__ para los estudiantes y las notas para los maestros hacen que la planificación y la entrega de lecciones sean más fáciles y rápidas."
+    lesson_slides: "diapositivas de la lección"
+    pd_blurb: "Aprenda las habilidades para enseñar ciencias de la computación de manera efectiva con nuestro curso de desarrollo profesional autodirigido y acreditado por la CSTA. Obtenga hasta 40 horas crédito en cualquier momento, desde cualquier dispositivo. Combina bien con Ozaria Classroom."
+    ai_league_blurb: "La codificación competitiva nunca ha sido tan épica con esta liga educativa de deportes electrónicos, exclusivamente un simulador de batalla de IA y un motor de juego para aprender código real."
+    codecombat_live_online_classes: "Clases en línea de CodeCombat Live"
+    learning_technology_blurb: "Nuestro juego original enseña habilidades del mundo real a través del poder del juego. El plan de estudios escalonado se basa sistemáticamente en las experiencias y el conocimiento de los estudiantes a medida que avanzan."
+    learning_technology_blurb_short: "Nuestra innovadora tecnología de aprendizaje basada en juegos ha transformado la forma en que los estudiantes aprenden a codificar."
+    online_classes_blurb: "Nuestras clases de codificación en línea combinan el poder del juego y la instrucción personalizada para una experiencia que le encantará a su hijo. Con opciones tanto privadas como grupales disponibles, este es un aprendizaje remoto que funciona."
+    for_educators: "Para Maestros"
+    for_parents: "Para Padres"
+    for_everyone: "Para Todos"
+    what_our_customers_are_saying: "Lo que nuestros clientes están diciendo"
+    game_based_learning: "Aprendizaje basado en juegos"
+    unique_approach_blurb: "Con nuestro enfoque único, los estudiantes adoptan el aprendizaje mientras juegan y escriben código desde el comienzo de su aventura, promoviendo el aprendizaje activo y una mentalidad de crecimiento."
+    text_based_coding: "Codificación basada en texto"
+    custom_code_engine_blurb: "Nuestro motor e intérprete de código personalizado está diseñado para principiantes, y enseña verdaderos lenguajes de programación Python, JavaScript y C ++ utilizando términos humanos para principiantes."
+    student_impact: "Impacto estudiantil"
+    help_enjoy_learning_blurb: "Nuestros productos han ayudado a más de 20 millones de estudiantes a disfrutar del aprendizaje de la informática, enseñándoles a ser aprendices críticos, seguros y creativos. Involucramos a todos los estudiantes, independientemente de su experiencia, ayudándolos a realizar un camino hacia el éxito en Ciencias de la Computación."
+    global_community: "Únase a nuestra comunidad global"
 #    million: "__num__ Million"
 #    billion: "__num__ Billion"

@@ -241,15 +241,15 @@
     apcsp: "Principios AP CS"
     parent: "Padres"
 #    esports: "Esports"
-#    browser_recommendation: "For the best experience we recommend using the latest version of Chrome. Download the browser here!"
-#    ozaria_classroom: "Ozaria Classroom"
-#    codecombat_classroom: "CodeCombat Classroom"
-#    ozaria_dashboard: "Ozaria Dashboard"
-#    codecombat_dashboard: "CodeCombat Dashboard"
-#    professional_development: "Professional Development"
-#    new: "New!"
-#    admin: "Admin"
-#    api_dashboard: "API Dashboard"
+    browser_recommendation: "Para obtener la mejor experiencia, recomendamos utilizar la última versión de Chrome. ¡Descarga el navegador aquí!"
+    ozaria_classroom: "Aula Ozaria"
+    codecombat_classroom: "Aula CodeCombat"
+    ozaria_dashboard: "Tablero de Ozaria"
+    codecombat_dashboard: "Tablero de CodeCombat"
+    professional_development: "Desarrollo profesional"
+    new: "Nuevo!"
+    admin: "Administrador"
+    api_dashboard: "Tablero de API"

   modal:
     close: "Cerrar"


atom-chktex: uninstall and remove from config

(cherry picked from commit efc38ac9da8acc44443d1d81fc34d23bf85c4468)

--- .atom/config.cson
+++ .atom/config.cson
@@ -14,7 +14,6 @@
   core:
     disabledPackages: [
       "linter-python-pep257"
-      "linter-chktex"
       "linter-pep257"
       "spell-check"
       "linter-shellcheck"


atom: update config

atom: update config

--- .atom/config.cson
+++ .atom/config.cson
@@ -101,6 +101,7 @@
     position: "Right"
   "tree-view":
     hideIgnoredNames: true
+    squashDirectoryNames: true
   "vim-mode":
     useClipboardAsDefaultRegister: true
   "vim-surround": {}


[atom] map project-opener to cmd-opt-p

[atom] map project-opener to cmd-opt-p

--- .atom/keymap.cson
+++ .atom/keymap.cson
@@ -1,2 +1,6 @@
 'atom-text-editor:not([mini])':
   'ctrl-u': 'sort-lines:sort'
+'atom-text-editor':
+  'alt-cmd-p': 'unset!'
+'.platform-darwin':
+  'alt-cmd-p': 'project-plus:toggle-project-finder'


An inactive workflow shouldn't be a project's default

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -1,6 +1,5 @@
 React = require 'react'
 WorkflowToggle = require '../../components/workflow-toggle'
-PromiseRenderer = require '../../components/promise-renderer'
 SetToggle = require '../../lib/set-toggle'
 Dialog = require 'modal-form/dialog'
 getWorkflowsInOrder = require '../../lib/get-workflows-in-order'
@@ -17,10 +16,17 @@
       private: false
       beta_requested: false
       launch_requested: false
+    workflows: null

   mixins: [SetToggle]

   setterProperty: 'project'
+
+  componentDidMount: ->
+    getWorkflowsInOrder(@props.project, fields: 'display_name,active')
+      .then((workflows) =>
+        @setState({ workflows })
+      )

   isReviewable: ->
     not @props.project.private and
@@ -57,6 +63,18 @@

,
       closeButton: true
     )
+
+  handleWorkflowSettingChange: (workflow, e) ->
+    checked = e.target.checked
+
+    workflow.update({ 'active': checked }).save()
+      .catch((error) =>
+        @setState {error}
+      ).then((workflow) =>
+        if not workflow.active and workflow.id is @props.project.configuration.default_workflow
+          @props.project.update({ 'configuration.default_workflow': null })
+          @props.project.save()
+      ).then(() => @forceUpdate()) # Dislike. Eventually we should refactor to not have to call this.forceUpdate()

   render: ->
     looksDisabled =
@@ -197,19 +215,27 @@

Workflow Settings

-      <PromiseRenderer promise={getWorkflowsInOrder @props.project, fields: 'display_name,active'}>{(workflows) =>
-        if workflows.length is 0
-
No workflows found

-        else
-

-

-            {workflows.map (workflow) =>
-

-                <WorkflowToggle workflow={workflow} project={@props.project} field="active" />
-
}
-

-

-      }</PromiseRenderer>
+      {if @state.workflows is null
+
Loading workflows...

+      else if @state.workflows.length is 0
+
No workflows found

+      else
+

+

+          {@state.workflows.map (workflow) =>
+            setting = workflow.active
+

+
+                {workflow.id} - {workflow.display_name}:
+
+                  <input type="checkbox" name="active" value={setting} checked={setting} onChange={@handleWorkflowSettingChange.bind(this, workflow)} />
+                  Active
+
+
+
}
+

+
}

In a live project active workflows are available to volunteers and cannot be edited. Inactive workflows can be edited if a project is live or in development.

+
If an active workflow is the default workflow for the project and is made inactive, then it will be removed as the default workflow.

On a live project, if you want to switch which subjects sets are associated with an active workflow: set the workflow to inactive, next change which subject sets are linked in the Workflow Section within the Project Builder, then return the workflow to active.


JReward: no need to log if users doesn't have any referrer

--- workers/social/lib/social/models/rewards/index.coffee
+++ workers/social/lib/social/models/rewards/index.coffee
@@ -245,7 +245,7 @@
     fetchReferrer = (account, callback)->

       unless referrerUsername = account.referrerUsername
-        return callback new KodingError "User doesn't have any referrer"
+        return callback null # User doesn't have any referrer

       # get referrer
       JAccount.one 'profile.nickname': referrerUsername, (err, referrer)->
@@ -254,7 +254,7 @@

         unless referrer
           # if referrer not fonud then do nothing and return
-          return callback new KodingError "Referrer couldn't found"
+          return callback null # Referrer couldn't found

         callback null, referrer

@@ -335,6 +335,7 @@

           fetchReferrer account, (err, _referrer)->
             return logError err  if err
+            return  unless _referrer
             referrer = _referrer
             queue.next()

@@ -386,7 +387,7 @@

           fetchReferrer me, (err, _referrer)->
             return logError err  if err
-
+            return  unless _referrer
             referrer = _referrer
             queue.next()


don't remove - prefixes for lemgrams in autocomplete

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -590,7 +590,7 @@
                 })

         scope.lemgramify = (lemgram) ->
-            lemgramRegExp = /([^_\.-]*--)?([^-]*)\.\.(\w+)\.(\d\d?)/
+            lemgramRegExp = /([^_\.-]*--)?(.*)\.\.(\w+)\.(\d\d?)/
             match = lemgram.match lemgramRegExp
             unless match then return false
             return {


Fix abortion of dialog

Fix abortion of dialog

--- app/scripts/lib/Dialog.coffee
+++ app/scripts/lib/Dialog.coffee
@@ -20,7 +20,9 @@
     @showDialog()

   showDialog: (start = 0) ->
-    Crafty('Dialog').each -> @destroy()
+    Crafty('Dialog').each ->
+      @trigger('Abort')
+      @destroy()
     dialogIndex = @determineDialog(start)

     unless dialogIndex?
@@ -41,6 +43,7 @@
         y: @level.visibleHeight - ((lines.length + 1) * 20)
         z: 100
       )
+    back.bind('Abort', => @handle('Abort'))

     speakerText = Crafty.e('2D, Canvas, Text')
       .attr(w: 550, x: back.x + 10, y: back.y + 10, z: 101, alpha: 1)
@@ -74,7 +77,6 @@
         @showDialog(start + 1)
       , 2500 * lines.length, 0)

-
   determineDialog: (start = 0) ->
     elements = []
     Crafty('Player ControlScheme').each ->


More :lipstick:

More :lipstick:

--- spec/settings-panel-spec.coffee
+++ spec/settings-panel-spec.coffee
@@ -37,11 +37,11 @@
             order: 100
       atom.config.setSchema("foo", config)
       atom.config.setDefaults("foo", gong: 'gong')
-      expect(_.size(atom.config.getSettings()["foo"])).toBe 5
+      expect(_.size(atom.config.getSettings().foo)).toBe 5
       settingsPanel = new SettingsPanel("foo", {includeTitle: false})

     it "sorts settings by order and then alphabetically by the key", ->
-      settings = atom.config.getSettings()["foo"]
+      settings = atom.config.getSettings().foo
       expect(_.size(settings)).toBe 5
       sortedSettings = settingsPanel.sortSettings("foo", settings)
       expect(sortedSettings[0]).toBe 'zing'


Log errors in generators.

Log errors in generators.

--- src/invoke/generator.coffee
+++ src/invoke/generator.coffee
@@ -23,6 +23,7 @@
     try
       res = gen.next value
     catch err
+      console.error err.stack
       return done err

     prev = last


change startup message to show name, version, mode & url

--- app/express.coffee
+++ app/express.coffee
@@ -2,6 +2,7 @@
 flash      = require 'connect-flash'
 http       = require 'http'
 _          = require 'underscore'
+pkg        = require '../package.json'
 middleware = require '../app/middleware'
 routes     = require '../routes'
 config     = require '../config'
@@ -96,4 +97,4 @@
 # Http
 http.createServer(app).listen(3000)

-console.log "Express server listening on port 3000"
+console.log "Argumenta #{pkg.version} (#{config.appMode} mode) | http://localhost:3000"


Refactored logging module

Refactored logging module

--- app/logging.coffee
+++ app/logging.coffee
@@ -8,17 +8,18 @@
   return (component) ->
     if loggers[component]?
       return loggers[component]
+    else if cfg.get 'log level'
+      transports = [
+        new winston.transports.Console
+          level: cfg.get 'log level'
+      ]
+
+      if not cfg.disabled 'log to file'
+        transports.push new winston.transports.File
+          filename: "#{cfg.get 'logs path'}/#{component}.log",
+          level: cfg.get 'log level'
+
+      return loggers[component] =
+        new winston.Logger transports: transports
     else
-      return loggers[component] =
-        if cfg.get 'log level'
-          new winston.Logger transports: [
-            new winston.transports.Console {
-              level: cfg.get 'log level'
-            }
-            new winston.transports.File {
-              filename: "#{cfg.get 'logs path'}/#{component}.log",
-              level: cfg.get 'log level'
-            }
-          ]
-        else
-          fakeLogger+      return fakeLogger


Use EditSession.getSoftTabs instead of .softTabs attr in snippets spec

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -168,7 +168,7 @@
       describe "when the snippet contains hard tabs", ->
         describe "when the edit session is in soft-tabs mode", ->
           it "translates hard tabs in the snippet to the appropriate number of spaces", ->
-            expect(editSession.softTabs).toBeTruthy()
+            expect(editSession.getSoftTabs()).toBeTruthy()
             editor.insertText("t3")
             editor.trigger keydownEvent('tab', target: editor[0])
             expect(buffer.lineForRow(1)).toBe "  line 2"
@@ -192,7 +192,7 @@

         describe "when the snippet spans multiple lines", ->
           it "indents the subsequent lines of the snippet to be even with the start of the first line", ->
-            expect(editSession.softTabs).toBeTruthy()
+            expect(editSession.getSoftTabs()).toBeTruthy()
             editor.setCursorScreenPosition([2, Infinity])
             editor.insertText ' t3'
             editor.trigger 'snippets:expand'


Groups: debitPack and creditPack methods are added to subscription

--- workers/social/lib/social/models/payment/subscription.coffee
+++ workers/social/lib/social/models/payment/subscription.coffee
@@ -1,5 +1,6 @@
-jraphical = require 'jraphical'
-payment   = require 'koding-payment'
+jraphical   = require 'jraphical'
+payment     = require 'koding-payment'
+KodingError = require '../../error'

 forceRefresh  = yes
 forceInterval = 60 * 3
@@ -353,3 +354,15 @@
             @downgrade transitionOptions, callback
           else
             @upgrade transitionOptions, callback
+
+  debitPack: ({tag, multiplyFactor}, callback) ->
+    multiplyFactor ?= 1
+    JPaymentPack = require './pack'
+    JPaymentPack.one tags: tag, (err, pack) =>
+      console.err err if err
+      return callback new KodingError "#{tag} pack not found"  unless pack
+      console.log 'pack', pack
+      @debit {pack, multiplyFactor}, callback
+
+  creditPack: ({tag}, callback) ->
+    @debitPack {tag, multiplyFactor: -1}, callback


Simple recovery from coffee quoting errors in a socket.

--- src/languages/coffee.coffee
+++ src/languages/coffee.coffee
@@ -970,6 +970,8 @@
 # =============

 fixCoffeeScriptError = (lines, e) ->
+  if lines.length is 1 and /^['"]|['"]$/.test lines[0]
+    return fixQuotedString lines
   if /unexpected\s*(?:newline|if|for|while|switch|unless|end of input)/.test(
       e.message) and /^\s*(?:if|for|while|unless)\s+\S+/.test(
       lines[e.location.first_line])
@@ -987,6 +989,40 @@
       return backTickLine lines, unmatchedline

   return null
+
+# To fix quoting errors, we first do a lenient C-unescape, then
+# we do a string C-escaping, to add backlsashes where needed, but
+# not where we already have good ones.
+fixQuotedString = (lines) ->
+  line = lines[0]
+  quotechar = if /^"|"$/.test(line) then '"' else "'"
+  if line.getCharAt(0) is quotechar
+    line = line.substr(1)
+  if line.getCharAt(line.length - 1) is quotechar
+    line = line.substr(0, line.length -1)
+  return quoteAndCEscape fixQuotedLine line
+
+looseCUnescape = (str) ->
+  codes =
+    '\\b': '\b'
+    '\\t': '\t'
+    '\\n': '\n'
+    '\\f': '\f'
+    '\\"': '"'
+    "\\'": "'"
+    "\\\\": "\\"
+    "\\0": "\0"
+  str.replace /\\[btnf'"\\0]|\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}/g, (m) ->
+    if m.length is 2 then return codes[m]
+    return String.fromCharCode(parseInt(m.substr(1), 16))
+
+quoteAndCEscape = (str, quotechar) ->
+  result = JSON.stringify(str)
+  if quotechar is "'"
+    return quotechar +
+      result.substr(1, result.length -2).replace(/\\"/g, '"').
+      replace(/'/g, "\\'") + quotechar
+  return result

 findUnmatchedLine = (lines, above) ->
   # Not done yet


Bootstrapper: Required spark.core.Router.

--- src/lib/Bootstrapper.coffee
+++ src/lib/Bootstrapper.coffee
@@ -16,6 +16,7 @@
 goog.require 'spark.components.DraggableView'
 goog.require 'spark.components.Overlay'
 goog.require 'spark.components.Modal'
+goog.require 'spark.core.Router'

 class spark.Bootstrapper


HistoryManager: Handle destroy in history manager to dispose the goog.History instance.

--- src/lib/core/HistoryManager.coffee
+++ src/lib/core/HistoryManager.coffee
@@ -37,8 +37,8 @@
     {useHtml5History} = options

     if isHtml5HistorySupported and useHtml5History
-      tokenTransformer = new spark.core.HistoryManager.TokenTransformer
-      @history = new goog.history.Html5History null, tokenTransformer
+      @tokenTransformer = new spark.core.HistoryManager.TokenTransformer
+      @history = new goog.history.Html5History null, @tokenTransformer
       @history.setPathPrefix  options.pathPrefix
       @history.setUseFragment no
     else
@@ -68,7 +68,25 @@
     @private
   ###
   getHistoryInput_: ->
-    return new spark.components.Input().getElement()
+    @historyInput_ = new spark.components.Input()
+    return @historyInput_.getElement()
+
+
+  ###*
+    Destroys the HistoryManager and goog.history instance.
+
+    @export
+    @override
+  ###
+  destroy: ->
+    unless @isDestroyed()
+      @history.disposeInternal()
+      @history = null
+
+      @historyInput_.destroy()
+      @tokenTransformer = null
+
+    super

 ###*


Remove workspace snapshot data from storage by user name

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -723,7 +723,8 @@

   removeWorkspaceSnapshot: ->

-    @mountedMachine.getBaseKite().storageDelete @getWorkspaceSnapshotName()
+    key = @getWorkspaceSnapshotName nick()
+    @mountedMachine.getBaseKite().storageDelete key

   getWorkspaceSnapshotName: (username) ->


User removed modal

User removed modal

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1311,3 +1311,21 @@
           e.stopPropagation()

           @goToTabNumber parseInt(match[1], 10) - 1
+
+
+  showUserRemovedModal: ->
+
+    options        =
+      title        : 'Machine access revoked'
+      content      : 'Your access to this machine has been removed by its owner.'
+      blocking     : yes
+      buttons      :
+        quit       :
+          style    : 'solid light-gray medium'
+          title    : 'OK'
+          callback : =>
+
+            @modal.destroy()
+            @quit()
+
+    @showModal options


IDE:Index: check before if machine exists

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -322,9 +322,9 @@
       @splitTabView 'horizontal', createNewEditor: no
       @getMountedMachine (err, machine) =>

+        return unless machine
+
         machine = new Machine { machine }  unless machine instanceof Machine
-
-        return unless machine

         for ideView in @ideViews
           ideView.mountedMachine = @mountedMachine


IDE: Save open folders to drive.

IDE: Save open folders to drive.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1315,12 +1315,13 @@
         if context.file?.content
           delete context.file.content

+      if @amIHost and change.type is 'FileTreeInteraction'
+        @saveOpenFoldersToDrive()
+
       @changes.push change

   ###*
-   * Am I watching to change's owner?
-   *
    * @param {string} origin  Nickname of the change's owner
    * @return {boolean}
   ###


converted a portion of prefs-colours to js

--- client/prefs-colours.coffee
+++ client/prefs-colours.coffee
@@ -1,81 +1,92 @@
-prefs=require './prefs'
-{join,dict,esc}=require './util'
-
-@name='Colours'
-
-G=[];H={} # G:syntax highlighting groups {t,s,c,controls}; H:reverse lookup dict for G
-D.addSyntaxGroups=(groups)->G=G.concat groups;H=dict(G.map (g,i)->[g.t,i]);updateStyle?();return
-D.addSyntaxGroups [
-  # t: token type, a short key for storing customisations in localStorage
-  # s: name to display in the UI
-  # c: css selector
-  # controls: which UI controls are allowed or disallowed for this group (other than the default ones)
-  {t:'norm',s:'normal',          c:'.cm-s-default,.CodeMirror-gutters'}
-  {t:'num', s:'number',          c:'.cm-apl-num' }
-  {t:'str', s:'string',          c:'.cm-apl-str' }
-  {t:'zld', s:'zilde',           c:'.cm-apl-zld' }
-  {t:'var', s:'name',            c:'.cm-apl-var' }
-  {t:'glb', s:'global name',     c:'.cm-apl-glb' }
-  {t:'quad',s:'quad name',       c:'.cm-apl-quad'}
-  {t:'fn',  s:'function',        c:'.cm-apl-fn'  }
-  {t:'op1', s:'monadic operator',c:'.cm-apl-op1' }
-  {t:'op2', s:'dyadic operator', c:'.cm-apl-op2' }
-  {t:'ns',  s:'namespace',       c:'.cm-apl-ns'  }
-  {t:'asgn',s:'assignment',      c:'.cm-apl-asgn'}
-  {t:'diam',s:'diamond',         c:'.cm-apl-diam'}
-  {t:'par', s:'parenthesis',     c:'.cm-apl-par' }
-  {t:'sqbr',s:'bracket',         c:'.cm-apl-sqbr'}
-  {t:'semi',s:'semicolon',       c:'.cm-apl-semi'}
-  {t:'dfn', s:'dfn',             c:'.cm-apl-dfn' }
-  {t:'dfn1',s:'dfn level 1',     c:'.cm-apl-dfn1'}
-  {t:'dfn2',s:'dfn level 2',     c:'.cm-apl-dfn2'}
-  {t:'dfn3',s:'dfn level 3',     c:'.cm-apl-dfn3'}
-  {t:'dfn4',s:'dfn level 4',     c:'.cm-apl-dfn4'}
-  {t:'dfn5',s:'dfn level 5',     c:'.cm-apl-dfn5'}
-  {t:'trad',s:'tradfn',          c:'.cm-apl-trad'}
-  {t:'kw',  s:'keyword',         c:'.cm-apl-kw'  }
-  {t:'lbl', s:'label',           c:'.cm-apl-lbl' }
-  {t:'idm', s:'idiom',           c:'.cm-apl-idm' }
-  {t:'com', s:'comment',         c:'.cm-apl-com' }
-  {t:'err', s:'error',           c:'.cm-apl-err' }
-  {t:'lnum',s:'line number',     c:'.CodeMirror-linenumber'}
-  {t:'cur', s:'cursor',          c:'div.CodeMirror-cursor', controls:{lb:1,fg:0,bg:0,BIU:0}}
-  {t:'mtch',s:'matching bracket',c:'.CodeMirror-matchingbracket'}
-  {t:'srch',s:'search match',    c:'.cm-searching'}
-  {t:'mod', s:'modified line',   c:'.modified'}
-  {t:'sel', s:'selection',       c:'.CodeMirror-selected,.CodeMirror-focused .CodeMirror-selected', controls:{fg:0,BIU:0}}
+`
+var prefs=require('./prefs'),util=require('./util'),join=util.join,dict=util.dict,esc=util.esc
+
+this.name='Colours'
+
+var G=[],H={} // G:syntax highlighting groups {t,s,c,ctrls}; H:reverse lookup dict for G
+D.addSyntaxGroups=function(x){G=G.concat(x);H=dict(G.map(function(g,i){return[g.t,i]}));builtInSchemes&&updateStyle()}
+D.addSyntaxGroups([
+  // t: token type, a short key for storing customisations in localStorage
+  // s: name to display in the UI
+  // c: css selector
+  // ctrls: which UI controls are allowed or disallowed for this group (other than the default ones)
+  {t:'norm',s:'normal',          c:'.cm-s-default,.CodeMirror-gutters'},
+  {t:'num', s:'number',          c:'.cm-apl-num' },
+  {t:'str', s:'string',          c:'.cm-apl-str' },
+  {t:'zld', s:'zilde',           c:'.cm-apl-zld' },
+  {t:'var', s:'name',            c:'.cm-apl-var' },
+  {t:'glb', s:'global name',     c:'.cm-apl-glb' },
+  {t:'quad',s:'quad name',       c:'.cm-apl-quad'},
+  {t:'fn',  s:'function',        c:'.cm-apl-fn'  },
+  {t:'op1', s:'monadic operator',c:'.cm-apl-op1' },
+  {t:'op2', s:'dyadic operator', c:'.cm-apl-op2' },
+  {t:'ns',  s:'namespace',       c:'.cm-apl-ns'  },
+  {t:'asgn',s:'assignment',      c:'.cm-apl-asgn'},
+  {t:'diam',s:'diamond',         c:'.cm-apl-diam'},
+  {t:'par', s:'parenthesis',     c:'.cm-apl-par' },
+  {t:'sqbr',s:'bracket',         c:'.cm-apl-sqbr'},
+  {t:'semi',s:'semicolon',       c:'.cm-apl-semi'},
+  {t:'dfn', s:'dfn',             c:'.cm-apl-dfn' },
+  {t:'dfn1',s:'dfn level 1',     c:'.cm-apl-dfn1'},
+  {t:'dfn2',s:'dfn level 2',     c:'.cm-apl-dfn2'},
+  {t:'dfn3',s:'dfn level 3',     c:'.cm-apl-dfn3'},
+  {t:'dfn4',s:'dfn level 4',     c:'.cm-apl-dfn4'},
+  {t:'dfn5',s:'dfn level 5',     c:'.cm-apl-dfn5'},
+  {t:'trad',s:'tradfn',          c:'.cm-apl-trad'},
+  {t:'kw',  s:'keyword',         c:'.cm-apl-kw'  },
+  {t:'lbl', s:'label',           c:'.cm-apl-lbl' },
+  {t:'idm', s:'idiom',           c:'.cm-apl-idm' },
+  {t:'com', s:'comment',         c:'.cm-apl-com' },
+  {t:'err', s:'error',           c:'.cm-apl-err' },
+  {t:'lnum',s:'line number',     c:'.CodeMirror-linenumber'},
+  {t:'cur', s:'cursor',          c:'div.CodeMirror-cursor', ctrls:{lb:1,fg:0,bg:0,BIU:0}},
+  {t:'mtch',s:'matching bracket',c:'.CodeMirror-matchingbracket'},
+  {t:'srch',s:'search match',    c:'.cm-searching'},
+  {t:'mod', s:'modified line',   c:'.modified'},
+  {t:'sel', s:'selection',       c:'.CodeMirror-selected,.CodeMirror-focused .CodeMirror-selected', ctrls:{fg:0,BIU:0}},
   {t:'tc',  s:'tracer',          c:'.tracer .CodeMirror,.tracer .CodeMirror .CodeMirror-gutter-wrapper'}
-]
-
-# Colour schemes are represented in one of two ways:
-#   In memory                 In localStorage
-#     {                         {
-#       "name": "MyScheme",       "name": "MyScheme",
-#       "group1": {               "styles": "group1=fg:f00,B group2=bg:f00 ..."
-#         "fg": "f00",          }
-#         "B": 1
-#       },
-#       "group2": {
-#         "bg": "f00"
-#       },
-#       ...
-#     }
-# encodeScheme() and decodeScheme() convert between the two.
-
-encodeScheme = (x) ->
-  s = ''
-  for g, h of x when g != 'name'
-    u = ''; for p, v of h then u += ",#{p}"; if !(p in ['B', 'I', 'U'] && v) then u += ":#{v}"
-    u && s += " #{g}=#{u[1..]}"
-  name: x.name, styles: s[1..]
-
-decodeScheme = (x) ->
-  {name, styles} = x; r = {name}
-  if styles
-    for gh in styles.split /\s+/ when gh
-      [g, s1] = gh.split '='; r[g] = h = {}; for pv in s1.split ',' then [p, v] = pv.split ':'; h[p] = v ? 1
-      h.bgo? && h.bgo = +h.bgo # if .bgo is present, convert it to a number
-  r
+])
+
+// Colour schemes have two representations:
+//   in memory                 in localStorage
+//     {                         {
+//       "name": "MyScheme",       "name": "MyScheme",
+//       "group1": {               "styles": "group1=fg:f00,B group2=bg:f00 ..."
+//         "fg": "f00",          }
+//         "B": 1
+//       },
+//       "group2": {
+//         "bg": "f00"
+//       },
+//       ...
+//     }
+// encodeScheme() and decodeScheme() convert between them
+function encodeScheme(x){
+  var s=''
+  for(var g in x)if(g!=='name'){
+    var u=''
+    for(var p in x[g]){
+      var v=x[g][p]
+      u+=','+p
+      if(!((p==='B'||p==='I'||p==='U')&&v)){u+=':'+v}
+    }
+    u&&(s+=' '+g+'='+u.slice(1))
+  }
+  return{name:x.name,styles:s.slice(1)}
+}
+function decodeScheme(x){           // x:for example "num=fg:345,bg:f,B,U,bgo:.5 str=fg:2,I com=U"
+  var r={name:x.name}               // r:this will be the result
+  var a=(x.styles||'').split(/\s+/) // a:for example ["num=fg:345,bg:f,B,U,bgo:.5","str=fg:2,I","com=U"]
+  for(var i=0;i<a.length;i++)if(a[i]){
+    var b=a[i].split('='),g=b[0],c=b[1].split(','),h=r[g]={}  // b:["num","fg:345,bg:f,B,U,bgo:.5"]  g:"num" (the group)
+    for(var j=0;j<c.length;j++){                              // c:["fg:345","bg:f","B","U","bgo:.5"]
+      var pv=c[j].split(':'),p=pv[0],v=pv[1];h[p]=v!=null?v:1 // p:"fg" v:"345"  or  p:"B" v:undefined
+    }
+    h.bgo!=null&&(h.bgo=+h.bgo)     // if .bgo (background opacity) is present, convert it to a number
+  }
+  return r
+}
+`

 builtInSchemes = [
   {name: 'Default', styles: '''
@@ -270,7 +281,7 @@
       $("#col-#{p}").val(expandColour(h[p]) || '#000000').toggle !!h[p]
     for p in 'BIU' then $("#col-#{p}").prop 'checked', !!h[p]
     $('#col-bgo').slider 'value', h.bgo ? .5
-    c = (G[i] || G[0]).controls || {}
+    c = (G[i] || G[0]).ctrls || {}
     $('#col-fg-p' ).toggle !!(c.fg ? 1)
     $('#col-bg-p' ).toggle !!(c.bg ? 1)
     $('#col-bgo'  ).toggle !!h.bg


Support for native callback

Support for native callback

--- lib/model_extensions.coffee
+++ lib/model_extensions.coffee
@@ -3,6 +3,8 @@
 Backbone = @Backbone or require 'backbone'
 JSONUtils = require './json_utils'
 Queue = require 'queue-async'
+Utils = require './utils'
+adapters = Utils.adapters

 module.exports = (model_type, sync) ->

@@ -122,6 +124,7 @@
     else
       (attributes = {})[key] = value;

+    options = adapters.bbCallback(options) if _.isFunction(options) # node style callback
     original_success = options.success
     original_error = options.error
     options = _.clone(options)


use not instead of unless

use not instead of unless

--- src/compilers.coffee
+++ src/compilers.coffee
@@ -84,7 +84,7 @@
       .include(path.dirname(_path))
       .set('include css', ('--includeCss' in process.argv))
       .set('line-numbers', compilers.DEBUG)
-      .set('compress', unless compilers.DEBUG)
+      .set('compress', not compilers.DEBUG)
       .render((err, css) ->
         throw err if err
         result = css


Whitespace

Whitespace

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -139,7 +139,6 @@
         @settingsButton.hide()
         @enablementButton.hide()

-
     if @isInstalled() or @isDisabled()
       @installButton.hide()
       @uninstallButton.show()


Be more careful with objects (when they are not fully loaded)

--- client/view/proposal.coffee
+++ client/view/proposal.coffee
@@ -32,7 +32,7 @@

   photo: (user) -> user.photoUrl(40)

-  editMode: -> @proposal.editing and canEdit(@proposal)
+  editMode: -> @proposal?.editing and canEdit(@proposal)

 canEdit = (proposal) ->
   Meteor.userId() and (proposal.mine() or User.current().admin())


don't require julia for kill

don't require julia for kill

--- lib/package/commands.coffee
+++ lib/package/commands.coffee
@@ -60,7 +60,7 @@
     @subs.add atom.commands.add 'atom-workspace',
       'julia-client:open-a-repl': -> juno.connection.terminal.repl()
       'julia-client:start-julia': -> disrequireClient 'boot Julia', -> boot()
-      'julia-client:kill-julia': => requireClient 'kill Julia', -> juno.connection.client.kill()
+      'julia-client:kill-julia': -> juno.connection.client.kill()
       'julia-client:interrupt-julia': => requireClient 'interrupt Julia', -> juno.connection.client.interrupt()
       'julia-client:reset-julia-server': -> juno.connection.local.server.reset()
       'julia-client:connect-to-local-port': -> disrequireClient -> juno.connection.terminal.connectPortUI()


Fix dump output for mruby builder

Fix dump output for mruby builder

--- src/builder/mrubybuilder.coffee
+++ src/builder/mrubybuilder.coffee
@@ -111,6 +111,7 @@
     en: "mruby compiler dump output"
     ja: "mruby コンパイラダンプ出力"
   })
+  DUMP_ENCODING = RB_ENCODING

   #--------------------------------------------------------------------------------
   # Public methods
@@ -180,7 +181,10 @@
     ).then((data) =>
       return mrb.writeContent(data)
     ).then(=>
-      return dump?.writeContent(mrbc.readStdout())
+      return dump?.writeContent(
+        String.fromCharCode.apply(null, mrbc.readStdout())
+        {encoding: DUMP_ENCODING}
+      )
     ).then(=>
       return  # Last PromiseValue
     ) # return Promise.resolve().then()...


Restore routeLinks, fix non-a location bug, rename linkTest to skipRouting and allow it to take a selector, a function or false as a value

--- src/chaplin/views/layout.coffee
+++ src/chaplin/views/layout.coffee
@@ -35,13 +35,18 @@
       @title = options.title
       @settings = _(options).defaults
         titleTemplate: _.template("<%= subtitle %> \u2013 <%= title %>")
-        openExternalLinksInNewWindow: true
-        linkTest: false
+        openExternalToBlank: true
+        routeLinks: 'a'
+        skipRouting: '.noscript'
         scrollTo: [0, 0]

       @subscribeEvent 'beforeControllerDispose', @hideOldView
       @subscribeEvent 'startupController', @showNewView
       @subscribeEvent 'startupController', @adjustTitle
+
+      # Set the app link routing
+      if @settings.routeLinks
+        @initLinkRouting()

       # Set app wide event handlers
       @delegateEvents()
@@ -91,6 +96,12 @@
     # Automatic routing of internal links
     # -----------------------------------

+    initLinkRouting: ->
+      $(document).on('click', @settings.routeLinks, @openLink)
+
+    stopLinkRouting: ->
+      $(document).off('click', @settings.routeLinks, @openLink)
+
     # Handle all clicks on A elements and try to route them internally
     openLink: (event) ->
       return if utils.modifierKeyPressed(event)
@@ -102,12 +113,18 @@

       # Link test ---------------
-      hrefTest = if _.isFunction(@settings.linkTest) then @settings.linkTest(href) else @settings.linkTest
+      if typeof @settings.skipRouting is "function"
+        skipRouting = @settings.skipRouting(href)
+      else if typeof @settings.skipRouting is "string"
+        skipRouting = $el.is(@settings.skipRouting)
+      else
+        skipRouting = @settings.skipRouting
+
       return if href is null or
                 href is '' or
                 href.charAt(0) is '#' or
                 target == "_blank" or
-                hrefTest
+                skipRouting

       # External link -----------
@@ -115,19 +132,26 @@
       external = not ///#{currentHostname}$///i.test(el.hostname)

       if external
-        if @settings.openExternalLinksInNewWindow
+        if @settings.openExternalToBlank
           event.preventDefault() && window.open el.href

         return

       # Internal link -----------
-      path = el.pathname + el.search                  # path + query string
-      path = "/#{path}" if path.charAt(0) isnt '/'    # starting '/' for IE8
+      if el.nodeName is 'A'
+        path = el.pathname + el.search                  # path + query string
+        path = "/#{path}" if path.charAt(0) isnt '/'    # starting '/' for IE8
+        callback = (routed) -> event.preventDefault() if routed
+      else
+        path = href
+        callback = (routed) -> if routed then event.preventDefault() else location.href = path

       # Pass to the router, try to route internally
-      mediator.publish '!router:route', path, (routed) ->
-        event.preventDefault() if routed
+      mediator.publish '!router:route', path, callback
+
+      # mediator.publish '!router:route', path, (routed) ->
+      #   event.preventDefault() if routed


Show up to 10 links as preview in context menu

--- src/links.coffee
+++ src/links.coffee
@@ -19,6 +19,7 @@
 # THE SOFTWARE.

 LINK_WARNING_AMOUNT = 20
+LINK_PREVIEW_THRESHOLD = 10
 BLACKLIST = /^javascript/i
 FILTERS = {
 						all: ".*",
@@ -29,6 +30,8 @@

 @links = []
 @linksForFilter = {}
+@previewAmount = 0
+@previewLinks = {}

 onInstalledHandler = ->
 	chrome.contextMenus.create({contexts:["all"], id:"parent", title:chrome.i18n.getMessage("menu_main")})
@@ -41,11 +44,29 @@
 onMessageHandler = (request, sender, sendResponse) ->
 	if request.type == "verifySelection"
 		@links = request.links
-		for own key, value of FILTERS
-			@linksForFilter[key] = filterLinks(value)
-			updateContextMenu(key)
-	# todo: store current selection for optimization (in case it asks for the same extension again next time)
-	# todo: show up to three links directly (+ all)
+		updateContextMenus()
+
+updateContextMenus = ->
+	renderPreviewContextMenus()
+	for own key, value of FILTERS
+		@linksForFilter[key] = filterLinks(value)
+		updateContextMenu(key)
+
+renderPreviewContextMenus = ->
+	clearPreviewContextMenus()
+	@previewAmount = Math.min(@links.length, LINK_PREVIEW_THRESHOLD)
+	if @previewAmount > 0
+		chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:"preview_separator", type:"separator"})
+		for i in [0..@previewAmount]
+			@previewLinks["preview#{i}"] = @links[i]
+			chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:"preview#{i}", title:@links[i]})
+
+clearPreviewContextMenus = ->
+	chrome.contextMenus.remove("preview_separator")
+	for i in [0..@previewAmount]
+		chrome.contextMenus.remove("preview#{i}")
+	@previewAmount = 0
+	@previewLinks = {}

 filterLinks = (filter) ->
 	link for link in @links.reverse() when link.match(new RegExp(filter, "i")) and not link.match(BLACKLIST)
@@ -56,10 +77,17 @@
 	chrome.contextMenus.update(id, {title:title, enabled:links.length > 0})

 onClickHandler = (info, tab) ->
-	links = @linksForFilter[info.menuItemId]
-	if links.length <= LINK_WARNING_AMOUNT or confirm(chrome.i18n.getMessage("selection_alert_tooManyLinks", [links.length]))
-		for link in links
-			chrome.tabs.create({url:link,index:tab.index+1})
+	previewLink = @previewLinks[info.menuItemId]
+	if previewLink
+		openLink(previewLink, tab)
+	else
+		links = @linksForFilter[info.menuItemId]
+		if links.length <= LINK_WARNING_AMOUNT or confirm(chrome.i18n.getMessage("selection_alert_tooManyLinks", [links.length]))
+			for link in links
+				openLink(link, tab)
+
+openLink = (url, currentTab) ->
+	chrome.tabs.create({url:url,index:currentTab.index+1})

 chrome.runtime.onInstalled.addListener(onInstalledHandler)
 chrome.extension.onMessage.addListener(onMessageHandler)


emulator view

emulator view

--- lib/emulator/emulator-view.coffee
+++ lib/emulator/emulator-view.coffee
@@ -25,7 +25,7 @@
             @button 'Debug', class: 'btn btn-primary inline-block-tight', click: 'onClickDebug'

   initialize: (serializeState) ->
-    #换另外一边时用到 添加一个观察者
+    #observe tree-view side toggle event
     @subscribe atom.config.observe 'tree-view.showOnRightSide', callNow: false, (newValue) =>
       @detach()
       @attach()
@@ -62,7 +62,8 @@
     # @webview[0].reload()

   onClickDebug: ->
-    @webview[0].openDevTools() unless @webview[0].isDevToolsOpened()
+    webview = document.querySelector('webview')
+    webview.openDevTools() unless webview.isDevToolsOpened()

   onClickChrome: ->
     {exec} = require 'child_process'


Exo.Spine.Controller: Added null & undefined checks/exceptions to addChild

--- src/core/node.coffee
+++ src/core/node.coffee
@@ -163,9 +163,17 @@
       return arr

     @addChild = (node) ->
+
+      throw new Error(
+        "ExoReferenceError -> addChild: #{node} is not a valid Exo.Node"
+      ) if node == null or typeof node == 'undefined'
+
+      throw new Error(
+        "ExoReferenceError -> An Exo.Node instance can't pass itself to addChild"
+      ) if @nodeId() == node.nodeId()
+
       node.setParent(@)
       __childMap[node.nodeId()] = node
-      #console.log "addChild:", node.id, __childMap[node.id]

     @removeChild = (node) ->
       delete __childMap[node.nodeId()]


KDView::hasClass: needs to check existence of cssClass otherwise .contains throws DOM error

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -314,7 +314,10 @@

     return this

-  hasClass:(cssClass)-> @getElement().classList.contains cssClass
+  hasClass:(cssClass)->
+
+    return no  unless cssClass
+    @getElement().classList.contains cssClass

   getBounds: ->


Fixed completion handler code to get the callback on init

--- lib/service/dataLink.coffee
+++ lib/service/dataLink.coffee
@@ -82,13 +82,11 @@

     return registeredCollections[collectionName]

-  initCompletionHandler = (task) ->
+  initCompletionHandler = (task, callback) ->
     unless task.originalRequest?
       task.originalRequest = task.request

     environmentId = task.appMetadata._id
-
-    return () ->

     convertToError = (body) ->

@@ -100,7 +98,7 @@

       return errorResult

-    completionHandler = (entity, callback) ->
+    completionHandler = (entity) ->
       entityParser = entityHelper environmentId

       responseCallback = callback
@@ -199,7 +197,11 @@
       collectionToProcess = collection task.collectionName
       dataOp = ''

-      completionHandler = initCompletionHandler task
+      completionHandler = initCompletionHandler task, (err, result) ->
+        if err?
+          return callback(convertToError err)
+
+        callback null, result

       if task.method is 'POST'
         dataOp = 'onInsert'
@@ -245,11 +247,7 @@
       domainBoundOperationHandler = taskDomain.bind operationHandler

-      domainBoundOperationHandler task.request, completionHandler, (err, result) ->
-        if err?
-          return callback(convertToError err)
-
-        callback null, result
+      domainBoundOperationHandler task.request, completionHandler

   obj =
     collection: collection


Use skinny arrow for subscribeToBuffer

Use skinny arrow for subscribeToBuffer

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -27,10 +27,15 @@
     @views = []
     @constructor.createTask()

-    @subscribe @editorView, 'editor:path-changed', @subscribeToBuffer
-    @subscribe @editorView, 'editor:grammar-changed', @subscribeToBuffer
-    @subscribe atom.config.observe 'editor.fontSize', @subscribeToBuffer
-    @subscribe atom.config.observe 'spell-check.grammars', @subscribeToBuffer
+    @subscribe @editorView, 'editor:path-changed', =>
+      @subscribeToBuffer()
+    @subscribe @editorView, 'editor:grammar-changed', =>
+      @subscribeToBuffer()
+
+    @subscribe atom.config.observe 'editor.fontSize', =>
+      @subscribeToBuffer()
+    @subscribe atom.config.observe 'spell-check.grammars', =>
+      @subscribeToBuffer()

     @subscribeToBuffer()

@@ -45,7 +50,7 @@
       @unsubscribe(@buffer)
       @buffer = null

-  subscribeToBuffer: =>
+  subscribeToBuffer: ->
     @unsubscribeFromBuffer()

     if @spellCheckCurrentGrammar()


Added examples to `#props()` method comments

--- src/dahelpers.coffee
+++ src/dahelpers.coffee
@@ -703,6 +703,13 @@
     #
     # Get a value of a property tree `p` on the object `o` or undefined if any
     # of the segments is undefined without throwing an exception.
+    #
+    # Example:
+    #
+    #     var obj = {foo: {bar: {baz: 1}}};
+    #     dahelpers.props(obj, 'foo.bar.baz'); // returns 1
+    #     dahelpers.props(obj, 'foo.foo.baz'); // returns undefined
+    #
     props: (o, p) ->
       return undefined if not o?
       return o if not p?


prototype of Nothing is null

prototype of Nothing is null

--- src/monad.coffee
+++ src/monad.coffee
@@ -19,7 +19,7 @@
   , (TMonad value))

 # Not a value.
-Nothing = Trait.create Object.prototype, (Trait.override (Trait {
+Nothing = Trait.create null, (Trait.override (Trait {
   inspect: () -> "<Nothing>"
   unit : () -> Nothing
   bind : (fn) -> Nothing })


Don't bother checking for error properties on error object

--- timeAsyncMethod.coffee
+++ timeAsyncMethod.coffee
@@ -22,7 +22,7 @@
 		realMethod.call this, firstArgs..., (callbackArgs...) ->
 			elapsedTime = timer.done()
 			possibleError = callbackArgs[0]
-			if possibleError? && possibleError.message? && possibleError.stack?
+			if possibleError?
 				metrics.inc "#{key}.failure"
 			else
 				metrics.inc "#{key}.success"


feature: gui: strighten view

feature: gui: strighten view

--- application.coffee
+++ application.coffee
@@ -37,6 +37,9 @@

   getViewCenter: ->@center
   getViewOffsetMatrix: ->@tfm
+  setViewOffsetMatrix: (m) ->
+    @tfm = m
+    @renderGrid @tfm

   rebuildAt: (newCenter) ->
     @center = newCenter
@@ -539,6 +542,9 @@
 doNavigateHome = ->
   observer.navigateTo unity

+doStraightenView = ->
+  observer.setViewOffsetMatrix M.eye()
+
 class Debouncer
   constructor: (@timeout, @callback) ->
     @timer = null
@@ -587,7 +593,7 @@
   '77A': doClearMemory
   #H
   '72': doNavigateHome
-
+  '72S': doStraightenView

 document.addEventListener "keydown", (e)->
   keyCode = "" + e.keyCode


ignore json messages from out-dated clients.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -1938,6 +1938,8 @@
     ################################################################

     handle_json_message_from_client: (data) =>
+        if @_ignore_client
+            return
         try
             mesg = from_json(data)
         catch error
@@ -1960,6 +1962,9 @@
             handler(mesg)
         else
             @push_to_client(message.error(error:"Hub does not know how to handle a '#{mesg.event}' event.", id:mesg.id))
+            if mesg.event == 'get_all_activity'
+                winston.debug("ignoring all further messages from old client=#{@id}")
+                @_ignore_client = true

     ######################################################
     # Plug into an existing sage session


added width and height for avatars in group switcher popup to prevent big avatar images to blow it up

--- client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
@@ -112,6 +112,8 @@
       cssClass   : 'avatar-image'
       attributes :
         src      : avatar or "http://lorempixel.com/20/20?#{@utils.getRandomNumber()}"
+        width    : 20
+        height   : 20

     @switchLink = new CustomLinkView
       title       : title


home/teammates: update member component for ui

--- client/home/lib/myteam/components/hometeamteammates/member.coffee
+++ client/home/lib/myteam/components/hometeamteammates/member.coffee
@@ -16,6 +16,7 @@

   onClickMemberRole: (role, event) ->
+
     kd.utils.stopDOMEvent event
     @setState { isMenuOpen: yes }

@@ -72,49 +73,44 @@

     { nickname, email, role, firstName, lastName, fullName } = @getData()

-

+

+      <AvatarView member={@props.member} />

-        <AvatarView member={@props.member} />
-
{fullName}

+
{fullName}

+

+          <Email email={email} />
+          <NickName nickname={nickname}/>
+

-      <Email email={email} />
-      <NickName nickname={nickname}/>
-
-        <MemberRole role={role} />
-
-

-      <ButtonWithMenu menuClassName='menu-class' items={@getMenuItems role} isMenuOpen={@state.isMenuOpen} />
+

+        <MemberRole role={role}  />
+        <ButtonWithMenu menuClassName='menu-class' items={@getMenuItems role} isMenuOpen={@state.isMenuOpen} />
+

 NickName = ({ nickname }) ->

   if nickname.length
-
 | @{nickname}

-  else
-

+  then @{nickname}
+  else

 Email = ({ email }) ->

-
{email}

+  {email}

 AvatarView = ({ member }) ->

   unless member.get 'status'
-
+

       <ProfilePicture account={member.toJS()} height={40} width={40} />
-
-
+

   else
-
-
-
+

+

 MemberRole = ({ role }) ->
-  className = 'role'
+
   role = capitalizeFirstLetter role
-

-    {role}
-
-

+
{role}


myteam: hot fix

myteam: hot fix

--- client/home/lib/myteam/components/hometeamteammates/member.coffee
+++ client/home/lib/myteam/components/hometeamteammates/member.coffee
@@ -99,11 +99,11 @@

   unless canEdit

-      <MemberRole role={role} userRole={userRole}  />
+      <MemberRole role={role} canEdit={canEdit}  />

   else

-      <MemberRole role={role} userRole={userRole} />
+      <MemberRole role={role} canEdit={canEdit} />
       <ButtonWithMenu menuClassName='menu-class' items={items} isMenuOpen={isMenuOpen} />

@@ -129,8 +129,8 @@

-MemberRole = ({ role, userRole }) ->
+MemberRole = ({ role, canEdit }) ->

   role = capitalizeFirstLetter role
-  className = if userRole is 'member' then '' else 'role'
+  className = unless canEdit then '' else 'role'

{role}


Typo fix

Typo fix

--- components/SendEdges.coffee
+++ components/SendEdges.coffee
@@ -28,5 +28,5 @@
       do callback
       return
     c.params.runtime.sendNetwork 'edges', data
-    out.send edges
+    out.send data
     do callback


Parse values as they are entered, not just when show list is clicked

--- src/coffee/cilantro/ui/values.coffee
+++ src/coffee/cilantro/ui/values.coffee
@@ -86,6 +86,13 @@
             'click .toggle': 'toggle'
             'click .clear': 'clear'

+        # The delay in milliseconds to wait before responding to changes in
+        # the textarea.
+        inputDelay: 500
+
+        initialize: ->
+            @_parseText = _.debounce(@parseText, @inputDelay)
+
         clear: (event) ->
             event?.preventDefault()
             @collection.reset()
@@ -99,7 +106,6 @@
                 @ui.textarea.show()
                 @ui.toggle.html(' Show List')
             else
-                @parseText()
                 @ui.list.show()
                 @ui.textarea.hide()
                 @ui.toggle.html(' Edit List')
@@ -148,5 +154,9 @@

             @collection.set(models)

+        onRender: ->
+            @ui.textarea.on 'input propertychange', =>
+                @_parseText()
+

     { ValueList }


fix fix_relative_url for public deploy

fix fix_relative_url for public deploy

--- tools/exportapp.coffee
+++ tools/exportapp.coffee
@@ -148,9 +148,9 @@
 readCacheTextFile = utils.readCacheTextFile

 fix_relative_url = (url,path)->
-  if url.indexOf(publicdburl)==0
-    console.log "Rewrite external URL #{url}"
-    url = couchdburl+url.substring(publicdburl.length)
+  if path.indexOf(publicdburl)==0
+    console.log "Rewrite external URL #{path}"
+    path = couchurl+path.substring(publicdburl.length)
   resolve_url url,path

 files = {}


Log score

Log score

--- src/Player.coffee
+++ src/Player.coffee
@@ -1,12 +1,13 @@
 class Player
   constructor: (@game)->
-  	@points = 0
-  	@words = []
+    @points = 0
+    @words = []

   addWord: (word) ->
-  	@words.push word
+    @words.push word

   addPoints: (num) ->
-  	@points += num
+    @points += num
+    console.log "POINTS: " + @points

 module.exports = Player


This file should never have been added to source control in the first place.

--- test.coffee
+++ test.coffee
@@ -1,9 +0,0 @@
-dir = '../.././some/path/bingo'
-
-parts = dir.split '/'
-
-non_dots = parts.filter (p) -> p.substring(0, 1) != '.'
-
-console.log non_dots
-
-


ACLs for vm.*().

ACLs for vm.*().

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -16,8 +16,9 @@

 #=====================================================================

+# TODO: Implement ACLs
 # FIXME: Make the method as atomic as possible.
-exports.create = $coroutine ({
+create = $coroutine ({
   installation
   name_label
   template
@@ -169,8 +170,10 @@

   # The VM should be properly created.
   return VM.uuid
-exports.create.permission = 'admin'
-exports.create.params = {
+
+create.permission = 'admin'
+
+create.params = {
   installation: {
     type: 'object'
     optional: true
@@ -224,7 +227,15 @@
   }
 }

-exports.delete = $coroutine ({vm, delete_disks: deleteDisks}) ->
+create.resolve = {
+  template: ['template', 'VM-template'],
+}
+
+exports.create = create
+
+#---------------------------------------------------------------------
+
+delete_ = $coroutine ({vm, delete_disks: deleteDisks}) ->
   if $isVMRunning vm
     @throw 'INVALID_PARAMS', 'The VM can only be deleted when halted'

@@ -246,7 +257,8 @@
   $wait xapi.call 'VM.destroy', vm.ref

   return true
-exports.delete.params = {
+
+delete_.params = {
   id: { type: 'string' }

   delete_disks: {
@@ -254,11 +266,16 @@
     type: 'boolean'
   }
 }
-exports.delete.resolve = {
+
+delete_.resolve = {
   vm: ['id', ['VM', 'VM-snapshot']]
 }

-exports.ejectCd = $coroutine ({vm}) ->
+exports.delete = delete_
+
+#---------------------------------------------------------------------
+
+ejectCd = $coroutine ({vm}) ->
   xapi = @getXAPI vm

   # Finds the CD drive.
@@ -274,14 +291,20 @@
     $wait xapi.call 'VBD.destroy', cdDriveRef

   return true
-exports.ejectCd.params = {
-  id: { type: 'string' }
-}
-exports.ejectCd.resolve = {
+
+ejectCd.params = {
+  id: { type: 'string' }
+}
+
+ejectCd.resolve = {
   vm: ['id', 'VM']
 }

-exports.insertCd = $coroutine ({vm, vdi, force}) ->
+exports.ejectCd = ejectCd
+
+#---------------------------------------------------------------------
+
+insertCd = $coroutine ({vm, vdi, force}) ->
   xapi = @getXAPI vm

   # Finds the CD drive.
@@ -317,17 +340,23 @@
   $wait xapi.call 'VBD.insert', cdDriveRef, vdi.ref

   return true
-exports.insertCd.params = {
+
+insertCd.params = {
   id: { type: 'string' }
   cd_id: { type: 'string' }
   force: { type: 'boolean' }
 }
-exports.insertCd.resolve = {
+
+insertCd.resolve = {
   vm: ['id', 'VM'],
   vdi: ['cd_id', 'VDI'],
 }

-exports.migrate = $coroutine ({vm, host}) ->
+exports.insertCd = insertCd
+
+#---------------------------------------------------------------------
+
+migrate = $coroutine ({vm, host}) ->
   unless $isVMRunning vm
     @throw 'INVALID_PARAMS', 'The VM can only be migrated when running'

@@ -336,19 +365,25 @@
   $wait xapi.call 'VM.pool_migrate', vm.ref, host.ref, {'force': 'true'}

   return true
-exports.migrate.params = {
+
+migrate.params = {
   # Identifier of the VM to migrate.
   id: { type: 'string' }

   # Identifier of the host to migrate to.
   host_id: { type: 'string' }
 }
-exports.migrate.resolve = {
+
+migrate.resolve = {
   vm: ['id', 'VM']
   host: ['host_id', 'host']
 }

-exports.migrate_pool = $coroutine ({
+exports.migrate = migrate
+
+#---------------------------------------------------------------------
+
+migratePool = $coroutine ({
   id
   target_host_id
   target_sr_id
@@ -418,8 +453,8 @@
   )

   return true
-exports.migrate_pool.permission = 'admin'
-exports.migrate_pool.params = {
+
+migratePool.params = {

   # Identifier of the VM to migrate.
   id: { type: 'string' }
@@ -437,13 +472,21 @@
   migration_network_id: { type: 'string', optional: true }
 }

+migratePool.resolve = {
+  vm: ['id', 'VM'],
+  host: ['target_host_id', 'host'],
+  sr: ['target_sr_id', 'SR'],
+  network: ['target_network_id', 'network'],
+  migrationNetwork: ['migration_network_id', 'network'],
+}
+
+# TODO: camel case.
+exports.migrate_pool = migratePool
+
+#---------------------------------------------------------------------
+
 # FIXME: human readable strings should be handled.
-exports.set = $coroutine (params) ->
-  try
-    VM = @getObject params.id, ['VM', 'VM-snapshot']
-  catch
-    @throw 'NO_SUCH_OBJECT'
-
+set = $coroutine (params) ->
   xapi = @getXAPI VM

   {ref} = VM
@@ -509,8 +552,8 @@
       $wait xapi.call "VM.set_#{field}", ref, "#{params[param]}"

   return true
-exports.set.permission = 'admin'
-exports.set.params = {
+
+set.params = {
   # Identifier of the VM to update.
   id: { type: 'string' }

@@ -533,7 +576,15 @@
   memory: { type: 'integer', optional: true }
 }

-exports.restart = $coroutine ({vm, force}) ->
+set.resolve = {
+  VM: ['id', 'VM'],
+}
+
+exports.set = set
+
+#---------------------------------------------------------------------
+
+restart = $coroutine ({vm, force}) ->
   xapi = @getXAPI(vm)

   try
@@ -547,15 +598,21 @@
     $wait xapi.call 'VM.hard_reboot', vm.ref

   return true
-exports.restart.params = {
+
+restart.params = {
   id: { type: 'string' }
   force: { type: 'boolean' }
 }
-exports.restart.resolve = {
+
+restart.resolve = {
   vm: ['id', 'VM']
 }

-exports.clone = $coroutine ({vm, name, full_copy}) ->
+exports.restart = restart
+
+#---------------------------------------------------------------------
+
+clone = $coroutine ({vm, name, full_copy}) ->
   xapi = @getXAPI vm
   if full_copy
     $wait xapi.call 'VM.copy', vm.ref, name, ''
@@ -563,38 +620,56 @@
     $wait xapi.call 'VM.clone', vm.ref, name

   return true
-exports.clone.params = {
+
+clone.params = {
   id: { type: 'string' }
   name: { type: 'string' }
   full_copy: { type: 'boolean' }
 }
-exports.clone.resolve = {
+
+clone.resolve = {
   vm: ['id', 'VM']
 }

+exports.clone = clone
+
+#---------------------------------------------------------------------
+
 # TODO: rename convertToTemplate()
-exports.convert = $coroutine ({vm}) ->
+convert = $coroutine ({vm}) ->
   $wait @getXAPI(vm).call 'VM.set_is_a_template', vm.ref, true

   return true
-exports.convert.params = {
-  id: { type: 'string' }
-}
-exports.convert.resolve = {
+
+convert.params = {
+  id: { type: 'string' }
+}
+
+convert.resolve = {
   vm: ['id', 'VM']
 }

-exports.snapshot = $coroutine ({vm, name}) ->
+exports.convert = convert
+
+#---------------------------------------------------------------------
+
+snapshot = $coroutine ({vm, name}) ->
   return $wait @getXAPI(vm).call 'VM.snapshot', vm.ref, name
-exports.snapshot.params = {
+
+snapshot.params = {
   id: { type: 'string' }
   name: { type: 'string' }
 }
-exports.snapshot.resolve = {
+
+snapshot.resolve = {
   vm: ['id', 'VM']
 }

-exports.start = $coroutine ({vm}) ->
+exports.snapshot = snapshot
+
+#---------------------------------------------------------------------
+
+start = $coroutine ({vm}) ->
   $wait @getXAPI(vm).call(
     'VM.start', vm.ref
     false # Start paused?
@@ -602,19 +677,24 @@
   )

   return true
-exports.start.params = {
-  id: { type: 'string' }
-}
-exports.start.resolve = {
+
+start.params = {
+  id: { type: 'string' }
+}
+
+start.resolve = {
   vm: ['id', 'VM']
 }

+exports.start = start
+
+#---------------------------------------------------------------------

 # TODO: implements timeout.
 # - if !force → clean shutdown
 # - if force is true → hard shutdown
 # - if force is integer → clean shutdown and after force seconds, hard shutdown.
-exports.stop = $coroutine ({vm, force}) ->
+stop = $coroutine ({vm, force}) ->
   xapi = @getXAPI vm

   # Hard shutdown
@@ -633,26 +713,38 @@
       throw error

   return true
-exports.stop.params = {
+
+stop.params = {
   id: { type: 'string' }
   force: { type: 'boolean', optional: true }
 }
-exports.stop.resolve = {
+
+stop.resolve = {
   vm: ['id', 'VM']
 }

-exports.suspend = $coroutine ({vm}) ->
+exports.stop = stop
+
+#---------------------------------------------------------------------
+
+suspend = $coroutine ({vm}) ->
   $wait @getXAPI(vm).call 'VM.suspend', vm.ref

   return true
-exports.suspend.params = {
-  id: { type: 'string' }
-}
-exports.suspend.resolve = {
+
+suspend.params = {
+  id: { type: 'string' }
+}
+
+suspend.resolve = {
   vm: ['id', 'VM']
 }

-exports.resume = $coroutine ({vm, force}) ->
+exports.suspend = suspend
+
+#---------------------------------------------------------------------
+
+resume = $coroutine ({vm, force}) ->
   # FIXME: WTF this is?
   if not force
     force = true
@@ -660,34 +752,41 @@
   $wait @getXAPI(vm).call 'VM.resume', vm.ref, false, force

   return true
-exports.resume.params = {
+
+resume.params = {
   id: { type: 'string' }
   force: { type: 'boolean', optional: true }
 }
-exports.resume.resolve = {
+
+resume.resolve = {
   vm: ['id', 'VM']
 }

+exports.resume = resume
+
+#---------------------------------------------------------------------
+
 # revert a snapshot to its parent VM
-exports.revert = $coroutine ({snapshot}) ->
+revert = $coroutine ({snapshot}) ->
   # Attempts a revert from this snapshot to its parent VM
   $wait @getXAPI(snapshot).call 'VM.revert', snapshot.ref

   return true
-exports.revert.permission = 'admin'
-exports.revert.params = {
-  id: { type: 'string' }
-}
-exports.revert.resolve = {
+
+revert.params = {
+  id: { type: 'string' }
+}
+
+revert.resolve = {
   snapshot: ['id', 'VM-snapshot']
 }

-exports.export = $coroutine ({vm, compress}) ->
+exports.revert = revert
+
+#---------------------------------------------------------------------
+
+export_ = $coroutine ({vm, compress}) ->
   compress ?= true
-  try
-    VM = @getObject vm, ['VM', 'VM-snapshot']
-  catch
-    @throw 'NO_SUCH_OBJECT'

   xapi = @getXAPI VM

@@ -742,15 +841,23 @@
   return {
     $getFrom: url
   }
-exports.export.permission = 'admin'
-exports.export.params = {
+
+export_.params = {
   vm: { type: 'string' }
   compress: { type: 'boolean', optional: true }
 }

+export_.resolve = {
+  vm: ['vm', ['VM', 'VM-snapshot']],
+}
+
+exports.export = export_;
+
+#---------------------------------------------------------------------
+
 # FIXME
 # TODO: "sr_id" can be passed in URL to target a specific SR
-exports.import = $coroutine ({host}) ->
+import_ = $coroutine ({host}) ->

   {sessionId} = @getXAPI(host)

@@ -769,24 +876,23 @@
     $sendTo: url
   }

-exports.import.params = {
+import_.params = {
   host: { type: 'string' }
 }
-exports.import.resolve = {
+
+import_.resolve = {
   host: ['host', 'host']
 }
+
+exports.import = import_
+
+#---------------------------------------------------------------------

 # FIXME: position should be optional and default to last.
 #
 # FIXME: if position is used, all other disks after this position
 # should be shifted.
-exports.attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) ->
-  try
-    VM = @getObject vm, 'VM'
-    VDI = @getObject vdi, 'VDI'
-  catch
-    @throw 'NO_SUCH_OBJECT'
-
+attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) ->
   xapi = @getXAPI VM

   VBD_ref = $wait xapi.call 'VBD.create', {
@@ -806,8 +912,7 @@

   return true

-exports.attachDisk.permission = 'admin'
-exports.attachDisk.params = {
+attachDisk.params = {
   bootable: {
     type: 'boolean'
     optional: true
@@ -817,6 +922,15 @@
   vdi: { type: 'string' }
   vm: { type: 'string' }
 }
+
+attachDisk.resolve = {
+  vm: ['vm', 'VM'],
+  vdi: ['vdi', 'VDI'],
+}
+
+exports.attachDisk = attachDisk
+
+#---------------------------------------------------------------------

 # FIXME: position should be optional and default to last.
 #
@@ -825,27 +939,21 @@
 #
 # FIXME: disk should be created using disk.create() and then attached
 # via vm.attachDisk().
-exports.addDisk = $coroutine ({vm, name, size, sr, position, bootable}) ->
-  try
-    VM = @getObject vm, 'VM'
-    SR = @getObject sr, 'SR'
-  catch
-    @throw 'NO_SUCH_OBJECT'
-
-  xapi = @getXAPI VM
-  VDI_ref = $wait xapi.call 'VDI.create', {
+addDisk = $coroutine ({vm, name, size, sr, position, bootable}) ->
+  xapi = @getXAPI vm
+  vdiRef = $wait xapi.call 'VDI.create', {
     name_label: name
     virtual_size: size
     type: 'user'
-    SR: SR.ref
+    SR: sr.ref
     sharable: false
     read_only: false
     other_config: {}
   }

-  VBD_ref = $wait xapi.call 'VBD.create', {
-    VM: VM.ref
-    VDI: VDI_ref
+  vbdRef = $wait xapi.call 'VBD.create', {
+    VM: vm.ref
+    VDI: vdiRef
     mode: 'RW'
     type: 'Disk'
     userdevice: position
@@ -856,12 +964,11 @@
     qos_algorithm_params: {}
   }

-  $wait xapi.call 'VBD.plug', VBD_ref
-
-  return true
-
-exports.addDisk.permission = 'admin'
-exports.addDisk.params = {
+  $wait xapi.call 'VBD.plug', vbdRef
+
+  return true
+
+addDisk.params = {
   bootable: {
     type: 'boolean'
     optional: true
@@ -872,3 +979,8 @@
   size: { type: 'string' }
   sr: { type: 'string' }
 }
+
+addDisk.resolve = {
+  vm: ['vm', 'VM'],
+  sr: ['sr', 'SR'],
+}


Move around initializers again.

Move around initializers again.

--- src/batman.coffee
+++ src/batman.coffee
@@ -33,9 +33,6 @@
     for key, value of mixin
       continue if key in ['initialize', 'deinitialize', 'prototype']
       if hasSet then set.call(to, key, value) else to[key] = value
-
-    if $typeOf(mixin.initialize) is 'Function'
-      mixin.initialize.call to

   to

@@ -130,6 +127,11 @@
 # order to make that object bindable. It is applied by default to every
 # instance of Batman.Object and subclasses.
 Batman.Observable = {
+  initialize: ->
+    Batman._initializeObject @
+    @_batman.observers ||= {}
+    @_batman.preventCounts ||= {}
+
   keypath: (string) ->
     new Batman.Keypath(@, string)

@@ -152,8 +154,7 @@
   # Pass a key and a callback. Whenever the value for that key changes, your
   # callback will be called in the context of the original object.
   observe: (wholeKeypathString, fireImmediately, callback) ->
-    Batman._initializeObject @
-    @_batman.observers ||= {}
+    Batman.Observable.initialize.call @

     if not callback
       callback = fireImmediately
@@ -201,7 +202,7 @@
   # You normally shouldn't call this directly. It will be invoked by `set`
   # to inform all observers for `key` that `value` has changed.
   fire: (key, value, oldValue) ->
-    # Batman._initializeObject @ # allowed will call this already
+    # Batman.Observable.initialize.call @ # allowed will call this already
     return if not @allowed key

     if typeof value is 'undefined'
@@ -217,8 +218,7 @@
   # its removed. If no callback but a key is passed in, all the observers on
   # that key are removed. If no key is passed in, all observers are removed.
   forget: (key, callback) ->
-    Batman._initializeObject @
-    @_batman.observers ||= {}
+    Batman.Observable.initialize.call @

     if key
       if callback
@@ -242,9 +242,9 @@
   # nest prevent counts, so three calls to prevent means you need to
   # make three calls to allow before you can fire observers again.
   prevent: (key) ->
-    Batman._initializeObject @
-
-    counts = @_batman.preventCounts ||= {}
+    Batman.Observable.initialize.call @
+
+    counts = @_batman.preventCounts
     counts[key] ||= 0
     counts[key]++
     @
@@ -252,17 +252,16 @@
   # Unblocks a property for firing observers. Every call to prevent
   # must have a matching call to allow.
   allow: (key) ->
-    Batman._initializeObject @
-
-    counts = @_batman.preventCounts ||= {}
+    Batman.Observable.initialize.call @
+
+    counts = @_batman.preventCounts
     counts[key]-- if counts[key] > 0
     @

   # Returns a boolean whether or not the property is currently allowed
   # to fire its observers.
   allowed: (key) ->
-    Batman._initializeObject @
-
+    Batman.Observable.initialize.call @
     !(@_batman.preventCounts?[key] > 0)
 }


Use async error handling for flushing tasks.

--- src/broker.coffee
+++ src/broker.coffee
@@ -48,12 +48,15 @@
   _submitTasks: ->
     @store.keys (error, ids) =>
       throw error if error?
-      async.forEachSeries ids, @_submitTask, ->
+      async.forEachSeries ids, @_submitTask, (error) ->
+        throw error if error?
         console.log "Pending tasks flushed"

   _submitTask: (id, callback) =>
     @store.read id, (error, task) =>
-      throw error if error?
-      payload = JSON.stringify id: task.id, request: task.request, data: task.data
-      @dealer.send [new Buffer(""), payload]
-      callback()
+      if error?
+        callback error
+      else
+        payload = JSON.stringify id: task.id, request: task.request, data: task.data
+        @dealer.send [new Buffer(""), payload]
+        callback null


Make Railgun rewrite HTML to point to optimized scripts.

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -101,7 +101,7 @@
                 return @files.splice i, 1

     find: (path) ->
-        _.find @files, (file) -> file.originalPath is path
+        _.find @files, (file) -> path in [file.path, file.originalPath]

     # (1) create an unoptimized bundle
     create: (callback) ->
@@ -202,9 +202,8 @@

     # (5) try to merge and order links from all the different files
     # in a logical way
-    orderLinks: (self..., callback) ->
-        # TODO: properly implement this (or drop support for smart ordering),
-        # and clean it up a little
+    aggregateLinks: (self..., callback) ->
+        # TODO: do we want to support smart (re)ordering or not?
         links = _(@files)
             .chain()
             .pluck('links')
@@ -222,7 +221,6 @@

     # (6) concatenate and optimize scripts and stylesheets
     optimize: (self..., callback) ->
-
         scripts = @links.scripts
             .map (ref) =>
                 # TODO: everything should exist and have content, so
@@ -235,23 +233,35 @@
             content: utils.code.compress scripts
             origin: @links.scripts
         @remove ('/' + script) for script in @links.scripts
+
         callback null, this

     # (7) rewrite HTML files to point to our optimized scripts and styles
     rewriteHtml: (self..., callback) ->
-        ###
-        # TODO: we can't just remove all scripts -- absolute references to
-        # external scripts and styles should be kept intact
-        $('script').remove()
-        script = window.document.createElement('script')
-        script.type = 'text/javascript'
-        script.src = 'application.min.js'
-        window.document.getElementsByTagName('head')[0].appendChild script
-        ###
-        callback null, this
+        entrypoint = @find @relativeEntrypoint
+
+        utils.html.parse entrypoint.content, (errors, window) =>
+            $ = window.$
+
+            $('script').each ->
+                el = $ @
+                # we can't remove all scripts -- absolute references to
+                # external scripts and styles should be kept intact
+                if el.attr('src').indexOf('/') isnt 0
+                    el.remove()
+                if el.hasClass 'jsdom'
+                    el.remove()
+
+            script = window.document.createElement('script')
+            script.type = 'text/javascript'
+            script.src = 'application.min.js'
+            window.document.getElementsByTagName('head')[0].appendChild script
+
+            entrypoint.content = window.document.outerHTML
+
+            callback null, this

     report: ->
-

 exports.bundle = (entrypoint, callback) ->
     bundle = new Bundle entrypoint
@@ -260,7 +270,7 @@
         bundle.preprocess
         bundle.setProductionEnvironment
         bundle.findLinks
-        bundle.orderLinks
+        bundle.aggregateLinks
         bundle.optimize
         bundle.rewriteHtml
         ]


apps logic wip

apps logic wip

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -18,9 +18,9 @@
       tagName : "span"
       partial : "fetching apps..."

-    kac = @getSingleton("kodingAppsController")
-
-    kac.fetchApps (apps)=>
+    kodingAppsController = @getSingleton("kodingAppsController")
+
+    kodingAppsController.fetchApps (apps)=>

       temp.destroy()
       for app, manifest of apps
@@ -29,7 +29,7 @@
             tagName : "p"
             partial : "#{manifest.name} v#{manifest.version}"
             click   : =>
-              kac.getApp manifest.name, (appScript)=>
+              kodingAppsController.getApp manifest.name, (appScript)=>
                 mainView = @getSingleton('mainView')
                 mainView.mainTabView.showPaneByView
                   name         : manifest.name
@@ -37,10 +37,12 @@
                   type         : "application"
                 , (appView = new KDView)

-                appInstance = eval appScript
-                appInstance()
+                eval appScript
+
+                return appView
+
                 # appInstance = Function(appScript)
-                # do (appView)=> appInstance.call @, appView
+                # do (appView)=> appInstance.call null, appView

 # appView.addSubView a = new KDView
@@ -48,7 +50,9 @@
 # appView.$().css "background-color", "pink"
 # a.$().css backgroundColor : "red", color : "white"
 # return a
-
+
+# do (appView)->
+#   appView.addSubview new AceView {}, FSHelper.createFileFromPath "localfile:/Untitled.txt"


login: removed cursing

login: removed cursing

--- client/landing/site.hackathon/coffee/home/registerform.coffee
+++ client/landing/site.hackathon/coffee/home/registerform.coffee
@@ -51,9 +51,7 @@

     @email.input.on    'focus', @bound 'handleFocus'
     @password.input.on 'focus', @bound 'handleFocus'
-    @email.input.on    'blur',  =>
-      log 'why not you yavsak'
-      @fetchGravatarInfo @email.input.getValue()
+    @email.input.on    'blur',  => @fetchGravatarInfo @email.input.getValue()

     KD.singletons.router.on 'RouteInfoHandled', =>
       @email.icon.unsetTooltip()


Remove `autoCommit` options, as it’s not used anywhere.

--- src/cartjs.coffee
+++ src/cartjs.coffee
@@ -4,7 +4,6 @@
 CartJS =
   # Default settings, publicly accessible on `module.settings`.
   settings:
-    autoCommit: true
     dataAPI: true
     requestBodyClass: null
     rivetsModels: {}


Minor readability fixes

Minor readability fixes

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -56,14 +56,16 @@
   DiscussionModalOnFrontend.openDiscussion = (fact) ->
     Backbone.history.navigate fact.get('url'), false

-    if !discussionModalContainer
+    unless discussionModalContainer
       discussionModalContainer = new DiscussionModalContainer
       FactlinkApp.discussionModalRegion.show discussionModalContainer
+
     discussionModalContainer.mainRegion.show new DiscussionView model: fact

   DiscussionModalOnFrontend.closeDiscussion = ->
     discussionModalContainer.fadeOut ->
       FactlinkApp.discussionModalRegion.close()
+
     discussionModalContainer = null

     FactlinkApp.ModalWindowContainer.close()


Default to `as_user=true`

Default to `as_user=true`

--- src/client.coffee
+++ src/client.coffee
@@ -74,11 +74,11 @@
     message = @format.outgoing(message)

     if typeof message isnt 'string'
-      @web.chat.postMessage(envelope.room, message.text, message)
+      @web.chat.postMessage(envelope.room, message.text, _.defaults(message, {'as_user': true}))
     else if /<.+\|.+>/.test(message)
-      @web.chat.postMessage(envelope.room, message)
+      @web.chat.postMessage(envelope.room, message, {'as_user' : true})
     else
-      @rtm.sendMessage(message, envelope.room)
+      @rtm.sendMessage(message, envelope.room) # RTM behaves as though `as_user` is true already

 module.exports = SlackClient


made the team name comparison at startup be case insensitive, because the internal mattermost representation of the team name is in all lower-case

--- src/client.coffee
+++ src/client.coffee
@@ -73,7 +73,7 @@
             @logger.debug 'Found '+Object.keys(@teams).length+' teams.'
             for t in @teams
                 @logger.debug "Testing #{t.name} == #{@group}"
-                if t.name == @group
+                if t.name.toLowerCase() == @group.toLowerCase()
                     @logger.debug "Found team! #{t.id}"
                     @teamID = t.id
                     break


Fix issue in getUserDirectMessageChannel  for new channel.

--- src/client.coffee
+++ src/client.coffee
@@ -298,12 +298,11 @@
             # check if channel in other direction exists
             channel = userID + "__" + @self.id
             channel = @findChannelByName(channel)
-            if !channel
-                # channel obviously doesn't exist, let's create it
-                channel = @createDirectChannel(userID)
-                if !channel
-                    if callback? then callback(null)
-        if callback? then callback(channel)
+        if channel
+            # channel obviously doesn't exist, let's create it
+            if callback? then callback(channel)
+            return
+        @createDirectChannel(userID,callback)

     getAllChannels: ->
         @channels
@@ -337,6 +336,8 @@
         return null

     _chunkMessage: (msg) ->
+        if not msg
+            return ['']
         message_length = new TextEncoder.TextEncoder('utf-8').encode(msg).length
         message_limit = messageMaxRunes
         chunks = []
@@ -350,6 +351,15 @@
             create_at: Date.now()
             user_id: @self.id
             channel_id: channelID
+
+        if typeof msg is 'string'
+          postData.message = msg
+        else
+          postData.message = msg.message
+          if msg.props
+            postData.props = msg.props
+
+        console.log '=======postMessage data =======' + JSON.stringify postData

         # break apart long messages
         chunks = @_chunkMessage(postData.message)
@@ -400,12 +410,12 @@
                 'Content-Length': new TextEncoder.TextEncoder('utf-8').encode(post_data).length
         options.headers['Authorization'] = 'BEARER ' + @token if @token
         @logger.debug "#{method} #{path}"
+        @logger.info 'api url:' + options.uri
         request options, (error, res, value) ->
             if error
                 if callback? then callback({'id': null, 'error': error.errno}, {}, callback_params)
             else
                 if callback?
-                    # some create method return 201 when successful, such as create channel
                     if res.statusCode is 200 or res.statusCode is 201
                         if typeof value == 'string'
                             value = JSON.parse(value)


BF-125 Handle zoom when button is pressed.

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -278,25 +278,29 @@
     # to just use THREEJS' lookAt() function, because it may still
     # look at the plane in a wrong angle. Therefore, the rotation
     # has to be hard coded.
-    @tween = new TWEEN.Tween({ cameraPrev: @cameraPrev, x: @cameraPrev.position.x, y: @cameraPrev.position.y, z: @cameraPrev.position.z, xRot: @cameraPrev.rotation.x, yRot: @cameraPrev.rotation.y, zRot: @cameraPrev.rotation.z})
+    @tween = new TWEEN.Tween({ cameraPrev: @cameraPrev, x: @cameraPrev.position.x, y: @cameraPrev.position.y, z: @cameraPrev.position.z, xRot: @cameraPrev.rotation.x, yRot: @cameraPrev.rotation.y, zRot: @cameraPrev.rotation.z, l: @cameraPrev.left, r: @cameraPrev.right, t: @cameraPrev.top, b: @cameraPrev.bottom})
     switch id
       when VIEW_3D
-        @tween.to({  x: 4000, y: 4000, z: 5000, xRot: @degToRad(-36.25), yRot: @degToRad(30.6), zRot: @degToRad(20.47)}, 2000)
+        scale = 2100
+        @tween.to({  x: 4000, y: 4000, z: 5000, xRot: @degToRad(-36.25), yRot: @degToRad(30.6), zRot: @degToRad(20.47), l: -scale, r: scale, t: scale, b: -scale}, 2000)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (-36.25, 30.6, 20.47) -> (-36.25, 30.6, 20.47)
       when PLANE_XY
-        @tween.to({  x: 1250, y: 4000, z: 1250, xRot: @degToRad(-90), yRot: @degToRad(0), zRot: @degToRad(0)}, 2000)
+        scale = 1600
+        @tween.to({  x: 1250, y: 4000, z: 1250, xRot: @degToRad(-90), yRot: @degToRad(0), zRot: @degToRad(0), l: -scale, r: scale, t: scale, b: -scale}, 2000)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (-90, 0, 90) -> (-90, 0, 0)
       when PLANE_YZ
-        @tween.to({  x: 4000, y: 1250, z: 1250, xRot: @degToRad(-90), yRot: @degToRad(90), zRot: @degToRad(0)}, 2000)
+        scale = 1600
+        @tween.to({  x: 4000, y: 1250, z: 1250, xRot: @degToRad(-90), yRot: @degToRad(90), zRot: @degToRad(0), l: -scale, r: scale, t: scale, b: -scale}, 2000)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (0, 90, 0) -> (-90, 90, 0)
       when PLANE_XZ
-        @tween.to({  x: 1250, y: 1250, z: 4000, xRot: @degToRad(0), yRot: @degToRad(0), zRot: @degToRad(0)}, 2000)
+        scale = 1600
+        @tween.to({  x: 1250, y: 1250, z: 4000, xRot: @degToRad(0), yRot: @degToRad(0), zRot: @degToRad(0), l: -scale, r: scale, t: scale, b: -scale}, 2000)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (0, 0, 0) -> (0, 0, 0)
@@ -307,6 +311,11 @@
   updateCameraPrev : ->
     @cameraPrev.position = new THREE.Vector3(@x, @y, @z)
     @cameraPrev.rotation = new THREE.Vector3(@xRot, @yRot, @zRot)
+    @cameraPrev.left = @l
+    @cameraPrev.right = @r
+    @cameraPrev.top = @t
+    @cameraPrev.bottom = @b
+    @cameraPrev.updateProjectionMatrix()
     cam2d.hasChanged = true

   changePrev3D : => View.changePrev(VIEW_3D)


Ensure cover for ghost clicks is added only once.

--- app/client/js/react/gesture.coffee
+++ app/client/js/react/gesture.coffee
@@ -145,6 +145,7 @@

        showCoverTemporarily_: ->
         # Seems to be more reliable then display block/none.
+        return if cover.parentNode == document.body
         document.body.appendChild cover
         clearTimeout @coverHideTimer
         @coverHideTimer = setTimeout ->


closes #134

closes #134

--- app/partials/field-guide-list.cjsx
+++ app/partials/field-guide-list.cjsx
@@ -22,8 +22,6 @@
   '''
   reptiles: '''
     Reptiles are cold-blooded, scaly-skinned vertebrates. Most reptiles lay leathery eggs; however, many lizard and snake species give birth to live young. The reptiles’ dry, scaly skin helps them retain water, making it possible for them to survive and thrive in typically inhospitable habitats such as deserts. However, they are absent from Antarctica. Reptiles comprise over 7,000 species worldwide, about 1,000 of which live in Africa. A third of the world’s reptiles are currently threatened with extinction.
-
-    Click on the icons below to browse some of Gorongosa's reptile species:
   '''


OX-299 fixed some bugs

OX-299 fixed some bugs

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -97,7 +97,7 @@
     cube = @cubes[address[3]]
     bucketIndex = @getBucketIndexByZoomedAddress(address)

-    if bucketIndex? and bucket = cube[bucketIndex] != @LOADING_PLACEHOLDER
+    if bucketIndex? and (bucket = cube[bucketIndex]) != @LOADING_PLACEHOLDER
       bucket
     else
       null
@@ -135,15 +135,14 @@
       bucketIndex = @getBucketIndexByZoomedAddress(address)

       @bucketCount++
-      @access.unshift([bucket_x, bucket_y, bucket_z, zoomStep])
+      @access.unshift(address)
       bucketData.access = 1
-      bucketData.zoomStep = zoomStep
+      bucketData.zoomStep = address[3]

       cube[bucketIndex] = bucketData

-      setArbitraryBucketByZoomedAddress(address, bucketData) if zoomStep <= @ARBITRARY_MAX_ZOOMSTEP
-
-      @trigger("bucketLoaded", [bucket_x, bucket_y, bucket_z, zoomStep])
+      #@setArbitraryBucketByZoomedAddress(address, bucketData) if address[3] <= @ARBITRARY_MAX_ZOOMSTEP
+      @trigger("bucketLoaded", address)

   setArbitraryBucketByZoomedAddress : ([bucket_x, bucket_y, bucket_z, zoomStep], bucketData) ->
@@ -195,10 +194,10 @@
       @bucketCount--
       cube[bucketIndex] = null

-      collectArbitraryBucket(address) if zoomStep <= @ARBITRARY_MAX_ZOOMSTEP
-
-
-  collectArbitraryBucket : ([bucket_x, bucket_y, bucket_z, zoomStep]) ->
+      @collectArbitraryBucket(address, bucket) if address[3] <= @ARBITRARY_MAX_ZOOMSTEP
+
+
+  collectArbitraryBucket : ([bucket_x, bucket_y, bucket_z, zoomStep], oldBucket) ->

     cube = @arbitraryCube

@@ -232,7 +231,7 @@

           bucketIndex = @getBucketIndexByZoomedAddress(subBucket)

-          cube[bucketIndex] = substitute if cube[bucketIndex] == bucket
+          cube[bucketIndex] = substitute if cube[bucketIndex] == oldBucket

   # remove buckets until cube is within bucketCount-limit


#205 Manually apply changes from #159

#205 Manually apply changes from #159

--- app/assets/javascripts/oxalis/model/celltracing.coffee
+++ app/assets/javascripts/oxalis/model/celltracing.coffee
@@ -512,18 +512,18 @@
       @deleteTree(false)

-  deleteTree : (notify, id, deleteBranchesAndComments) ->
+  deleteTree : (notify, id, deleteBranchesAndComments, informServer) ->

     if notify
       if confirm("Do you really want to delete the whole tree?")
-        @reallyDeleteTree(id, deleteBranchesAndComments)
+        @reallyDeleteTree(id, deleteBranchesAndComments, informServer)
       else
         return
     else
-      @reallyDeleteTree(id, deleteBranchesAndComments)
-
-
-  reallyDeleteTree : (id, deleteBranchesAndComments = true) ->
+      @reallyDeleteTree(id, deleteBranchesAndComments, informServer)
+
+
+  reallyDeleteTree : (id, deleteBranchesAndComments = true, informServer = true) ->

     unless id
       id = @activeTree.treeId
@@ -540,7 +540,8 @@
         @deleteComment(node.id)
         @deleteBranch(node)

-    @stateLogger.deleteTree(tree)
+    if informServer
+      @stateLogger.deleteTree(tree)
     @trigger("deleteTree", index)

     # Because we always want an active tree, check if we need
@@ -572,7 +573,7 @@

         @trigger("mergeTree", lastTree.treeId, lastNode, @activeNode)

-        #@deleteTree(false, lastTree.treeId, false)
+        @deleteTree(false, lastTree.treeId, false, false)

         @setActiveNode(activeNodeID)
       else


Maintain a list of series colors

Maintain a list of series colors

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -28,6 +28,7 @@
     @options = $.extend yes, @defaults, options
     @graphs  = []
     @palette = new PG.Palette()
+    @seriesColors = {}

     @renderLoaders()

@@ -79,10 +80,14 @@
     series = []
     _(data).each (seriesData, name) =>
       seriesData = _.map seriesData, (s) -> { x: s[0], y: s[1] }
-      if @options.series[name]?.color?
-        color = @palette.colors[@options.series[name].color]
+      if @seriesColors[name]?
+        color = @seriesColors[name]
       else
-        color = @palette.random()
+        if @options.series[name]?.color?
+          color = @palette.colors[@options.series[name].color]
+        else
+          color = @palette.random()
+        @seriesColors[name] = color
       if renderer is "area"
         stroke = no
       else


templateRequest implementation causes ng-animate to fail. No longer used since we've upgraded to angular 1.5

--- src/factories/templateRequest.coffee
+++ src/factories/templateRequest.coffee
@@ -1,14 +0,0 @@
-# Temporary shim for $templateRequest angular service in place until we can upgrade to 1.3
-# doesn't throw exceptions and thus doesn't have the second parameter as in the angular version
-angular.module('bicker_router').factory '$templateRequest', ($templateCache, $http, $q) ->
-  (url) ->
-    template = $templateCache.get url
-
-    if template?
-      deferred = $q.defer()
-      deferred.resolve template
-      return deferred.promise
-
-    $http.get(url).then (response) ->
-      $templateCache.put url, response.data
-      response.data


fix(account-store): Allow updateAccount in all windows

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -80,10 +80,9 @@
   # Inbound Events

   _onUpdateAccount: (id, updated) =>
-    return unless NylasEnv.isMainWindow()
     idx = _.findIndex @_accounts, (a) -> a.id is id
     account = @_accounts[idx]
-    return if !account or _.isEqual(account, updated)
+    return unless account
     account = _.extend(account, updated)
     @_caches = {}
     @_accounts[idx] = account


Fetch more users on scroll in collaboration picker.

For some reason, when a UserCollection is passed in a comparator
as an argument, it immediately gets an element.  However, if I
instead set that comparator on the next line then it doesn't get
an element right away.

This is breaking scrolling because PaginatedView checks for
whether the collection is "empty" in deciding whether to stop
listening for scrolling.

Yeah, this might be the stupidest bug fix I have ever made.

Fixes COMMS-703

Test Plan:
* Have a course with more than 50 students (make it a lot more).
  I suggest using rails console.  I did something like:
  `c = [some course]
   for i in 1..200 do
     u = User.create!(:name => "User #{i}")
     u.register!
     e = c.enroll_student(u)
     e.accept!
   end`
* Go get some snacks while these things are being made.
* Go to `courses/:course_id/collaborations`
* Scroll down.  Pagination should now work on
  hitting the bottom.

Change-Id: Ic547ee0c17cb3590ee77b1d0673d7953450b307e
Reviewed-on: https://gerrit.instructure.com/138909
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Reviewed-by: Aaron Kc Hsu <089edc94045de3d9f5f372fb17706d21afcc259f@instructure.com>
Tested-by: Jenkins
QA-Review: Landon Gilbert-Bland <60d9ff23682c863d36b48b4c32c5ce5f434d29c0@instructure.com>
Product-Review: Venk Natarajan <f9eab59b292314637a3c917fea4742741e36a19b@instructure.com>

--- app/coffeescripts/views/collaborations/ListView.coffee
+++ app/coffeescripts/views/collaborations/ListView.coffee
@@ -45,7 +45,14 @@
     # Returns a UserCollection or GroupCollection.
     createCollection: (type = 'user') ->
       if type is 'user'
-        new UserCollection(comparator: (user) -> user.get('sortable_name'))
+        # For reasons I don't understand, if a comparator is passed into the
+        # collection, it immediately gets an element (which, due to some logic in
+        # PaginatedView, causes us to not listen for scrolling when deciding
+        # to get more users).  However, if I set a comparator later then that element
+        # isn't added and infinite scrolling works fine.
+        c = new UserCollection()
+        c.comparator = 'sortable_name'
+        c
       else
         collection = new GroupCollection()
         collection.forCourse = true
@@ -118,4 +125,3 @@
     removeFromFilter: (model) ->
       @filteredMembers = reject @filteredMembers, (m) ->
         m.get('id') is model.get('id')
-


Removed newline from SETUP and PAYOFF snippets.

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -23,12 +23,10 @@
 '.source.gfm':
   'Setup Beat':
     'prefix': 'setup'
-    'body': '<!-- SETUP: $1 \n
-      -->'
+    'body': '<!-- SETUP: $1 -->'
   'Payoff Beat':
     'prefix': 'payoff'
-    'body': '<!-- PAYOFF: $1 \n
-      -->'
+    'body': '<!-- PAYOFF: $1 -->'
   'Story Front Matter':
     'prefix': 'storyhead'
     'body': '---\n


:atom 🎨

:atom 🎨

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -52,7 +52,7 @@
   "Test block":
     prefix: "test"
     body: """
-      test "$1" do
+      test '$1' do
         $2
       end
     """


Update Atom snippets

Update Atom snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -17,10 +17,24 @@
 # If you are unfamiliar with CSON, you can read more about it here:
 # https://github.com/bevry/cson#what-is-cson

+# HTML
 '.text.html':
   'Script':
     'prefix': 'script'
-    'body': '<script>\n\t$1\n</script>'
+    'body': """
+      <script>
+        $1
+      </script>
+    """
+
   'Script with source':
     'prefix': 'scriptsrc'
     'body': '<script src="$1"></script>'
+
+  'Style':
+    'prefix': 'style'
+    'body': """
+      <style>
+        $1
+      </style>
+    """