Finish joinMessages handler

Finish joinMessages handler

--- handlers/joinmessages.coffee
+++ handlers/joinmessages.coffee
@@ -30,7 +30,7 @@
 db = require '../db'
 { __, __n } = require '../i18n'

-getByUsernameQuery = getQuery = setQuery = delQuery = null
+getQuery = setQuery = delQuery = null

 onLoad = (callback) ->
 	db.serialize ->
@@ -40,7 +40,6 @@
 			PRIMARY KEY(userID)
 		);"

-		getByUsernameQuery = db.prepare "SELECT joinMessages.value, users.* FROM users LEFT JOIN joinMessages ON users.userID = joinMessages.userID WHERE users.lastUsername = ?"
 		getQuery = db.prepare "SELECT value FROM joinMessages WHERE userID = ?"
 		setQuery = db.prepare "INSERT OR REPLACE INTO joinMessages (userID, value) VALUES (?, ?)"
 		delQuery = db.prepare "DELETE FROM joinMessages WHERE userID = ?"
@@ -66,34 +65,74 @@
 			when 'quote'
 				[ username ] = parameters.split /,/
 				username = message.username unless username? and username
+				username = username.trim()

-				getByUsernameQuery.get username, (err, row) ->
-					if err? or not row?.value?
-						debug "Error while reading quote: #{err}" if err?
-
-						if username is message.username
-							api.replyTo message, __("You currently don't have a quote set."), no, callback
-						else
-							api.replyTo message, __("User “%1$s” has no quote set.", username), no, callback
+				db.getUserByUsername username, (err, user) ->
+					if user?
+						getQuery.get user.userID, (err, row) ->
+							if err? or not row?.value?
+								debug "Error while reading quote: #{err}" if err?
+
+								if username is message.username
+									api.replyTo message, __("You currently don't have a quote set."), no, callback
+								else
+									api.replyTo message, __("User “%1$s” has no quote set.", username), no, callback
+							else
+								api.replyTo message, __("[%1$s] %2$s", username, row.value), yes, callback
 					else
-						api.replyTo message, __("[%1$s] %2$s", username, row.value), yes, callback
+						callback?()
 			when 'setquote'
+				parameters = parameters.trim()
 				setQuery.run message.sender, parameters, (err) ->
 					if err?
-						debug "Error"
-						api.replyTo message, __("User %1$s has no quote set.", row.users.lastUsername), no, callback
+						debug "Error while setting quote: #{err}"
+						api.replyTo message, __("Failed while setting your quote."), no, callback
 					else
 						api.replyTo message, __("Your quote has been set to: “%1$s”", parameters), yes, callback
 			when 'delquote'
 				delQuery.run message.sender, (err) ->
 					if err?
 						debug "Error while deleting quote: #{err}"
+						api.replyTo message, __("Failed while deleting your quote."), no, callback
 					else
 						api.replyTo message, __("Your quote has been deleted."), yes, callback
 			when 'forcequote'
-				callback?()
+				db.checkPermissionByMessage message, 'joinMessages.forcequote', (hasPermission) ->
+					if hasPermission
+						[ username, quote... ] = parameters.split /,/
+						username = username.trim()
+						quote = quote.join(' ').trim()
+
+						db.getUserByUsername username, (err, user) ->
+							if user?
+								setQuery.run user.userID, quote, (err) ->
+									if err?
+										debug "Error while setting quote: #{err}"
+										api.replyTo message, __("Failed while setting quote of „%1$s“."), no, callback
+									else
+										api.replyTo message, __("Quote of „%1$s“ has been set to: “%2$s”", username, quote), yes, callback
+							else
+								callback?()
+					else
+						callback?()
 			when 'wipequote'
-				callback?()
+				db.checkPermissionByMessage message, 'joinMessages.wipequote', (hasPermission) ->
+					if hasPermission
+						[ username, quote... ] = parameters.split /,/
+						username = username.trim()
+
+						db.getUserByUsername username, (err, user) ->
+							if user?
+								delQuery.run user.userID, (err) ->
+									if err?
+										debug "Error while deleting quote: #{err}"
+										api.replyTo message, __("Failed while deleting quote of „%1$s“.", username), no, callback
+									else
+										api.replyTo message, __("Quote of „%1$s“ has been deleted.", username), yes, callback
+							else
+								callback?()
+					else
+						callback?()
 			else
 				callback?()
 	else
@@ -102,7 +141,6 @@

 unload = (callback) ->
 	async.parallel [
-		(callback) -> getByUsernameQuery.finalize -> getQuery = null; callback?()
 		(callback) -> getQuery.finalize -> getQuery = null; callback?()
 		(callback) -> setQuery.finalize -> setQuery = null; callback?()
 		(callback) -> delQuery.finalize -> delQuery = null; callback?()


Super important! Fixed the editor font.

Super important! Fixed the editor font.

--- app/client/client.coffee
+++ app/client/client.coffee
@@ -93,7 +93,8 @@
         editor.setOptions
             minLines: 21
             maxLines: 21
-            fontSize: '12pt'
+            fontFamily: 'Monospace,Menlo,Monaco,Consolas,"Courier New"'
+            fontSize: '11pt'
             highlightActiveLine: false
         editor.setValue sample.cc, -1
         editor.setValue sample.cc, 1


When a draggable is in the air, we prevent it from receiving pointer events. This is the secret to being able to perform mouse-event-based hit detection on droppables, without resorting to reading the offset of every droppable.

--- src/draggable.coffee
+++ src/draggable.coffee
@@ -90,6 +90,9 @@
       # Position the draggable relative if it's currently statically positioned
       @$element.css(position: 'relative') if @getConfig().helper is 'original' and @$element.css('position') is 'static'

+      # Save the original value of the pointer-events CSS property
+      @originalPointerEventsPropertyValue = @$element.css('pointerEvents')
+
       # Done!
       @setupPerformed = true

@@ -196,6 +199,8 @@
       @$helper
         # Apply the dragging class
         .addClass(@getConfig().draggingClass)
+        # Kill pointer events while in mid-drag
+        .css(pointerEvents: 'none')

       # Mark the drag as having started
       @dragStarted = true
@@ -217,6 +222,9 @@

     handleDragStop: (e) ->
       @cancelAnyScheduledDrag()
+
+      # Restore the original value of the pointer-events property
+      @$element.css(pointerEvents: @originalPointerEventsPropertyValue)

       # Call any user-supplied stop callback
       @getConfig().stop?(e)


Added more translations (#3803)

Added more translations (#3803)

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -209,10 +209,10 @@
     not: "no"
     "!": "no"
     "=": "asigne a"
-    "==": "iguala"
-    "===": "iguala estrictamente"
-    "!=": "no iguala"
-#    "!==": "does not strictly equal"
+    "==": "igual a"
+    "===": "igual a estrictamente"
+    "!=": "no igual a"
+    "!==": "no estrictamente igual"
     ">": "es mayor que"
     ">=": "es mayor que o igual"
     "<": "es menor que"
@@ -511,14 +511,14 @@
     tip_mistakes_proof_of_trying: "Errores en tu código son solo evidencia de que estas intentando."
 #    tip_adding_orgres: "Rounding up ogros."
     tip_sharpening_swords: "Afilando las espadas."
-#    tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille"
+    tip_ratatouille: "No debes dejar que nadie defina tus límites a causa de donde venas. Tu único límite es tu alma. - Gusteau, Ratatouille"
     tip_nemo: "¿Cuando huye la suerte, sabes que hay que hacer? Sigue nadando, sigue nadando. - Dory, Finding Nemo"
-#    tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green"
-#    tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green"
-#    tip_self_taught: "I taught myself 90% of what I've learned. And that's normal! - Hank Green"
+    tip_internet_weather: "Debes moverte al intenet, es genial aquí. Tenemos la oportunidad de vivir dentro donde el clima es siempre sorprendente. - John Green"
+    tip_nerds: "A los Nerds se les permite amor cosas, como saltar-arriba-y-abajo-en-la-silla-no-te-puedes-controlar-tu-mismo quiérelo. - John Green"
+    tip_self_taught: "Me enseñé a mí mismo el 90% de lo que he aprendido. ¡Y eso es normal! - Hank Green"
     tip_luna_lovegood: "No te preocupes, estas tan cuerdo como yo. - Luna Lovegood"
-#    tip_good_idea: "The best way to have a good idea is to have a lot of ideas. - Linus Pauling"
-#    tip_programming_not_about_computers: "La ciencia cpomputacional is no more about computers than astronomy is about telescopes. - Edsger Dijkstra"
+    tip_good_idea: "La mejor forma de tener una buena idea es tener un montón de ideas. - Linus Pauling"
+    tip_programming_not_about_computers: "En las Ciencias de la Computación no es más acerca de computadoras que la astronomía acerca de telescopios. - Edsger Dijkstra"
     tip_mulan: "Si crees que puedes, entonces lo harás. - Mulan"

   game_menu:
@@ -755,7 +755,7 @@
     josh_blurb: "El piso es Lava"
     phoenix_title: "Ingeniero de Software"
     nolan_title: "Administrador de territorio"
-#    elliot_title: "Administrador de Partnership"
+    elliot_title: "Administrador de Asociación"
     retrostyle_title: "Ilustración"
     retrostyle_blurb: "Juegos con estilo Retro"
     jose_title: "Música"
@@ -827,14 +827,14 @@
     primary_role_label: "Tu Rol Primario"
     role_default: "Seleccione Rol"
     primary_role_default: "Seleccione Rol Primario"
-#    purchaser_role_default: "Seleccione Purchaser Role"
+    purchaser_role_default: "Seleccione Rol de Comprador"
     tech_coordinator: "Coordinador de tecnología"
     advisor: "Tutor"
     principal: "Director"
     superintendent: "Supervisor"
     parent: "Padre"
-#    purchaser_role_label: "Your Purchaser Role"
-#    influence_advocate: "Influence/Advocate"
+    purchaser_role_label: "Tu Rol de Comprador"
+    influence_advocate: "Influencia/Partidario"
     evaluate_recommend: "Evaluar/Recomendar"
     approve_funds: "Aprobar Fondos"
 #    no_purchaser_role: "No role in purchase decisions"
@@ -1245,8 +1245,8 @@
     additional_resources_4_pref: "Consulte nuestra"
     additional_resources_4_mid: "Página de Escuelas"
     additional_resources_4_suff: "para aprender más sobre la oferta para el aula de CodeCombat."
-#    educator_wiki_pref: "Or check out our new"
-#    educator_wiki_mid: "educator wiki"
+    educator_wiki_pref: "O echa un vistazo a nuestra nieva"
+    educator_wiki_mid: "wiki de educador"
     educator_wiki_suff: "busca en nuestra guía en línea."
     your_classes: "Tus Clases"
     no_classes: "Aún no hay clases!"
@@ -1278,105 +1278,105 @@
     language_cannot_change: "El lenguaje no puede ser cambiado una vez que el estudiante ingreso a la clase."
     learn_p: "Aprender Python"
     learn_j: "Aprender JavaScript"
-#    avg_student_exp_label: "Average Student Programming Experience"
-#    avg_student_exp_desc: "This will help us understand how to pace courses better."
-#    avg_student_exp_select: "Select the best option"
-#    avg_student_exp_none: "No Experience - little to no experience"
-#    avg_student_exp_beginner: "Beginner - some exposure or block-based"
-#    avg_student_exp_intermediate: "Intermediate - some experience with typed code"
-#    avg_student_exp_advanced: "Advanced - extensive experience with typed code"
-#    avg_student_exp_varied: "Varied Levels of Experience"
-#    student_age_range_label: "Student Age Range"
-    student_age_range_younger: "Menor que than 6"
+    avg_student_exp_label: "Promedio de la experiencia en programación del estudiante"
+    avg_student_exp_desc: "Esto nos ayudará a entender como llevar el ritmo de los cursos."
+    avg_student_exp_select: "Selecciona la mejor opción"
+    avg_student_exp_none: "Sin experiencia - poca o ninguna experiencia"
+    avg_student_exp_beginner: "Principiante - cierto grado de exposición o basado en bloque"
+    avg_student_exp_intermediate: "Intermedio - un poco de experiencia con código tipeado"
+    avg_student_exp_advanced: "Avanzado - amplia experiencia con código tipeado"
+    avg_student_exp_varied: "Niveles Variados de Experiencia"
+    student_age_range_label: "Rango de Edad del Estudiante"
+    student_age_range_younger: "Menor que 6"
     student_age_range_older: "Mayor que 18"
-#    student_age_range_to: "to"
+    student_age_range_to: "a"
     create_class: "Crear Grupo"
     class_name: "Nombre de clase"
-#    teacher_account_restricted: "Your account is a teacher account, and so cannot access student content."
+    teacher_account_restricted: "Tu cuenta es una cuenta de maestro, y no puedes acceder al contenido del estudiante."

   teacher:
-#    teacher_dashboard: "Teacher Dashboard" # Navbar
-#    my_classes: "My Classes"
-#    courses: "Courses"
+    teacher_dashboard: "Tablero del maestro" # Navbar
+    my_classes: "Mis Clases"
+    courses: "Cursos"
     enrollments: "Recursos"
     resources: "Resources"
     help: "Ayuda"
-#    students: "Students" # Shared
+    students: "Estudiantes" # Shared
     language: "Lenguaje"
-#    edit_class_settings: "edit class settings"
-#    complete: "Complete"
-#    access_restricted: "Account Update Required"
-#    teacher_account_required: "A teacher account is required to access this content."
-    create_teacher_account: "Crear Cuenta de Maestra"
-    what_is_a_teacher_account: "Cuál es una Cuenta de Maestra?"
-#    teacher_account_explanation: "Una Cuenta de Maestra en CodeCombat Teacher da permiso a crear grupo, monitor students’ progress as they work through courses, manage enrollments and access resources to aid in your curriculum-building."
-#    current_classes: "Current Classes"
-#    archived_classes: "Archived Classes"
-#    archived_classes_blurb: "Classes can be archived for future reference. Unarchive a class to view it in the Current Classes list again."
-#    view_class: "view class"
-#    archive_class: "archive class"
-#    unarchive_class: "unarchive class"
-#    unarchive_this_class: "Unarchive this class"
-#    no_students_yet: "This class has no students yet."
-#    add_students: "Add Students"
-#    create_new_class: "Create a New Class"
-#    class_overview: "Class Overview" # View Class page
-#    avg_playtime: "Average level playtime"
-#    total_playtime: "Total play time"
-#    avg_completed: "Average levels completed"
-#    total_completed: "Total levels completed"
-#    created: "Created"
-#    concepts_covered: "Concepts covered"
-#    earliest_incomplete: "Earliest incomplete level"
-#    latest_complete: "Latest completed level"
-#    enroll_student: "Enroll student"
-#    adding_students: "Adding students"
-#    course_progress: "Course Progress"
+    edit_class_settings: "editar configuración de la clase"
+    complete: "Completo"
+    access_restricted: "Se requiere Actualización de la Cuenta"
+    teacher_account_required: "Una cuenta de Maestro es requerida para acceder a este contenido."
+    create_teacher_account: "Crear Cuenta de Maestro"
+    what_is_a_teacher_account: "Qué es una Cuenta de Maestro?"
+    teacher_account_explanation: "Una Cuenta de Maestro en CodeCombat da permiso a crear salones de clases, monitorear el progreso de los estudiantes mientras ellos trabajan a través de los cursos, manejar inscripciones y acceder a recursos para la creación de su plan de estudio."
+    current_classes: "Clases Recientes"
+    archived_classes: "Clases Archivadas"
+    archived_classes_blurb: "Las Clases pueden ser archivadas para futuras referencias. Desarchiva una Clase para verla en la lista de Clases Recientes de nuevo."
+    view_class: "ver clase"
+    archive_class: "almacenar clase"
+    unarchive_class: "desarchivar clase"
+    unarchive_this_class: "desarchivar esta clase"
+    no_students_yet: "Esta clase no tienes estudiantes todavía."
+    add_students: "Agregar Estudiantes"
+    create_new_class: "Crear una Nueva Clase"
+    class_overview: "Resumen de la Clase" # View Class page
+    avg_playtime: "Nivel medio de tiempo de juego"
+    total_playtime: "Tiempo Total de juego"
+    avg_completed: "Promedio de niveles completados"
+    total_completed: "Total de niveles completados"
+    created: "Creado"
+    concepts_covered: "Conceptos cubiertos"
+    earliest_incomplete: "Nivel incompleto más Reciente"
+    latest_complete: "Último nivel completado"
+    enroll_student: "Inscribir estudiante"
+    adding_students: "Agregar estudiantes"
+    course_progress: "Progreso del curso"
 #    not_applicable: "N/A"
-#    edit: "edit"
-#    remove: "remove"
-#    latest_completed: "Latest Completed"
-#    sort_by: "Sort by"
-#    progress: "Progress"
-#    completed: "Completed"
-#    started: "Started"
-#    click_to_view_progress: "click to view progress"
-#    no_progress: "No progress"
-#    select_course: "Select course to view"
-#    course_overview: "Course Overview"
-#    copy_class_code: "Copy Class Code"
-#    class_code_blurb: "New students can enter this class code on their dashboard or visit codecombat.com/courses to join the class."
-#    copy_class_url: "Copy Class URL"
-#    class_join_url_blurb: "New students can visit this URL while logged in to join the class."
-#    add_students_manually: "Add Students Manually"
-#    bulk_assign: "Bulk-assign"
-#    assign_to_selected_students: "Assign to Selected Students"
-#    assigned: "Assigned"
-#    enroll_selected_students: "Enroll Selected Students"
-#    cant_assign_to_unenrolled: "Course cannot be assigned to students who are not enrolled."
-#    no_students_selected: "No students were selected."
-#    guides_coming_soon: "Guides coming soon!" # Courses
-#    show_students_from: "Show students from" # Enroll students modal
-#    enroll_the_following_students: "Enroll the following students"
-#    all_students: "All Students"
-#    enroll_students: "Enroll Students"
-#    not_enough_enrollments: "Not enough Enrollments available."
-#    enrollments_blurb_1: "Students taking Computer Science" # Enrollments page
-#    enrollments_blurb_2: "require enrollments to access the courses."
-#    credits_available: "Credits Available"
-#    total_unique_students: "Total Unique Students"
-#    total_enrolled_students: "Total Enrolled Students"
-#    unenrolled_students: "Unenrolled Students"
-#    add_enrollment_credits: "Add Enrollment Credits"
+    edit: "editar"
+    remove: "eliminar"
+    latest_completed: "Último Completado"
+    sort_by: "Ordenado por"
+    progress: "Progreso"
+    completed: "Completado"
+    started: "Iniciado"
+    click_to_view_progress: "click para ver el progreso"
+    no_progress: "Sin progreso"
+    select_course: "Selecciona el curso a ver"
+    course_overview: "Resumen del Curso"
+    copy_class_code: "Copiar código de la Clase"
+    class_code_blurb: "Nuevos estudiantes pueden entrar a este código de la clase en su tablero o visitar codecombat.com/courses para unirse a la clase."
+    copy_class_url: "Copia la URL de la Clase"
+    class_join_url_blurb: "Nuevos estudiantes pueden visitar esta URL mientras esten logeados para unirse a la clase."
+    add_students_manually: "Agregar Estudiantes Manualmente"
+    bulk_assign: "Mayor a asignar"
+    assign_to_selected_students: "Asignar a los Estudiantes Seleccionados"
+    assigned: "Asignado"
+    enroll_selected_students: "Inscribir Estudiantes Seleccionados"
+    cant_assign_to_unenrolled: "El curso no puede ser asignado a estudiantes que no están inscritos."
+    no_students_selected: "No fueron seleccionados estudiantes."
+    guides_coming_soon: "¡Guías próximamente!" # Courses
+    show_students_from: "Muestra estudiantes de" # Enroll students modal
+    enroll_the_following_students: "Inscribir los siguientes estudianes"
+    all_students: "Todos los estudiantes"
+    enroll_students: "Inscribir Estudiantes"
+    not_enough_enrollments: "No tienes suficientes Inscripciones disponibles."
+    enrollments_blurb_1: "Estudiantes tomando Ciencias de la Computación" # Enrollments page
+    enrollments_blurb_2: "se requieren inscritos para acceder al curso."
+    credits_available: "Cŕeditos Disponibles"
+    total_unique_students: "Total de Estudiantes Únicos"
+    total_enrolled_students: "Total de Estudiantes Inscritos"
+    unenrolled_students: "Estudiantes no Inscritos"
+    add_enrollment_credits: "Agregar Cŕeditos de Inscripción"
     purchasing: "Adquiriendo..."
     purchased: "Adquirido!"
-#    purchase_now: "Purchase Now"
-#    how_to_enroll: "How to Enroll Students"
+    purchase_now: "Adquirir Ahora"
+    how_to_enroll: "¿Cómo inscribir estudiantes?"
     how_to_enroll_blurb_1: "Si un estudiante no está inscrito aún, encontrará un botón \"Inscribirse\" al lado del progreso de curso en su clase."
     how_to_enroll_blurb_2: "Para inscribir en bloque a estudiantes, seleccionelos utilizando la casilla de verificación al lado izquierdo de la página de la clase, luego de clic al botón \"Inscribir Estudiantes Seleccionados\" ."
     how_to_enroll_blurb_3: "Una vez que el estudiante se ha inscrito, podrá acceder a todo el contenido del curso."
     bulk_pricing_blurb: "Quieres comprar más de 25 cuentas de estudiante? Contáctanos para hablar del siguiente paso."
-#    total_unenrolled: "Total unenrolled"
+    total_unenrolled: "Total de Estudiantes no Inscritos"
     export_student_progress: "Exportar el Progreso del Estudiante(CSV)"

   classes:


Fix indentation

Fix indentation

--- app/models/Player.coffee
+++ app/models/Player.coffee
@@ -18,7 +18,7 @@
     tileheight: 32
     tilewidth: 32

-# overwrite render method
+  # overwrite render method
   render: (ctx, cx, cy) =>

     #map = (@get 'mediator').map


FIX: error in component test

FIX: error in component test

--- test/javascripts/tao/component_test.coffee
+++ test/javascripts/tao/component_test.coffee
@@ -30,7 +30,7 @@
     TaoComponent.register @TestComponent

   beforeEach: ->
-    @component = $('<test-component array="[4,5]">').appendTo('body').get(0)
+    @component = $('<test-component>').appendTo('body').get(0)

   afterEach: ->
     @component.jq.off().remove()
@@ -58,7 +58,7 @@
     assert.equal @component.active, true
     assert.equal @component.hasAttribute('active'), false
     assert.equal _.isEqual(@component.json, {x: 1}), true
-    assert.equal _.isEqual(@component.array, [4,5]), true
+    assert.equal _.isEqual(@component.array, []), true

     @component.active = false
     assert.equal @component.active, false


change export name to sprites as well

change export name to sprites as well

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -12,7 +12,7 @@
 {vector} = require('../god/vector')
 {sprite_state} = require './sprite_state'

-exports.sprite = {
+exports.sprites = {
   _LABEL: "sprites"
   _KIND: "sprite"
   _EXPORTS: ["step", "reset"]


fix fsTime

fix fsTime

--- src/fs/fsTime.coffee
+++ src/fs/fsTime.coffee
@@ -52,7 +52,9 @@
         []

     updateDropdown = ->
-      if scope.value?.indexOf(':') >= 0
+      if not scope.value
+        filterStr = ""
+      else if scope.value?.indexOf(':') >= 0
         filterStr = scope.value
       else
         filterStr = "#{scope.value}:"


Refactorings for players

Refactorings for players

--- app/scripts/index.coffee
+++ app/scripts/index.coffee
@@ -18,13 +18,15 @@
   createPlayerO = -> computerPlayer()

   # player :: {
-  #   setup : (done: None -> None) -> None
-  #   play : None -> None
-  #   end? : None -> None
-  #   toString : None -> Str
+  #   setup : (done: ->) ->
+  #   teardown : ->
+  #   play : ->
+  #   toString : -> Str
   # }

   next = ->
+    unplayable()
+    markWins()
     player = if game.nextPlayer is X then playerX else playerO
     player.play()

@@ -66,41 +68,41 @@
   playerText = -> (decode game.nextPlayer).toLowerCase()

   # bus :: {
-  #   open?
-  #   postMessage : (message : Any) -> None
-  #   onmessage
-  #   close?
-  #   toString
+  #   setup : (done : ->)
+  #   teardown : ->
+  #   postMessage : (message : Any) ->
+  #   onMessage : (callback : (m: Any) ->) ->
+  #   toString : -> Str
   # }
   messagingPlayer = (bus) ->
+    onAction = ([i, j]) ->
+      hideSpinner()
+      $ "##{i}\\,#{j}"
+        .text playerText()
+        .highlight()
+      game = game.play [i, j]
+      next()
     setup: (done) ->
-      bus.open?()
-      bus.onmessage (e) ->
-        hideSpinner()
-        [i, j] = e.data.action
-        $ "##{i}\\,#{j}"
-          .text playerText()
-          .highlight()
-        game = game.play [i, j]
-        next()
-      done()
+      bus.setup ->
+        bus.onmessage (m) -> onAction m.action
+        done()
     play: ->
-      unplayable()
-      markWins()
       return if checkGameOver()
       playable()
       showSpinner()
       bus.postMessage command: 'play', args: game.state()
-    end: -> bus.close?()
+    teardown: -> bus.teardown()
     toString: -> bus.toString()

   computerPlayer = (depth = 3) ->
     worker = new Worker 'scripts/minimax-worker.min.js'
     bus =
-      open: -> worker.postMessage command: 'setup', args: depth
+      setup: (done) ->
+        worker.postMessage command: 'setup', args: depth
+        done()
       postMessage: (m) -> worker.postMessage m
-      onmessage: (f) -> worker.onmessage = f
-      close: -> worker.terminate()
+      onmessage: (f) -> worker.onmessage = (e) -> f e.data
+      teardown: -> worker.terminate()
       toString: -> 'computer'
     messagingPlayer bus

@@ -111,8 +113,6 @@
       [(int match[1]), (int match[2])]
     setup: (done) -> done()
     play: ->
-      unplayable()
-      markWins()
       return if checkGameOver()
       playableClassName = playable()
       $ ".#{playableClassName}"
@@ -135,8 +135,8 @@
     playerX.setup -> playerO.setup -> next()

   teardown = ->
-    playerX?.end?()
-    playerO?.end?()
+    playerX?.teardown?()
+    playerO?.teardown?()
     hideSpinner()
     $ '.tic-tac-toe'
       .removeClass 'x-won-board'


add mode controls to robot control menu

add mode controls to robot control menu

--- app/scripts/menus.coffee
+++ app/scripts/menus.coffee
@@ -2,11 +2,12 @@
 # huge hack, needs to be kept outside of Angular
 require = window.requireNode

-angular.module('daemon.menubar', ['daemon.fieldcontrol'])
+angular.module('daemon.menubar', ['daemon.fieldcontrol', 'daemon.radio'])

 .service('menubar', [
   'fieldcontrol'
-  (fieldcontrol) ->
+  'radio'
+  (fieldcontrol, radio) ->
     gui = require('nw.gui')
     win = gui.Window.get()
     menubar = new gui.Menu(type: 'menubar')
@@ -16,6 +17,26 @@
       menubar.createMacBuiltin('Daemon')

     fieldControlMenu = new gui.Menu()
+
+    fieldControlMenu.append new gui.MenuItem
+      label: 'Teleop'
+      click: -> radio.setTeleoperated()
+      key: '1'
+      modifiers: if onMac then 'cmd-shift' else 'ctrl-shift'
+
+    fieldControlMenu.append new gui.MenuItem
+      label: 'Autonomous'
+      click: -> radio.setAutonomous()
+      key: '2'
+      modifiers: if onMac then 'cmd-shift' else 'ctrl-shift'
+
+    fieldControlMenu.append new gui.MenuItem
+      label: 'Emergency Stop'
+      click: -> radio.emergencyStop()
+      key: 'e'
+      modifiers: if onMac then 'cmd' else 'ctrl'
+
+    fieldControlMenu.append new gui.MenuItem type: 'separator'

     fieldControlMenu.append new gui.MenuItem
       type: 'checkbox'
@@ -28,7 +49,7 @@
       checked: false

     menubar.append new gui.MenuItem
-      label: 'Field Control'
+      label: 'Robot Control'
       submenu: fieldControlMenu

     debugMenu = new gui.Menu()


:art: Consolidate logic for splitting/merging MarkerIndex nodes

--- src/marker-index.coffee
+++ src/marker-index.coffee
@@ -48,9 +48,8 @@
         i += newChildren.length - 1
       break if rangeIsEmpty

-    if @children.length > BRANCHING_THRESHOLD
-      splitIndex = Math.ceil(@children.length / BRANCHING_THRESHOLD)
-      [new Node(@children.slice(0, splitIndex)), new Node(@children.slice(splitIndex))]
+    if newNodes = @splitIfNeeded()
+      newNodes
     else
       addSet(@ids, ids)
       return
@@ -60,10 +59,7 @@
     i = 0
     while i < @children.length
       @children[i].delete(id)
-      if @children[i - 1]?.shouldMergeWith(@children[i])
-        @children.splice(i - 1, 2, @children[i - 1].merge(@children[i]))
-      else
-        i++
+      i++ unless @mergeChildrenIfNeeded(i - 1)

   splice: (position, oldExtent, newExtent, exclusiveIds, precedingIds) ->
     oldRangeIsEmpty = oldExtent.isZero()
@@ -83,7 +79,6 @@
       child = @children[i]
       childStart = childEnd
       childEnd = childStart.traverse(child.extent)
-      i++

       switch childEnd.compare(position)
         when -1 then childPrecedesRange = true
@@ -100,19 +95,14 @@
         else
           relativeStart = position.traversalFrom(childStart)
           if splitNodes = child.splice(relativeStart, oldExtent, newExtent, exclusiveIds, precedingIds)
-            @children.splice(i - 1, 1, splitNodes...)
+            @children.splice(i, 1, splitNodes...)
           remainderToDelete = spliceOldEnd.traversalFrom(childEnd)
           childEnd = childStart.traverse(child.extent)

-      if @children[i - 2]?.shouldMergeWith(child)
-        @children.splice(i - 2, 2, @children[i - 2].merge(child))
-        i--
-
+      i++ unless @mergeChildrenIfNeeded(i - 1)
       precedingIds = child.ids

-    if @children.length > BRANCHING_THRESHOLD
-      splitIndex = Math.ceil(@children.length / BRANCHING_THRESHOLD)
-      [new Node(@children.slice(0, splitIndex)), new Node(@children.slice(splitIndex))]
+    @splitIfNeeded()

   getStart: (id) ->
     return unless @ids.has(id)
@@ -187,6 +177,18 @@
     if children[joinIndex].shouldMergeWith(children[joinIndex + 1])
       children.splice(joinIndex, 2, children[joinIndex].merge(children[joinIndex + 1]))
     new Node(children)
+
+  splitIfNeeded: ->
+    if (branchingRatio = @children.length / BRANCHING_THRESHOLD) > 1
+      splitIndex = Math.ceil(branchingRatio)
+      [new Node(@children.slice(0, splitIndex)), new Node(@children.slice(splitIndex))]
+
+  mergeChildrenIfNeeded: (i) ->
+    if @children[i]?.shouldMergeWith(@children[i + 1])
+      @children.splice(i, 2, @children[i].merge(@children[i + 1]))
+      true
+    else
+      false

   toString: (indentLevel=0) ->
     indent = ""


Guard against non-existent .workspace element

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -52,10 +52,12 @@
     # a body element that has the same classes as the current body element.
     unless @testEditor?
       testBody = document.createElement('body')
-      testBody.classList.add(document.body.classList.toString().split(' ')...)
+      testBody.classList.add(@classesForElement(document.body)...)

       testWorkspace = document.createElement('body')
-      testWorkspace.classList.add(document.body.querySelector('.workspace').classList.toString().split(' ')...)
+      workspaceClasses = @classesForElement(document.body.querySelector('.workspace')) ? ['.workspace']
+      testWorkspace.classList.add(workspaceClasses...)
+
       testBody.appendChild(testWorkspace)

       @testEditor = document.createElement('div')
@@ -114,3 +116,7 @@
       label.replace(/\&/g, '')
     else
       label
+
+  # Get an {Array} of {String} classes for the given element.
+  classesForElement: (element) ->
+    element?.classList.toString().split(' ') ? []


Proper caps of GitHub

Proper caps of GitHub

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -189,7 +189,7 @@
             dom.a({href:'https://twitter.com/sollaires'}, '@sollaires')
             ")"
           dom.div {className: 'techNotes'}, "Technical details and code available on ",
-            dom.a {href: 'https://github.com/seriallos/eve-year-in-review'}, 'Github'
+            dom.a {href: 'https://github.com/seriallos/eve-year-in-review'}, 'GitHub'

   renderLoading: ->
       return dom.div {className: 'vert-center'},


Resolved syntax error in Sheet#enterPunch related to the last commit

--- src/models/sheet.coffee
+++ src/models/sheet.coffee
@@ -109,7 +109,7 @@
                 deferred.reject err
               else
                 row_matches =
-                  (r for r in rows when r[headers.id] is row[headers.id])[0]
+                  (r for r in rows when r[headers.id] is row[headers.id])
                 row_match = row_matches[0]
                 punch.assignRow row_match
                 user.punches.push punch


Add initial cache lookup method

Add initial cache lookup method

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -6,8 +6,17 @@
 # Precompute versions of all modules in node_modules
 # Precompute the version each file is compatible

+getCachedModulePath = (relative, parent) ->
+  return unless relative
+  return unless parent?.id
+
+  return if relative[0] is '.'
+  return if relative[relative.length - 1] is '/'
+  return if fs.isAbsolute(relative)
+
+  console.log "looking up #{relative} from #{parent.id}"
+
+  undefined
+
 Module._resolveFilename = (relative, parent) ->
-  resolved = originalResolveFilename.apply(global, arguments)
-  if relative[0] isnt '.' and not fs.isAbsolute(relative) and relative isnt resolved
-    console.log "#{relative} -> #{resolved}"
-  resolved
+  getCachedModulePath(relative, parent) ? originalResolveFilename(relative, parent)


Added necessary stubs to native_methods to prevent undefined errors

--- src/natives/main.coffee
+++ src/natives/main.coffee
@@ -1064,6 +1064,7 @@
         o 'getSystemGMTOffsetID()L!/lang/String;', (rs) ->
             null # XXX may not be correct
       ]
+    net: {}
   sun:
     font:
       FontManager: [
@@ -1329,3 +1330,5 @@
               # docs.
               gLong.fromNumber(-1)
         ]
+    net:
+      spi: {}


Correct Logger

Correct Logger

--- src/notification.coffee
+++ src/notification.coffee
@@ -78,7 +78,7 @@
 	  	if cb
 	  		cb err
 	  	else
-	  		Logging.eror err
+	  		Logger.eror err
 	  req.write payload, "utf-8"
 	  req.end()


Email Notifications: fix a typo

Email Notifications: fix a typo

--- client/Account/views/emailnotifications.coffee
+++ client/Account/views/emailnotifications.coffee
@@ -17,7 +17,7 @@
       comment          :
         title          : 'When my post gets a new comment'
       likeActivities   :
-        title          : 'When I someone likes my content'
+        title          : 'When someone likes my content'
       groupInvite      :
         title          : 'When someone invites me to their group'
       groupRequest     :


If error occurred when fetching compiledApp force to refresh from FS, and show a notification about that.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -154,9 +154,12 @@
     appPath = getAppPath manifest
     indexJsPath = "#{appPath}/index.js"
     @kiteController.run "cat #{escapeFilePath indexJsPath}", (err, response)=>
-      if err then warn err
+      if err
+        new KDNotificationView
+          title    : "App list is out-dated, refreshing apps..."
+          duration : 2000
+        @refreshApps noop
       callback err, response
-

   # #
   # MISC


ComputeHelpers: Add comment for test method

--- client/app/lib/providers/computehelpers.coffee
+++ client/app/lib/providers/computehelpers.coffee
@@ -82,6 +82,9 @@
               globals.userMachines.push machine

+  # This method is not used in any place, I put it here until
+  # we have a valid test suit for client side modular tests. ~ GG
+  #
   @infoTest = (count = 5)->

     {log} = kd


video/model: add a method to request camera change

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -103,6 +103,60 @@

   ###*
+   * Because we are alternating 2 different types of publishers that are
+   * `video-disabled` and `video-enabled` this method will handle that logic.
+   * Publishing mechanism is being controlled by here.
+   *
+   * This method's responsibilities are somehow mixed:
+   *
+   *   - It just enables (starts publishing) the video chat and calls the optional callback.
+   *   - If it's already being published,
+   *     - it first unpublishes the current publisher.
+   *     - then creates a new publisher that is either `video-enabled` or `video-enabled`
+   *       publishes that to the session.
+   *     - updates the publisher's video data.
+   *     - then updates the video state of the model.
+   *
+   * TODO: this method needs some after thinking. ~Umut
+   *
+   * @param {boolean} videoState
+   * @param {function=} callback - this will be callled after video state update is done.
+  ###
+  requestVideoStateChange: (videoState, callback) ->
+
+    # this options are required for publishing functions.
+    options = getCameraOptionFromState videoState
+
+    oldVideoState = @state.video
+
+    if @state.publishing
+      # if we are trying to change video state to same state, just return
+      return  if videoState is oldVideoState
+
+      # this method will just unpublish from the session, will not make any
+      # other update, or any mutation to recent state of model.
+      @_unpublishFromSession =>
+        @_createPublisher options, (err, videoData) =>
+          # first update KodingPublisher's video data
+          @publisher.setVideoData videoData
+          # set video state to given state so that other views can react to it.
+          @setVideoState videoState
+
+          callback?()
+
+    else
+      # no need to unpublish, and at this state, we want all the other handlers
+      # to work with `startPublishing` and so on. When the success callback is
+      # called, the publisher will be ready with the necessary video data, so
+      # no need to update its videoData here like we did before.
+      @enableVideo options,
+        error   : (err) -> console.error err
+        success : =>
+          @setVideoState videoState
+          callback?()
+
+
+  ###*
    * If it's already being published just sets state's audio to given state.
    * If not enables the video and then sets the state.
    *


Fix visualBell settings for terminal.

Fix visualBell settings for terminal.

--- client/ide/workspace/panes/settingspane.coffee
+++ client/ide/workspace/panes/settingspane.coffee
@@ -119,12 +119,12 @@
     @fontSize   = new KDSelectBox  selectOptions : IDE.settings.terminal.fontSizes
     @theme      = new KDSelectBox  selectOptions : IDE.settings.terminal.themes
     @scrollback = new KDSelectBox  selectOptions : IDE.settings.terminal.scrollback
-    @bell       = new KodingSwitch size: "tiny settings-on-off"
+    @visualBell = new KodingSwitch size: "tiny settings-on-off"

   getStorageInformation: -> { name: 'Terminal', version: '1.0.1' }

   getSettingKeys: ->
-    return [ 'bell', 'font', 'theme', 'fontSize', 'scrollback' ]
+    return [ 'visualBell', 'font', 'theme', 'fontSize', 'scrollback' ]

   pistachio: ->
     """
@@ -133,7 +133,7 @@

Font size   {{> @fontSize}}

Theme       {{> @theme}}

Scrollback  {{> @scrollback}}

-
Use visual bell                 {{> @bell}}

+
Use visual bell                 {{> @visualBell}}

     """


TerminalPane: Implemented makeEditable/makeReadOnly interface for terminal.

--- client/ide/workspace/panes/terminalpane.coffee
+++ client/ide/workspace/panes/terminalpane.coffee
@@ -100,3 +100,26 @@
       paneType : paneType
       session  : @remote?.session
       hash     : @hash
+
+
+  setEditMode: (state) ->
+
+    {terminal} = @webtermView
+    return  unless terminal
+
+    {cursor} = terminal
+    return  unless cursor
+
+    if state
+      @webtermView.terminal.isReadOnly = no
+      cursor.stopped = no
+      cursor.setBlinking yes
+    else
+      @webtermView.terminal.isReadOnly = yes
+      cursor.stopBlink()
+
+
+  makeEditable: -> @setEditMode yes
+
+
+  makeReadOnly: -> @setEditMode no


Fix bug in KiCad generator

Fix bug in KiCad generator

--- src/kicad-generator.coffee
+++ src/kicad-generator.coffee
@@ -131,11 +131,12 @@
       topPaste: 'F.Paste'
       topSilkscreen: 'F.SilkS'
       topAssembly: 'F.Fab'
-      bottomCopper: 'F.Cu'
-      bottomMask: 'F.Mask'
-      bottomPaste: 'F.Paste'
-      bottomSilkscreen: 'F.SilkS'
-      bottomAssembly: 'F.Fab'
+      intCopper: '*.Cu'
+      bottomCopper: 'B.Cu'
+      bottomMask: 'B.Mask'
+      bottomPaste: 'B.Paste'
+      bottomSilkscreen: 'B.SilkS'
+      bottomAssembly: 'B.Fab'
     obj.layer = obj.layer.map((a) => layers[a]).join(' ')
     if obj.kind is 'attribute'
       switch obj.name


KodingRouter: Removed an unnecessary if check.

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -81,8 +81,7 @@
         appManager = KD.getSingleton "appManager"

         appManager.once "AppCreated", (appInstance) ->
-          if app.name is appInstance.name
-            appManager.tell app, 'handleQuery', query
+          appManager.tell app, "handleQuery", query

         appManager.open app, (appInstance)=>
           appInstance.setOption "initialRoute", @getCurrentPath()


kodingrouter fix for activity permalinks

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -140,12 +140,14 @@
       @handleNotFound route

     if name
-      KD.remote.cacheable name or routeWithoutParams, (err, models)=>
+      KD.remote.cacheable name, (err, models)=>
         if models?
         then onSuccess models
         else onError err
     else
-      KD.remote.api.JName.one {name: routeWithoutParams.slice(1)}, (err, jName)=>
+      # TEMP FIX: getting rid of the leading slash for the post slugs
+      slashlessSlug = routeWithoutParams.slice(1)
+      KD.remote.api.JName.one { name: slashlessSlug }, (err, jName)=>
         if err then onError err
         else if jName?
           models = []


Made barrier interruptable

Made barrier interruptable

--- async/Barrier.coffee
+++ async/Barrier.coffee
@@ -102,6 +102,9 @@
     * @chainable
     *###
     _invokeNextFunction: (data, idx) ->
+        if @_isStopped
+            return @
+
         func = data.func or data[0]
         scope = data.scope or data[1]
         params = data.params or data[2]
@@ -112,6 +115,11 @@
                     try
                         return func.apply(scope, params)
                     catch error
+                        console.error "================================================================="
+                        console.error "App.Barrier::_invokeNextFunction: Given function (at index #{idx}) threw an Error!"
+                        console.warn "Here is the data:", data
+                        console.warn "Here is the error:", error
+                        console.error "================================================================="
                         return error
             }
             {
@@ -196,3 +204,22 @@
             @_endCallback   = endCallback

         return @
+
+    stop: (execCallbacks = true) ->
+        @_isStopped = true
+
+        sequence.stop(execCallbacks) for sequence in @_sequences
+
+        if execCallbacks
+            @_endCallback?()
+            @_execDoneCallbacks()
+        return @
+
+    interrupt: () ->
+        return @stop(false)
+
+    resume: () ->
+        @_isStopped = false
+        # TODO: pass correct params here (prev res....)
+        @_invokeNextFunction()
+        return @


Updated generation of MessageId and QueueId based on uuid

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -20,17 +20,18 @@

 # Queue Id (16-char upper case random string) generator
 #
-exports.generateQueueId = (length=16) ->
+exports.generateQueueId = () ->
     s = ''
-    s += Math.random().toString(36).substr(2) while s.length < length
-    (s.substr 0, length).toUpperCase()
+    s += Math.random().toString(36).substr(2).substr 0, 6
+    s += '-'
+    s += Math.random().toString(36).substr(2).substr 0, 6
+    s += '-'
+    s += Math.random().toString(36).substr(2).substr 0, 2
+    s.toUpperCase() # Exim format: XXXXXX-YYYYYY-ZZ

 # Generate an almost unique Message-ID (32-char timestamp+random string)
 #
 exports.generateMsgId = (senderEmail) ->
-    length = 32
-    d = new Date()
-    s = '' + d.getTime() + '-'
-    s += Math.random().toString(36).substr(2) while s.length < length
-    (s.substr 0, length) + '@' + senderEmail.replace /^[a-z0-9\._-]+@/i, ''
+    uuid = require 'node-uuid'
+    uuid.v1() + '@' + senderEmail.replace /^[a-z0-9\._-]+@/i, ''


Log output add timestamp

Log output add timestamp

--- src/module/log/log.coffee
+++ src/module/log/log.coffee
@@ -1,13 +1,18 @@
+require("./../util/date.js")
+
 isDebugEnable = true

 debug = (msg)->
-	console.log("[DEBUG] #{msg}") if isDebugEnable
+	console.log("[#{_getTime()}][DEBUG] #{msg}") if isDebugEnable

 info = (msg)->
-	console.log("[INFO] #{msg}")
+	console.log("[#{_getTime()}][INFO] #{msg}")

 error = (msg)->
-	console.error("[ERROR] #{msg}")
+	console.error("[#{_getTime()}][ERROR] #{msg}")
+
+_getTime = ->
+	new Date().format("yyyy-MM-dd HH:mm:ss.S")

 exports.debug = debug
 exports.info = info


atom: change project home path

atom: change project home path

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -13,3 +13,4 @@
     'exception-reporting'
     'metrics'
   ]
+  'projectHome': '~/Projects'


asdfasdfasdf

asdfasdfasdf

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,40 +1,22 @@
 "*":
+  "activate-power-mode":
+    plugins:
+      playAudio: true
+      screenShake: true
+    screenShake: {}
+  "autocomplete-plus":
+    enableAutoActivation: false
   blame:
-    dateFormat: "MM/DD/YY"
+    dateFormat: "DD/MM/YY"
   "bracket-matcher":
     autocompleteBrackets: false
     autocompleteSmartQuotes: false
   core:
-    customFileTypes:
-      "source.ini": [
-        ".buckconfig"
-        ".flowconfig"
-        ".hgrc"
-      ]
-      "source.json": [
-        ".arcconfig"
-        "BUCK.autodeps"
-      ]
-      "source.python": [
-        "BUCK"
-      ]
-    disabledPackages: [
-      "haskell-grammar"
-      "autocomplete-haskell"
-      "atom-reason"
-      "atom-reason-loader"
-    ]
-    packagesWithKeymapsDisabled: [
-      "blame"
-    ]
+    disabledPackages: []
     telemetryConsent: "no"
-    themes: [
-      "one-light-ui"
-      "one-light-syntax"
-    ]
   editor:
     fontFamily: "\"Fira Code\""
-    fontSize: 12
+    fontSize: 16
     invisibles: {}
     preferredLineLength: 120
     showIndentGuide: true
@@ -42,54 +24,15 @@
     userId: "2a02be6a-d91a-998e-0abe-bbe3d07e3dd4"
   "git-plus": {}
   "haskell-ghc-mod": {}
-  "ide-purescript": {}
+  "ide-purescript":
+    addPscPackageSources: true
   "language-babel":
     createTranspiledCode: true
-  "language-elm":
-    elmOraclePath: "/Users/jwoo/.npm/bin/elm-oracle"
   linter: {}
-  nuclide:
-    installRecommendedPackages: true
-    "nuclide-arcanist": {}
-    "nuclide-blame-provider-hg": {}
-    "nuclide-buck-files": {}
-    "nuclide-diagnostics-store":
-      consumeLegacyLinters: false
-    "nuclide-flow":
-      diagnosticsOnFly: true
-      enableStructuredTypeHints: true
-    "nuclide-format-js": {}
-    "nuclide-hack": {}
-    "nuclide-health": {}
-    "nuclide-home":
-      showHome: false
-    "nuclide-ocaml": {}
-    "nuclide-output": {}
-  "prettier-atom":
-    prettierOptions:
-      semi: false
-      singleQuote: true
-  react:
-    enabledForAllJavascriptFiles: true
   "tool-bar": {}
   welcome:
     showOnStartup: false
 ".ansi.text":
-  editor:
-    preferredLineLength: 120
-".elm.source":
-  editor:
-    preferredLineLength: 120
-".hack.html.text":
-  editor:
-    preferredLineLength: 120
-".hackfragment.source":
-  editor:
-    preferredLineLength: 120
-".hg-commit.text":
-  editor:
-    preferredLineLength: 120
-".js.jsx.source":
   editor:
     preferredLineLength: 120
 ".psci.purescript.source":
@@ -98,6 +41,3 @@
 ".purescript.source":
   editor:
     preferredLineLength: 120
-".source.swift":
-  editor:
-    preferredLineLength: 120


Update Atom syntax theme

Update Atom syntax theme

--- atom/symlink.config.cson
+++ atom/symlink.config.cson
@@ -4,7 +4,7 @@
   core:
     themes: [
       "one-dark-ui"
-      "tomorrow-night-eighties-syntax"
+      "hybrid-next-syntax"
     ]
   editor:
     fontFamily: "\"Fira Code\""


partial content headers

partial content headers

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -149,7 +149,8 @@
             fsize = (id3?.length||0) + (predata?.length||0) + size
             fstart = 0
             fend = fsize - 1
-
+            console.log "fsize, fstart, fend is", fsize, fstart, fend
+
             console.log "id3 length is ", id3?.length||0

             @listeners++
@@ -159,22 +160,28 @@
             rangeRequest = false

             # Is the range header a string?
-            rangeStr = req.headers.range
-
-            # Is the range header formatted properly?
+            rangeStr = if _u.isString(req.headers.range) then req.headers.range else undefined
+            console.log "rangeStr is", rangeStr
+
+            # Get the requested start and end
             if _u.isString rangeStr
                 rangeVals = rangeStr.match(/bytes=(.+)-(.+)?/)
-
-            # If both of the above are true, then check if the start and end values
+                requestStart = rangeVals[1]
+                requestEnd = rangeVals[2]
+
+            console.log "requested start, end is", requestStart, requestEnd
+
+            # If rangeVals has two numbers, then check if they
             # are valid and within the file size
-            if !_u.isUndefined rangeStr and !_u.isNull rangeVals
-                rangeStart = _u.isNumber(rangeVals[1]) && rangeVals[1] >= fstart && rangeVals[1] < fend ? rangeVals[1] - 0 : fstart
-                rangeEnd = _u.isNumber(rangeVals[2]) && rangeVals[2] > fstart && rangeVals[2] <= fend ? rangeVals[2] - 0 : fend
+            if _u.isNumber requestStart and _u.isNumber requestEnd
+                rangeStart = if (requestStart >= fstart && requestStart < fend) then requestStart else fstart
+                rangeEnd = if (requestEnd > fstart && requestEnd <= fend) then requestEnd else fend
+
+                # Request is for a range
                 rangeRequest = true

-
             # What is the actual length of content being sent back?
-            length = rangeRequest ? (rangeEnd - rangeStart) + 1 : fsize
+            length = if rangeRequest then (rangeEnd - rangeStart + 1) else fsize

             # send out headers
             headers =
@@ -191,6 +198,7 @@

             console.log "final size should be ", fsize
             console.log "request method is ", req.method
+            console.log "headers are", headers

             res.writeHead 200, headers


fix selection

fix selection

--- src/selection.coffee
+++ src/selection.coffee
@@ -24,7 +24,7 @@

   constructor: (@doc) ->
     @root = @doc.domNode
-    @range = new Range(0, 0)
+    @lastRange = @savedRange = new Range(1, 1)  # savedRange is never null
     ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave'].forEach((eventName) =>
       @root.addEventListener(eventName, =>
         this.update()  # Do not pass event handler params
@@ -38,8 +38,7 @@
   focus: ->
     return if this.checkFocus()
     @root.focus()
-    if !this.getNativeRange() && @range?
-      this.setRange(@range)
+    this.setRange(@savedRange)

   getBounds: (index) ->
     pos = @doc.findPath(index).pop()
@@ -153,7 +152,7 @@
       # setRange(null) will fail to blur in IE10/11 on Travis+SauceLabs (but not local VMs)
       document.body.focus() if dom.isIE(11) and !dom.isIE(9)

-  setRange: (range, source) ->
+  setRange: (range) ->
     convert = (index) =>
       pos = @doc.findPath(index).pop() # TODO inclusive
       if pos.blot instanceof Parchment.Embed
@@ -170,14 +169,15 @@
         this.setNativeRange(startNode, startOffset, endNode, endOffset)
     else
       this.setNativeRange(null)
-    this.update(source)
+    this.update()

   update: (args...) ->
-    oldRange = @range
-    @range = this.getRange()
-    return if oldRange == @range
-    if oldRange == null || @range == null || oldRange.start != @range.start || oldRange.end != @range.end
-      this.onUpdate(@range, args...)
+    oldRange = @lastRange
+    @lastRange = this.getRange()
+    @savedRange = @lastRange if @lastRange?
+    return if oldRange == @lastRange
+    if oldRange == null || @lastRange == null || oldRange.start != @lastRange.start || oldRange.end != @lastRange.end
+      this.onUpdate(@lastRange, args...)

 module.exports = Selection


update test for constructor

update test for constructor

--- src/test/core.coffee
+++ src/test/core.coffee
@@ -1,20 +1,87 @@
 (->
 	describe 'Trocha JS Routes List engine', ->
 		describe 'Route creation', ->
+			describe 'Route creation params', ->
+				it 'should create routes without name printing', ->
+					r = trocha routes: simple_route_without_name: $hide: true
+					assert r.simple_route_without_name.path(), ''
+
+				it 'should create routes with method', ->
+					r = trocha routes: simple_route_with_method: $method: trocha.POST
+					assert r.simple_route_with_method.$method, 'POST'
+
+				it 'should create routes with id', ->
+					r = trocha routes: simple_id_route: $id: 'simple_id'
+					assert r.simple_id_route.path(), '/simple_id_route/:simple_id'
+
+				it 'should create routes with hiden parent id', ->
+					r = trocha routes: simple_id_route:
+						$id: 'simple_id'
+						without_parent_id: $id: false
+					assert r.simple_id_route.without_parent_id.path(), '/simple_id_route/without_parent_id'
+
+				it 'should create routes with just id', ->
+					r = trocha routes: simple_route_with_just_id:
+						$justId: true
+						$id: 'simple_id'
+					assert r.simple_route_with_just_id.path(), '/:simple_id'
+
+				# will fail
+				# it 'should create routes with after id', ->
+				# 	r = trocha routes: simple_route_with_after_id:
+				# 		$afterId: true
+				# 		$id: 'simple_id'
+				# 	assert r.simple_route_with_after_id.path(), '/:simple_id/simple_route_with_after_id'
+
+				it 'should create routes with hiden parents id and child id', ->
+					r = trocha routes: simple_id_route:
+						$id: 'simple_id'
+						hide_parent_id:
+							$id: 'child_id'
+							$parentId: false
+						id_2:
+							$id: 'child_id'
+							$parentId: false
+							hide_parents_id:
+								child_id: false
+							# overide_id:
+							# 	simple_id: true # does not compute
+							hide_glitch:
+								$id: '$hide'
+								# simple_id: ':$hide' # does not compute
+								l:
+									$hide: false # This will be ignore in constructor
+					assert r.simple_id_route.hide_parent_id.path(), '/simple_id_route/hide_parent_id/:child_id'
+					assert r.simple_id_route.id_2.hide_parents_id.path(), '/simple_id_route/id_2/hide_parents_id'
+					#check Override
+					assert r.simple_id_route.hide_parent_id.path(simple_id: 'asd'), '/simple_id_route/asd/hide_parent_id/:child_id'
+					assert r.simple_id_route.id_2.hide_parents_id.path(child_id: 'asd'), '/simple_id_route/id_2/asd/hide_parents_id'
+					# assert r.simple_id_route.id_2.overide_id.path(), '/simple_id_route/asd/id_2/:child_id/overide_id'
+					assert r.simple_id_route.id_2.hide_glitch.l.path(), '/simple_id_route/id_2/:child_id/hide_glitch/:$hide/l'
+
+				# will fail
+				# it 'should create routes with postfix', ->
+				# 	r = trocha routes: simple_route_post:
+				# 		$post: 'the_post'
+				# 	assert r.simple_route_post.path(), '/simple_route_postthe_post'
+
+					# assert r.simple_id_route.path(simple_id: 'the_simple_id'), '/simple_id_route/the_simple_id'
+					# assert r.simple_id_route.path(simple_id: false), '/simple_id_route'
+
 			it 'should create routes via JSON Constructor', ->
-				r = trocha {
+				r = trocha
 					routes:
-						simple_route :{}
+						simple_route: {}
 						simple_scope: {$type: trocha.SCOPE}
 						simple_alias: "simple_alias"
 						simple_resource:
 							$type: trocha.RESOURCE
 							$id: "simple_id" #resource must have ID
-				}
+
 				assert r.simple_route, {}
 				assert r.simple_scope, {}
 				assert r.simple_resource, {}
-				#assert r.simple_alias, {} #will fail
+				# assert r.simple_alias, {} # will fail
 				assert r.simple_alias, "simple_alias" # @TODO remove me after alias fix
 			it 'should create routes via post init functions', ->
 				r = trocha()


Update mocked URL to match fetching from cnx-archive

--- src/test/mock.coffee
+++ src/test/mock.coffee
@@ -9,4 +9,4 @@
     service = settings.url.match(/\/contents\/(.*)$/)

     if service
-      return {proxy: '/test/data/' + service[1] + '.json'}
+      return {proxy: 'data/' + service[1] + '.json'}


Remove debug statement

Remove debug statement

--- fabric/fmc-webui/src/main/webapp/app/controllers/agents_page.coffee
+++ fabric/fmc-webui/src/main/webapp/app/controllers/agents_page.coffee
@@ -110,7 +110,6 @@
       @model.unbind()

     toggle_class: ->
-      console.error("hi!")
       @provision.toggleClass("yellow-dot.png")
       @provision.toggleClass("pending.gif")


bug fix in isVisibleOnClient(window).

bug fix in isVisibleOnClient(window).

--- src/shared/utils.coffee
+++ src/shared/utils.coffee
@@ -15,7 +15,10 @@
     return false if !node || !topDoc
     doc = node._ownerDocument
     if !doc
-        throw new Error("Missing ownerDocument")
+        if node.nodeType == 9
+            doc = node
+        else
+            return false
     if (!node._attachedToDocument && node.nodeType != 9) ||
          node.nodeType == 11
         return false


KDContentEditable: tab navigation option

--- components/inputs/KDContentEditableView.coffee
+++ components/inputs/KDContentEditableView.coffee
@@ -1,10 +1,11 @@
 class KDContentEditableView extends KDView
   constructor: (options = {}, data) ->
-    options.cssClass      = KD.utils.curry "kdcontenteditableview", options.cssClass
-    options.bind          = KD.utils.curry "click input keydown paste drop", options.bind
-    options.type        or= "text"
-    options.multiline    ?= off
-    options.placeholder or= ""
+    options.cssClass         = KD.utils.curry "kdcontenteditableview", options.cssClass
+    options.bind             = KD.utils.curry "click input keydown paste drop", options.bind
+    options.type           or= "text"
+    options.multiline       ?= off
+    options.placeholder    or= ""
+    options.tabNavigation   ?= no

     super options, data

@@ -85,13 +86,15 @@
     @emit "ValueChanged", event

   keyDown: (event) =>
+    {tabNavigation, multiline, validate} = @getOptions()
+
     switch event.which
-      when 9, 13
-        event.preventDefault()
-        @utils.stopDOMEvent event
+      when 9  then @utils.stopDOMEvent event  if tabNavigation
+      when 13 then @utils.stopDOMEvent event

     switch event.which
       when 9 # Tab key
+        break  unless tabNavigation
         @blur()
         if event.shiftKey then @emit "PreviousTabStop"
         else @emit "NextTabStop"


deserialize on originalInput change

deserialize on originalInput change

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -210,8 +210,8 @@

     true

-  deserializeInput: ->
-    @_deserialize(@$originalInput.val())
+  deserializeFromInput: =>
+    @$editable.html @_deserialize(@$originalInput.val())

   _deserialize: (text)->
     result = []
@@ -268,7 +268,7 @@
     @$input = $("<input type='hidden' name='#{@$originalInput.attr('name')}'/>")
     @$input.val(@$originalInput.val())
     @$editable.addClass(@$originalInput.attr("class"))
-    @$editable.html(@deserializeInput()) unless @$originalInput.val() == ''
+    @deserializeFromInput() unless @$originalInput.val() == ''
     if placeholder = @$originalInput.attr('placeholder')
       @$placeholder = $("#{placeholder}").appendTo(@$editable)

@@ -284,6 +284,8 @@
     @$editable.bind 'keyup', @handleKeyup
     @$editable.bind 'paste', @handlePaste
     @$editable.bind 'focus blur', @handlePlaceholder
+
+    @$originalInput.bind 'change', @deserializeFromInput
     if form = @$originalInput.get(0).form
       $(form).on('reset', @handleReset)
       $(form).on('reset', @handlePlaceholder)


Associate commands and help strings, update them automatically

--- code/li.coffee
+++ code/li.coffee
@@ -5,103 +5,116 @@

 Linode = (require 'linode').Linode

+command = {}
+
 # args[1] is the name of the command, e.g. start
 # args[2..] are the command args
-help = (args) ->
-  help =
-  """
-  Usage: li <command> [OPTIONS]
-    Commands:
-      list                        List instances
-      create <config-name>        Create a instance by config
-      destroy <instance-name>     Destroy an instance
-
-      start <instance-name>       Start an instance
-      stop <instance-name>        Stop an instance
-      restart <instance-name>     Restart an instance
-      deploy <instance-name>      Run deployment hooks on an instance [OBSOLESCENT]
-      sh <instance-name> <cmd>    Execute a command on an instance
-      ssh [user@]<instance-name>  Log in to instance using ssh
-
-  """
-  process.stdout.write help
+command.help =
+  help: "help                        Show this help"
+  run: (args) ->
+    cmdhelp = (command[cmd].help for cmd of command).join('\n    ')
+    help =
+    """
+    Usage: li <command> [OPTIONS]
+      Commands:
+
+    """ + cmdhelp
+    process.stdout.write help

 # args[2] is the name of the instance to start
-start = (args) ->
-  Linode.get args[2], (instance) ->
-    instance.start ->
-      process.stdout.write('started\n')
+command.start =
+  help: "start <instance-name>       Start an instance"
+  run: (args) ->
+    Linode.get args[2], (instance) ->
+      instance.start ->
+        process.stdout.write('started\n')

 # args[2] is the name of the instance to stop
-stop = (args) ->
-  Linode.get args[2], (instance) ->
-    instance.stop ->
-      process.stdout.write('stopped\n')
+command.stop =
+  help: "stop <instance-name>        Stop an instance"
+  run: (args) ->
+    Linode.get args[2], (instance) ->
+      instance.stop ->
+        process.stdout.write('stopped\n')

 # args[2] is the name of the instance to restart
-restart = (args) ->
-  Linode.get args[2], (instance) ->
-    instance.restart ->
-      process.stdout.write('restarted\n')
+command.restart =
+  help: "restart <instance-name>     Restart an instance"
+  run: restart = (args) ->
+    Linode.get args[2], (instance) ->
+      instance.restart ->
+        process.stdout.write('restarted\n')

 # Takes a config name, and creates an instance based on that config.
 # args[2] is the name of the config
-create = (args) ->
-  process.stdout.write('Creating...\n')
-  Linode.create args[2], (dummy_, item) ->
-    Linode.get item.name, (item) ->
-      process.stdout.write('Created!\n')
-      log_one_item item
+command.create =
+  help: "create <config-name>        Create a instance by config"
+  run: (args) ->
+    process.stdout.write('Creating...\n')
+    Linode.create args[2], (dummy_, item) ->
+      Linode.get item.name, (item) ->
+        process.stdout.write('Created!\n')
+        log_one_item item

 # args[2] is the name of the instance to destroy
-destroy = (args) ->
-  process.stdout.write "Destroying #{args[2]}...\n"
-  Linode.destroy args[2], ->
-    process.stdout.write "Destroyed\n"
+command.destroy =
+  help: "destroy <instance-name>     Destroy an instance"
+  run: (args) ->
+    process.stdout.write "Destroying #{args[2]}...\n"
+    Linode.destroy args[2], ->
+      process.stdout.write "Destroyed\n"

 # Executes a command on the instance (with ssh)
 # args[2] is the name of the instance;
 # args[3] and onwards are the command to run.
-sh = (args) ->
-  Linode.get args[2], (instance) ->
-    callback = (code) ->
-      console.log "Exit code: #{code}"
-      process.exit code
+command.sh =
+  help: "sh <instance-name> <cmd>    Execute a command on an instance"
+  run: (args) ->
+    Linode.get args[2], (instance) ->
+      callback = (code) ->
+        console.log "Exit code: #{code}"
+        process.exit code

-    instance.sh (args[3..].join ' '), callback
+      instance.sh (args[3..].join ' '), callback

 # ssh into an instance.
 # args[2] is the name of the instance.
-ssh = (args) ->
-  name = args[2]
-  l = name.split '@'
-  if l.length == 2
-    instancename = l[1]
-    username = l[0]
-  else
-    instancename = l[0]
-    username = 'root'
-  Linode.get instancename, (instance) ->
-    instance.ssh username
+command.ssh =
+  help: "ssh [user@]<instance-name>  Log in to instance using ssh"
+  run: (args) ->
+    name = args[2]
+    l = name.split '@'
+    if l.length == 2
+      instancename = l[1]
+      username = l[0]
+    else
+      instancename = l[0]
+      username = 'root'
+    Linode.get instancename, (instance) ->
+      instance.ssh username

 # Deploys an instance by running all its hooks
 # args[2] is the instance
-deploy = (args) ->
-  Linode.get args[2], (instance) ->
-    callback = (code) ->
-      if code is null then code = 0
-      console.log "Exit code: #{code}"
-      process.exit code
-    instance.run_hooks callback
+command.deploy =
+  help: "deploy <instance-name>      Run deployment hooks on an instance [OBSOLESCENT]"
+  run: (args) ->
+    Linode.get args[2], (instance) ->
+      callback = (code) ->
+        if code is null then code = 0
+        console.log "Exit code: #{code}"
+        process.exit code
+      instance.run_hooks callback

 # Displays a list of instances
-list = (args) ->
-  process.stdout.write "Listing instances...\n"
-  Linode.list (err, list) ->
-    if err?
-      console.log err
-    else
-      log_one_item item for item in list
+command.list =
+  help: "list                        List instances"
+  run: (args) ->
+    process.stdout.write "Listing instances...\n"
+    Linode.list (err, list) ->
+      if err?
+        console.log err
+      else
+        log_one_item item for item in list

 log_one_item = (item) ->
   state = friendly_state item.state
@@ -122,22 +135,24 @@
     '2' : 'Terminated'           # Powered Off
     '3' : 'Rebooting'            # Shutting Down

-jobs = (args) ->
-  # Linode lists jobs per instance, so we have to iterate over
-  # each of our instances.
-  Linode.list (err, instances) ->
-    if err?
-      console.log err
-    else
-      async.forEachSeries instances, (instance, callback) ->
-        console.log "Jobs for #{instance.name} #{instance.id}"
-        Linode.jobs instance, (err, jobs) ->
-          if err?
-            console.log err
-          else
-            async.forEachSeries jobs, ((job, cb) ->
-              log_one_job job
-              cb()), callback
+command.jobs =
+  help: "jobs                        List all Linode jobs"
+  run: (args) ->
+    # Linode lists jobs per instance, so we have to iterate over
+    # each of our instances.
+    Linode.list (err, instances) ->
+      if err?
+        console.log err
+      else
+        async.forEachSeries instances, (instance, callback) ->
+          console.log "Jobs for #{instance.name} #{instance.id}"
+          Linode.jobs instance, (err, jobs) ->
+            if err?
+              console.log err
+            else
+              async.forEachSeries jobs, ((job, cb) ->
+                log_one_job job
+                cb()), callback

 log_one_job = (job) ->
   console.log job
@@ -149,20 +164,13 @@
   if not args?
     args = process.argv[1..]

-  switch args[1]
-    when 'help' then help(args)
-    when 'start' then start(args)
-    when 'stop' then stop(args)
-    when 'restart' then restart(args)
-    when 'create' then create(args)
-    when 'destroy' then destroy(args)
-    when 'list' then list(args)
-    when 'sh' then sh(args)
-    when 'ssh' then ssh(args)
-    when 'deploy' then deploy(args)
-    when 'jobs' then jobs(args)
-    when undefined then help(args)
-    else process.stderr.write("Try li help for help, not #{args}\n")
+  cmd_name = args[1]
+  if cmd_name of command
+    command[cmd_name].run(args)
+  else if not cmd_name?
+    command['help'].run(args)
+  else
+    process.stderr.write("Try li help for help, not #{args}\n")

 if _s.endsWith process.argv[1], 'li.coffee'
   exports.main()


remove sourcemaps

remove sourcemaps

--- brunch-config.coffee
+++ brunch-config.coffee
@@ -3,7 +3,4 @@
     javascripts: joinTo: 'emotes.js'
     stylesheets: joinTo: 'emotes.css'
   npm:
-    enabled: true
-  overrides:
-    production:
-      sourceMaps: true+    enabled: true


fixtypo

fixtypo

--- core/router/user.coffee
+++ core/router/user.coffee
@@ -59,8 +59,6 @@
         unless user
           return res.json 400, error: 'auth_failed'

-        console.log(user.matchPasswd data.passwd);
-
         unless user.matchPasswd data.passwd
           return res.json 400, error: 'auth_failed'


Ensure @get('node') is called when adding a subview.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -42,6 +42,7 @@
     super

   _addSubview: (subview) ->
+    @get('node') if not @node
     subview.removeFromSuperview()

     subview.set('superview', this)
@@ -58,7 +59,7 @@

     else
       parentNode = subview.parentNode
-      parentNode = Batman.DOM.querySelector(@get('node'), parentNode) if typeof parentNode is 'string'
+      parentNode = Batman.DOM.querySelector(@node, parentNode) if typeof parentNode is 'string'
       parentNode = @node if not parentNode

       subview.addToParentNode(parentNode)


remove white space

remove white space

--- src/scripts/gauges.coffee
+++ src/scripts/gauges.coffee
@@ -114,14 +114,3 @@

       when "data"
           gauges.getViewsForDate data.day, handler
-
-
-
-
-
-
-
-
-
-
-


:lipstick:

:lipstick:

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -60,10 +60,8 @@

   getPackageNamesWithActiveGrammars: ->
     packageNames = []
-    addGrammar = (grammar={}) ->
-      {includedGrammarScopes, packageName} = grammar
+    addGrammar = ({includedGrammarScopes, packageName}={}) ->
       return unless packageName
-
       # Prevent cycles
       return if packageNames.indexOf(packageName) isnt -1


Do not treat packages in unusual locations as bundled despite name

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -426,7 +426,7 @@
       pack
     else if packagePath = @resolvePackagePath(nameOrPath)
       name = path.basename(nameOrPath)
-      @loadAvailablePackage({name, path: packagePath, isBundled: @isBundledPackage(name)})
+      @loadAvailablePackage({name, path: packagePath, isBundled: @isBundledPackagePath(name)})
     else
       console.warn "Could not resolve '#{nameOrPath}' to a package path"
       null


Update command line app to use new item API

--- command.coffee
+++ command.coffee
@@ -65,7 +65,7 @@
       item = results[0]
       # console.log JSON.stringify results[0].overview, null, 2
       # uuid = results[0].uuid
-      details = item.decryptDetails(keychain.master)
+      details = item.unlock('details')
       displayItem(item, details)
     else
       console.log 'Nothing found... Hint: Enter `list` to display all'
@@ -107,4 +107,4 @@
       fs.writeFileSync "#{filepath}/1Password.cloudkeychain/default/profile.js", keychain.exportProfile()
       createItem keychain, (bands) ->
         for filename, band of bands
-          fs.writeFileSync "#{filepath}/1Password.cloudkeychain/default/#{filename}", band+          fs.writeFileSync "#{filepath}/1Password.cloudkeychain/default/#{filename}", band


Onlined a function call

Onlined a function call

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -26,8 +26,7 @@
     @$('.fact-wheel').on 'click.whatsyouropinion', =>
       @close()

-    $(window).on 'resize.whatsyouropinion', =>
-      @showTooltip();
+    $(window).on 'resize.whatsyouropinion', => @showTooltip();

     @showTooltip()


made it possible to set an opinion when testing without opening from a side

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -74,7 +74,7 @@
     persistentWheelView.render()

     persistentWheelView.on 'opinionSet', ->
-      parent.remote.trigger('opinionSet')
+      parent?.remote?.trigger('opinionSet')

   createCancelEvent: ->
     @$('#cancel').on 'click', (e)->


simplified rendering action

simplified rendering action

--- core/app/backbone/views/activities_view.coffee
+++ core/app/backbone/views/activities_view.coffee
@@ -14,8 +14,7 @@

     this.childViews = []

-  render: ->
-    super arguments
+  onRender: ->
     this.renderChildren()

   renderChildren: ->


PrivateMessage: Add initPrivateMessage to requests

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -197,8 +197,15 @@
   url = "/account/#{data.accountId}/channels"
   get url, data, callback

+initPrivateMessage = (data, callback)->
+  if not data.body or not data.recipients or data.recipients.length < 1
+    return callback { message: "Request is not valid"}
+
+  url = "/privatemessage/init"
+  post url, data, callback
+
 sendPrivateMessage = (data, callback)->
-  if not data.body or not data.recipients or data.recipients.length < 1
+  if not data.body or not data.channelId
     return callback { message: "Request is not valid"}

   url = "/privatemessage/send"
@@ -347,6 +354,7 @@
   fetchProfileFeed
   searchTopics
   fetchPrivateMessages
+  initPrivateMessage
   sendPrivateMessage
   fetchFollowedChannels
   listParticipants


Rename "defaults" to "globals", because they function as such.

--- src/project_creator.coffee
+++ src/project_creator.coffee
@@ -6,7 +6,7 @@

 module.exports = class ProjectCreator
   constructor: (@config = {}, @resolveValues) ->
-    @config.defaults = {} unless @config.defaults
+    @config.globals = {} unless @config.globals

   createProject: (targetDir, templateName, next) ->
     @findTemplateDir templateName, (err, templateDir) =>
@@ -19,7 +19,7 @@
           # @log "Collecting placeholder names in #{templateDir}."
           return collectPlacholderNames paths, (err, varNames) =>
             return next err if err
-            values = makeValues varNames, @config.defaults, targetDir, templateName
+            values = makeValues varNames, @config.globals, targetDir, templateName
             missingVarNames = (name for name, value of values when value is null)
             @resolveValues missingVarNames, (err, resolvedValues) =>
               return next err if err
@@ -62,10 +62,10 @@
     @log "================="
     @log easytable.printObj sortValues values

-makeValues = (varNames, defaults, targetDir, templateName) ->
+makeValues = (varNames, globals, targetDir, templateName) ->
   values = {}
   for varName in varNames
-    values[varName] = if defaults[varName] then defaults[varName] else null
+    values[varName] = if globals[varName] then globals[varName] else null
   values["target-dir"] = targetDir
   values["template-name"] = templateName
   values["current-year"] = new Date().getFullYear()


ComputeUtils: reviveClient: revert default to koding line [fixes #77817654]

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -49,8 +49,6 @@
   return callback null  unless revive

   { connection: { delegate:account }, context: { group } } = client
-
-  group = "koding"  if group is "guests"

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


minor

minor

--- core/KD.coffee
+++ core/KD.coffee
@@ -1,3 +1,7 @@
+# ios error handling
+# if location.hostname is "localhost"
+# window.onerror = (desc,page,line,chr)=> alert "Line: #{line}, desc: #{desc}, chr:#{chr}, page:#{page}"
+
 Function::bind or= do ->
   {slice} = []
   (context)->


Router: removed app labels implemented multiple routes support

--- core/KD.coffee
+++ core/KD.coffee
@@ -144,18 +144,19 @@
     options.navItem      or= {}           # <Object{title: string, eventName: string, shortcut: string}>
     options.labels       or= []           # <Array<string>> list of labels to use as app name
     options.version       ?= "1.0"        # <string> version
-    options.route        or= {}           # <string> or <Object{slug: string, handler: function}>
-
-    registerRoute = (route)=>
+    options.route        or= null         # <string> or <Object{slug: string, handler: function}>
+    options.routes       or= null         # <string> or <Object{slug: string, handler: function}>
+
+    registerRoute = (route, handler)=>
       slug        = if "string" is typeof route then route else route.slug
       route       =
         slug      : slug or '/'
-        handler   : route.handler or null
+        handler   : handler or route.handler or null
+

       if route.slug isnt '/'

         {slug, handler} = route
-
         cb = (router)->
           handler or= ({params:{name}, query})->
             router.openSection options.name, name, query
@@ -165,8 +166,10 @@
         then @utils.defer -> cb KD.getSingleton('router')
         else KodingRouter.on 'RouterReady', cb

-    options.route = [options.route]  unless Array.isArray options.route
-    registerRoute route for route in options.route
+    if   options.route
+    then registerRoute options.route
+    else if options.routes
+    then registerRoute route, handler for own route, handler of options.routes

     if options.navItem?.order
       @registerNavItem options.navItem
@@ -177,33 +180,23 @@
       writable      : no
       value         : { fn, options }

-    if options.labels.length > 0
-      @setAppLabels options.name, options.labels
-
-  setAppLabels       : (name, labels)-> @appLabels[name] = labels
-
-  getAppName         : (name)->
-    for own app, labels of @appLabels
-      return app  if name in labels or name is app
-    return name
-
   registerNavItem    : (itemData)-> @navItems.push itemData

   getNavItems        : -> @navItems.sort (a, b)-> a.order - b.order

   setNavItems        : (navItems)-> @navItems = navItems.sort (a, b)-> a.order - b.order

-  unregisterAppClass :(name)-> delete KD.appClasses[@getAppName name]
-
-  getAppClass        :(name)-> KD.appClasses[@getAppName name]?.fn or null
-
-  getAppOptions      :(name)-> KD.appClasses[@getAppName name]?.options or null
-
-  getAppScript       :(name)-> @appScripts[@getAppName name] or null
-
-  registerAppScript  :(name, script)-> @appScripts[@getAppName name] = script
-
-  unregisterAppScript:(name)-> delete @appScripts[@getAppName name]
+  unregisterAppClass :(name)-> delete KD.appClasses[name]
+
+  getAppClass        :(name)-> KD.appClasses[name]?.fn or null
+
+  getAppOptions      :(name)-> KD.appClasses[name]?.options or null
+
+  getAppScript       :(name)-> @appScripts[name] or null
+
+  registerAppScript  :(name, script)-> @appScripts[name] = script
+
+  unregisterAppScript:(name)-> delete @appScripts[name]

   resetAppScripts    :-> @appScripts = {}


hub/client: removing unnecessary "- 0" from date comparisons

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -1202,7 +1202,7 @@
                         cb         : (err, when_sent) =>
                             if err
                                 cb(err)
-                            else if when_sent - 0 >= misc.days_ago(7) - 0 # successfully sent < one week ago -- don't again
+                            else if when_sent >= misc.days_ago(7)   # successfully sent < one week ago -- don't again
                                 locals.done = true
                                 cb()
                             else
@@ -1348,7 +1348,7 @@
                                 cb         : (err, when_sent) =>
                                     if err
                                         cb(err)
-                                    else if when_sent - 0 >= misc.days_ago(7) - 0 # successfully sent < one week ago -- don't again
+                                    else if when_sent >= misc.days_ago(7)   # successfully sent < one week ago -- don't again
                                         done = true
                                         cb()
                                     else


Call `VM.error.display' when a project doesn't exist.

See #1152.

--- js/app.coffee
+++ js/app.coffee
@@ -176,6 +176,7 @@
     $.getJSON path, (data) =>
       @builds((new CI.inner.Build d for d in data))
       @builds_have_been_loaded(true)
+    .fail (request) => VM.error.display()

   loadRecentBuilds: (refresh) =>
     @loadBuilds('/api/v1/recent-builds', refresh)


Agent pie testing

Agent pie testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -248,7 +248,7 @@
             maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
             return
         )
-        .attr("x",0),attr("y",0)
+        .attr("x",0).attr("y",0)
         .transition()
             .duration(1000)
             .attr('x', (d) ->


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -29,7 +29,7 @@
         dropshadowy = 2                         # Y-offset for drop shadow filter
         dropshadowblur = "1.2"                  # [STRING] Blur value for drop shadow filter

-        pie = d3.layout.pie().value((d) -> d.value)
+        pie = d3.layout.pie().value((d) -> d.value).sort(null)
         arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
         svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
         #console.log 'update pie', dataSet


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -644,7 +644,34 @@
     #  d.x
     #).attr 'y', (d) ->
     #  d.y
-    .transition().duration(1500).attrTween 'd', (d,i) ->
+    .transition().duration(1500).attrTween 'x', (d,i) ->
+        `var i`
+        theOldDataInPie = oldPieData
+        # Interpolate the arcs in data space
+        s0 = undefined
+        e0 = undefined
+        if theOldDataInPie[i]
+          s0 = theOldDataInPie[i].startAngle
+          e0 = theOldDataInPie[i].endAngle
+        else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
+          s0 = theOldDataInPie[i - 1].endAngle
+          e0 = theOldDataInPie[i - 1].endAngle
+        else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
+          s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+          e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+        else
+          s0 = 0
+          e0 = 0
+        i = d3.interpolate({
+          startAngle: 0
+          endAngle: 0
+        },
+          startAngle: d.startAngle
+          endAngle: d.endAngle)
+        (t) ->
+          b = i(t)
+          return arc b
+    .attrTween 'y', (d,i) ->
         `var i`
         theOldDataInPie = oldPieData
         # Interpolate the arcs in data space


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -46,7 +46,7 @@
             defs = svg.append('defs')
         marker = defs.select('marker#circ')
         if marker.empty()
-            defs.append('marker').attr('id', 'circ').attr('markerWidth', 44).attr('markerHeight', 44).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
+            defs.append('marker').attr('id', 'circ').attr('markerWidth', 12).attr('markerHeight', 12).attr('refX', 6).attr('refY', 6).append('circle').attr('cx', 6).attr('cy', 6).attr 'r', 6

         # Add drop shadow filter
         filter = defs.append("filter")


Agent pie: edit pointer transition

Agent pie: edit pointer transition

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -320,7 +320,7 @@
         #            return 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
         #    )
         #    #.style('opacity', 1)
-        pointers.transition().duration(2000).attr('d', (d) ->
+        pointers.transition().duration(2000).attr("d", (d) ->
                 console.log('d',d)
                 if d.cx > d.l
                     'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy


Don't provide free group VMs.

Don't provide free group VMs.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -448,32 +448,33 @@
         if err then handleError err
         else user.update { $set: { uid } }, handleError

-    JGroup.on 'GroupCreated', ({group, creator})->
-      group.fetchBundle (err, bundle)->
-        console.log err, bundle
-        if err then handleError err
-        else if bundle and bundle.sharedVM
-          creator.fetchUser (err, user)->
-            if err then handleError err
-            else
-              # Following is just here to register this name in the counters collection
-              ((require 'koding-counter') {
-                db          : JVM.getClient()
-                counterName : "#{group.slug}~"
-                offset      : 0
-              }).next ->
-
-              addVm {
-                user
-                sudo        : yes
-                type        : 'group'
-                target      : group
-                planCode    : 'free'
-                planOwner   : "group_#{group._id}"
-                groupSlug   : group.slug
-                webHome     : group.slug
-                groups      : wrapGroup group
-              }
+    # Do not give free group VMs
+    # JGroup.on 'GroupCreated', ({group, creator})->
+    #   group.fetchBundle (err, bundle)->
+    #     console.log err, bundle
+    #     if err then handleError err
+    #     else if bundle and bundle.sharedVM
+    #       creator.fetchUser (err, user)->
+    #         if err then handleError err
+    #         else
+    #           # Following is just here to register this name in the counters collection
+    #           ((require 'koding-counter') {
+    #             db          : JVM.getClient()
+    #             counterName : "#{group.slug}~"
+    #             offset      : 0
+    #           }).next ->
+    #
+    #           addVm {
+    #             user
+    #             sudo        : yes
+    #             type        : 'group'
+    #             target      : group
+    #             planCode    : 'free'
+    #             planOwner   : "group_#{group._id}"
+    #             groupSlug   : group.slug
+    #             webHome     : group.slug
+    #             groups      : wrapGroup group
+    #           }

     JGroup.on 'GroupDestroyed', (group)->
       group.fetchVms (err, vms)->


Fix an issue with using env string without parsing

--- src/talk-rubbish.coffee
+++ src/talk-rubbish.coffee
@@ -29,7 +29,10 @@
     robot.brain.remove "hubot_phrases_said"
     msg.send "I have dumped rubbish!"

-  triggers = process.env.HUBOT_RUBBISH_TRIGGERS ? ["talk rubbish"]
+  triggers = ["talk rubbish"]
+  try
+    more = JSON.parse process.env.HUBOT_RUBBISH_TRIGGERS
+    triggers = triggers.concat more if more.length > 0
   regex = new RegExp triggers.join("|"), "i"

   randomPhrase = () ->
@@ -61,7 +64,7 @@
     robot.http(url)
       .get() (err, res, body) ->
         try
-          newList = JSON.parse(body)
+          newList = JSON.parse body
           robot.brain.set "hubot_rubbish_phrases", newList
         catch e
           console.log "#{e.message} from #{url}"


[atom] ctags

[atom] ctags

--- .atom/config.cson
+++ .atom/config.cson
@@ -1,5 +1,10 @@
 "*":
+  "atom-ctags":
+    autoBuildTagsWhenActive: true
   core:
+    disabledPackages: [
+      "symbols-view"
+    ]
     ignoredNames: [
       ".git"
       ".hg"


updated crawler to listen for when angular has loaded

--- crawler.coffee
+++ crawler.coffee
@@ -14,7 +14,7 @@
 base = system.args[2]
 home = system.args[1]
 ignoreRegex = system.args[4]
-timeout = system.args[3] || 3000
+pageTimeout = system.args[3] || 8000

 if ignoreRegex
   ignoreRegex = new RegExp system.args[4]
@@ -109,15 +109,49 @@
   else
     phantom.exit()

+# This function use's Angular's `$browser.notifyWhenNoOutstandingRequests()` to wait for the app to settle and then
+# writes a message to the `console.log` notifying Phantom that it's ready to be indexed.
+waitForAppLoaded = (consoleLogVar) ->
+  w = window
+
+  w.waitForAngular = () ->
+    if document.documentElement.classList.contains 'ng-scope'
+      w.clearInterval w.interval
+
+      w.angular.element(document.documentElement).injector().get('$browser').notifyWhenNoOutstandingRequests () ->
+        w.console.log consoleLogVar
+
+  document.addEventListener 'DOMContentLoaded', () ->
+    w.interval = w.setInterval w.waitForAngular, 0
+
 # This function loads the next address and prepares to index it. There's a default timeout of 3 seconds but you can
 # change that either in the source code or via the `cli`.
 next = (address) ->
+  timeout = null
   console.log "Opening #{address}"

-  page.open address, (status) ->
+  # Generate a unique `consoleLogVar` to notify Phantom that this page has loaded.
+  consoleLogVar = btoa address
+
+  # We listen to a specific `console.log` fired by our script when Angular's
+  # `$browser.notifyWhenNoOutstandingRequests()` is fired.
+  page.onConsoleMessage = (message) ->
+    # Check that the message is the same as our `consoleLogVar`.
+    if message is consoleLogVar
+      # So it looks like the page has loaded, cancel the `timeout` and index it
+      clearTimeout timeout
+      index address
+
+  page.onInitialized = () ->
+    # Add a listener to the page to notify us when Angular has settled.
+    page.evaluate waitForAppLoaded, consoleLogVar
+
     indexed.push address

-    setTimeout index, timeout, address
+    timeout = setTimeout index, pageTimeout, address
+
+  # Now we open the page in Phantom.
+  page.open address

 # Finally we start everything off by indexing the homepage.
-next home+next home


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,2 +1,3 @@
 TimedEvents = (I={}) ->
-
+  every: (n) ->
+


put back image after video is finished

put back image after video is finished

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -11,14 +11,14 @@
 player.addEvent('ready',->
     console.log('ready');
     player.addEvent('finish', ->
-      console.log("finished")
-      player.api("seekTo", 0)
+      $("iframe").hide()
+      $("#play-intro-button").show()
     )
 )

 $("#play-intro-button").on("click", ->
   $("iframe").show().css("margin-bottom", "-5px")
-  $(@).remove()
+  $(@).hide()
   window.vimeo_player.api("play")
 )


added asBlock method

added asBlock method

--- app/assets/javascripts/jblocks.coffee
+++ app/assets/javascripts/jblocks.coffee
@@ -85,4 +85,9 @@
 	if @e_name
 		@removeClass("b-#{@b_name}__#{@e_name}--#{mod}")
 	else
-		@removeClass("b-#{@b_name}--#{mod}")+		@removeClass("b-#{@b_name}--#{mod}")
+
+$.fn.asBlock = (name, o) ->
+	@addClass("b-#{name}")
+	@data('bem', o)
+	$$.getBlocks(@)


indented panel_input.click for the dropup button only.

--- app/assets/javascripts/mock.js.coffee
+++ app/assets/javascripts/mock.js.coffee
@@ -173,8 +173,8 @@

             add_dropup_tab_mouse_behavior(panel_input, panel_dropup)

-        $(panel_input).click ->
-            focus_last_dropup_item $(panel).find(".panel-dropup"), {wait: true}
+            $(panel_input).click ->
+                focus_last_dropup_item $(panel).find(".panel-dropup"), {wait: true}

         $(panel_input).keypress (e) ->
             if get_hotkey_command(e) == '\t'


Refactor coffee script for tips

Refactor coffee script for tips

 - Add form input highlight when charCount is over the limit
 - Simplify and cleanup the rest of the code

--- app/assets/javascripts/tips.js.coffee
+++ app/assets/javascripts/tips.js.coffee
@@ -1,7 +1,36 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
+
+charCount = (selector, limit, counter_display) ->
+  $counter_display = $(counter_display)
+  remaining = limit - $(selector).val().length
+  $counter_display.text remaining + " characters remaining"
+  if (remaining <= 10)
+    $counter_display.addClass("approaching-limit")
+  else
+    $counter_display.removeClass("approaching-limit")
+  if (remaining < 0)
+    $(selector).parent().addClass('has-error')
+  else
+    $(selector).parent().removeClass('has-error')
+
 jQuery ->
+  # Initialize the counters for Title and Description
+  charCount "#tip_title", 50, ".tip-title-countdown"
+  charCount "#tip_description", 250, ".tip-description-countdown"
+
+  # Monitor the changes for Title and Description
+  $("#tip_title, #tip_description").change ->
+    charCount "#tip_title", 50, ".tip-title-countdown"
+    charCount  "#tip_description", 250, ".tip-description-countdown"
+
+  # Monitor typing with the Title and Description
+  $("#tip_title, #tip_description").keyup ->
+    charCount "#tip_title", 50, ".tip-title-countdown"
+    charCount  "#tip_description", 250, ".tip-description-countdown"
+
+  # Send the tip body to Markdown parser to generate the preview and show preview
   $('#code_preview_tab').click (event) ->
     event.preventDefault()
     unless $("#code_preview_tab").parent().hasClass("active")
@@ -13,38 +42,11 @@
       $("#code_preview_tab").parent().addClass("active")
       $("#write_tab").parent().removeClass("active")

-jQuery ->
+  # Switch back to tip body input
   $('#write_tab').click (event) ->
     event.preventDefault()
     unless $("#write_tab").parent().hasClass("active")
-      url = "/tips/preview"
-      body = { body: $("#tip_body").val() }
-      $.post(url, body)
       $("#write_input").show()
       $("#code_preview").hide()
       $("#code_preview_tab").parent().removeClass("active")
       $("#write_tab").parent().addClass("active")
-
-
-charCount = (selector, limit, counter_display) ->
-  $counter_display = $(counter_display)
-  remaining = limit - $(selector).val().length
-  $counter_display.text remaining + " characters remaining"
-  if (remaining <= 10)
-    $counter_display.addClass("approaching-limit")
-  else
-    $counter_display.removeClass("approaching-limit")
-
-jQuery ->
-  charCount "#tip_title", 50, ".tip-title-countdown"
-  charCount "#tip_description", 250, ".tip-description-countdown"
-
-  $("#tip_title, #tip_description").change ->
-    charCount "#tip_title", 50, ".tip-title-countdown"
-    charCount  "#tip_description", 250, ".tip-description-countdown"
-
-  $("#tip_title, #tip_description").keyup ->
-    charCount "#tip_title", 50, ".tip-title-countdown"
-    charCount  "#tip_description", 250, ".tip-description-countdown"
-
-


Fix a bug when changing threshold in the toolbar

--- app/assets/javascripts/toolbar.coffee
+++ app/assets/javascripts/toolbar.coffee
@@ -121,7 +121,7 @@
   folding: ->
     return  unless @options.folding?
     items = $(@options.folding)
-    items.find(".folding").remove()
+    items.find(".folding").click()
     for i in items
       do (i) =>
         item  = $(i)


Fix error 'Favorited by' showing no one on fresh page

--- app/assets/javascripts/vote.js.coffee
+++ app/assets/javascripts/vote.js.coffee
@@ -15,7 +15,6 @@
             "Great",
             "Favorite"
         ]
-        @votes = {'0':[], '1':[], '2':[], '3':[]}

     registerVotes: (votes) ->
         @posts = votes
@@ -59,7 +58,9 @@
             $('#add-to-favs').css 'display', 'list-item'
             $('#remove-from-favs').css 'display', 'none'
         $('#post-score-'+@current_post.id).html score
-        $('#favorited-by').html Favorite.link_to_users @votes[@v.fav]
+        try
+            $('#favorited-by').html Favorite.link_to_users @votes[@v.fav]
+        catch err
         false


remove superflous caching

remove superflous caching

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -94,7 +94,6 @@
     options =
       data   : { default: false }
       url    : "#{sd.ARTSY_URL}/api/v1/partner_show/#{@get('id')}/images"
-      cache  : true
     _.extend options, callbacks
     @installShots.fetch options

@@ -104,7 +103,6 @@
     options =
       data   : { size: 10, published: true }
       url    : "#{@url()}/artworks"
-      cache  : true
     _.extend options, callbacks
     @artworks.fetchUntilEnd options


Use one liner for getting factoid

Use one liner for getting factoid

--- src/scripts/factoid.coffee
+++ src/scripts/factoid.coffee
@@ -11,10 +11,8 @@
     "OK."

   get: (key) ->
-    if @cache[key]
-      @cache[key]
-    else
-      "No factoid for #{key}"
+    @cache[key] or "No factoid for #{key}"
+

   handleFactoid: (text) ->
     if match = /^~(.+) is (.+)/i.exec text


Moregeorgie goodness

Moregeorgie goodness

--- src/scripts/geordie.coffee
+++ src/scripts/geordie.coffee
@@ -12,10 +12,12 @@
   "http://www.assetstorage.co.uk/AssetStorageService.svc/GetImageFriendly/721463860/339/422/0/0/1/80/ResizeBestFit/0/FRU/883FBEAF0426B94D8793618077E9D342/geoff-and-spuggy.jpg",
   "http://www.bbc.co.uk/tyne/content/images/2006/08/21/ant_and_dec_show_470x353.jpg",
   "http://www.starstills.com/product_images/m/ss2237118_-_photograph_of_jimmy_nail_as_leonard_jeffrey_oz_osborne_from_auf_wiedersehen_pet_available_in_4_sizes_framed_or_unframed_buy_now_at_starstills__91430.jpg",
-  "http://i2.cdnds.net/13/31/618x579/uktv-auf-wiedersehen-pet.jpg"
+  "http://i2.cdnds.net/13/31/618x579/uktv-auf-wiedersehen-pet.jpg",
+  "http://eil.com/images/main/Lindisfarne+-+Fog+On+The+Tyne+%5BRevisited%5D+-+5%22+CD+SINGLE-494879.jpg",
+  "https://s3.amazonaws.com/uploads.hipchat.com/64658/539392/B3WShhDa0HOiKZ3/upload.png",
 ]

 module.exports = (robot) ->

-  robot.respond /whey-aye man/i, (msg) ->
+  robot.respond /(whey-aye man|fog on the tyne)/i, (msg) ->
     msg.send msg.random geordies