Audio/video locking, switch keyframe

Audio/video locking, switch keyframe

--- rtmp.coffee
+++ rtmp.coffee
@@ -513,30 +513,33 @@

   for session in allSessions
     msgs = [];
+    msgs2 = []
     for rtmpMessage, i in rtmpMessagesToSend
-      msgs.push rtmpMessage if rtmpMessage.stream == session.stream
+      msgs.push rtmpMessage if session.from_packet(rtmpMessage)

     if not msgs? || msgs.length == 0
       continue

     #console.log "lets stream"
     #fixme -> remove the ! and test again, this was a hack for stream switching
-    if !session.isPlaying
+    if session.isWaitingForKeyFrame
       if session.stream.isVideoStarted  # has video stream
         for rtmpMessage, i in msgs
           if rtmpMessage.avType is 'video' and rtmpMessage.isKeyFrame
             session.isPlaying = true
             session.playStartTimestamp = rtmpMessage.originalTimestamp
             session.isWaitingForKeyFrame = false
-            msgs = msgs[i..]
+            msgs2 = msgs[i..]
             break
       else  # audio only
         session.isPlaying = true
         session.playStartTimestamp = msgs[0].originalTimestamp
         session.isWaitingForKeyFrame = false
-        msgs = msgs
+        msgs2 = msgs
     else
-      msgs = msgs
+      msgs2 = msgs
+
+    msgs2 = msgs;

     if not msgs? || msgs.length == 0
       continue
@@ -551,7 +554,7 @@
         console.log "metadata re-sent"

       for rtmpMessage in msgs
-        session.lastTimestamp = rtmpMessage.timestamp = session.offsetTimestamp + session.getScaledTimestamp(rtmpMessage.originalTimestamp) % TIMESTAMP_ROUNDOFF
+        session.lastTimestamp[rtmpMessage.avType] = rtmpMessage.timestamp = session.offsetTimestamp[rtmpMessage.avType] + session.getScaledTimestamp(rtmpMessage.originalTimestamp) % TIMESTAMP_ROUNDOFF

       if msgs.length > 1
         buf = createRTMPAggregateMessage msgs, session.chunkSize
@@ -796,8 +799,15 @@
     @receivedBytes = 0
     @stream_switched = false
     @server = server
-    @lastTimestamp = 0
-    @offsetTimestamp = 0
+    @lastTimestamp = { 'audio': 0, 'video': 0 }
+    @offsetTimestamp = { 'audio': 0, 'video': 0 }
+
+  from_packet: (pkt) ->
+    if (pkt.avType == 'audio')
+      if @audio_stream
+        console.log 'Audio filter'
+        return (@audio_stream == pkt.stream)
+    return @stream == pkt.stream

   clearTimeout: ->
     if @timeoutTimer?
@@ -1962,11 +1972,11 @@
       consumeNextRTMPMessage()

 class RTMPServer
-  setBroadcastStream: (name) ->
+  setBroadcastStream: (name, avType='video') ->
     console.log "setBroadcastStream", name
     broadcasted_stream = name
     stream = getStreamByName(name)
-    stream_ts = 0
+    stream_ts = { 'audio': 0, 'video': 0};
     for clientID, session of sessions
       if session.stream == stream
         stream_ts = session.lastTimestamp
@@ -1975,10 +1985,17 @@
     @emit 'broadcast_actual_changed', name
     for clientID, session of sessions
       if session.streamName == "broadcast" && session.stream != stream
-        session.offsetTimestamp = session.lastTimestamp - stream_ts
-        session.stream = stream
+        session.offsetTimestamp[avType] = session.lastTimestamp[avType] - stream_ts[avType]
+
+        if (avType == 'audio')
+          session.audio_stream = stream
+
+        if (avType == 'video')
+          session.stream = stream
+          session.isWaitingForKeyFrame = true
+
         session.stream_switched = true
-        console.log "switched stream", session.offsetTimestamp
+        console.log "switched stream", avType, session.offsetTimestamp[avType]

   on: (event, listener) ->


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -103,12 +103,12 @@
     for_beginners: "Para Iniciantes"
     multiplayer: "Multijogador"
     for_developers: "Para Programadores"
-#    javascript_blurb: "A linguagem da web. Ótima para escrever websites, aplicações da web, jogos HTML5 e servidores."
-#    python_blurb: "Simples mas poderoso, o Python é uma linguagem de programação ótima para propósitos gerais."
-#    coffeescript_blurb: "Sintaxe do Javascript mais agradável."
-#    clojure_blurb: "Um Lisp moderno"
-#    lua_blurb: "Linguagem para scripts de jogos"
-#    io_blurb: "Simples mas obscuro"
+    javascript_blurb: "A linguagem da web. Ótima para escrever websites, aplicações da web, jogos HTML5 e servidores."
+    python_blurb: "Simples mas poderoso, o Python é uma linguagem de programação ótima para propósitos gerais."
+    coffeescript_blurb: "Sintaxe do Javascript mais agradável."
+    clojure_blurb: "Um Lisp moderno"
+    lua_blurb: "Linguagem para scripts de jogos"
+    io_blurb: "Simples mas obscuro"

   play:
     choose_your_level: "Escolha o Seu Nível"
@@ -846,9 +846,9 @@
     rank_my_game: "Classifique o Meu Jogo!"
     rank_submitting: "A submeter..."
     rank_submitted: "Submetido para Classificação"
-    rank_failed: "Falhou a Classificar"
+    rank_failed: "A Classificação Falhou"
     rank_being_ranked: "Jogo a ser Classificado"
-#    rank_last_submitted: "submitted "
+    rank_last_submitted: "submetido "
     help_simulate: "Ajudar a simular jogos?"
     code_being_simulated: "O seu novo código está a ser simulado por outros jogadores, para ser classificado. Isto será atualizado quando surgirem novas partidas."
     no_ranked_matches_pre: "Sem jogos classificados pela equipa "
@@ -921,8 +921,8 @@
     gplus_friends: "Amigos do Google+"
 #    gplus_friend_sessions: "G+ Friend Sessions"
     leaderboard: "Tabela de Classificação"
-#    user_schema: "User Schema"
-#    user_profile: "User Profile"
+    user_schema: "Esquema do Utilizador"
+    user_profile: "Perfil do Utilizador"
 #    patches: "Patches"
     patched_model: "Documento Fonte"
     model: "Modelo"
@@ -937,7 +937,7 @@
     user_names: "Nomes de Utilizador"
     thang_names: "Nomes de Thangs"
     files: "Ficheiros"
-#    top_simulators: "Top Simulators"
+    top_simulators: "Melhores Simuladores"
     source_document: "Documento Fonte"
     document: "Documento"
 #    sprite_sheet: "Sprite Sheet"
@@ -954,37 +954,37 @@
     merge_conflict_with: "FUNDIR CONFLITO COM"
     no_changes: "Sem Alterações"

-#  user:
-#    stats: "Stats"
-#    singleplayer_title: "Singleplayer Levels"
-#    multiplayer_title: "Multiplayer Levels"
-#    achievements_title: "Achievements"
-#    last_played: "Last Played"
-#    status: "Status"
-#    status_completed: "Completed"
-#    status_unfinished: "Unfinished"
-#    no_singleplayer: "No Singleplayer games played yet."
-#    no_multiplayer: "No Multiplayer games played yet."
-#    no_achievements: "No Achievements earned yet."
-#    favorite_prefix: "Favorite language is "
-#    favorite_postfix: "."
-
-#  achievements:
-#    last_earned: "Last Earned"
-#    amount_achieved: "Amount"
-#    achievement: "Achievement"
-#    category_contributor: "Contributor"
-#    category_miscellaneous: "Miscellaneous"
-#    category_levels: "Levels"
-#    category_undefined: "Uncategorized"
-#    current_xp_prefix: ""
-#    current_xp_postfix: " in total"
-#    new_xp_prefix: ""
-#    new_xp_postfix: " earned"
-#    left_xp_prefix: ""
-#    left_xp_infix: " until level "
-#    left_xp_postfix: ""
-
-#  account:
-#    recently_played: "Recently Played"
-#    no_recent_games: "No games played during the past two weeks."
+  user:
+    stats: "Estatísticas"
+    singleplayer_title: "Níveis Um Jogador"
+    multiplayer_title: "Níveis Multijogador"
+    achievements_title: "Conquistas"
+    last_played: "Última Vez Jogado"
+    status: "estado"
+    status_completed: "Completado"
+    status_unfinished: "Inacabado"
+    no_singleplayer: "Sem jogos Um Jogador jogados."
+    no_multiplayer: "Sem jogos Multijogador jogados."
+    no_achievements: "Sem Conquistas ganhas."
+    favorite_prefix: "Linguagem favorita é "
+    favorite_postfix: "."
+
+  achievements:
+    last_earned: "Último Ganho"
+    amount_achieved: "Quantidade"
+    achievement: "Conquista"
+    category_contributor: "Contribuidor"
+    category_miscellaneous: "Vários"
+    category_levels: "Níveis"
+    category_undefined: "Sem Categoria"
+    current_xp_prefix: ""
+    current_xp_postfix: " no total"
+    new_xp_prefix: ""
+    new_xp_postfix: " ganho"
+    left_xp_prefix: ""
+    left_xp_infix: " até ao nível "
+    left_xp_postfix: ""
+
+  account:
+    recently_played: "Jogados Recentemente"
+    no_recent_games: "Sem jogos jogados nas passadas duas seemanas."


exportImage graph function

exportImage graph function

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -3,6 +3,7 @@
 _ = require 'underscore'
 moment = require 'moment'
 Bacon = require 'bacon.model'
+Q = require 'q'

 clearExtent = (v) -> _.extend {}, v, {extent: null}
 setBrushing = (v) -> _.extend {}, v, {brushing: true}
@@ -115,8 +116,9 @@
   svg = d3.select(container).append('svg')
     .on('mousemove', (d, i) -> mouse_moves.push d3.mouse g.node())

+  # TODO font
   g = svg
-    .append("g")
+    .append("g").style({'font-family': '"Helvetica Neue"'})

   xAxisG = g.append('g')
     .attr('class', 'x axis')
@@ -145,6 +147,27 @@
   destroy = ->
     _.each(destroyFunctions, (f) -> f())
     destroyFunctions = []
+
+  exportImage = ->
+    deferred = Q.defer()
+    canvas = document.createElement('canvas')
+    rect = svg.node().getBoundingClientRect()
+    canvas.width = rect.width
+    canvas.height = rect.height
+    ctx = canvas.getContext('2d')
+    svgString = new XMLSerializer().serializeToString(svg.node())
+    svgBlob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'})
+    url = URL.createObjectURL(svgBlob)
+    img = new Image
+    img.src = url
+    img.onload = ->
+      ctx.drawImage(img, 0, 0)
+      dataUrl = canvas.toDataURL()
+      deferred.resolve(dataUrl)
+    img.onerror = (e) ->
+      deferred.reject(e)
+    deferred.promise.finally ->
+      URL.revokeObjectURL(url)

   draw = (data, params) ->
     destroy()
@@ -497,6 +520,6 @@

     legend_target.exit().remove()

-  {draw, destroy}
+  {draw, exportImage, destroy}

 module.exports = {create}


Actualizando(Update)

Actualizando(Update)

Actualice un poco.

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -141,8 +141,8 @@
     books: "Libros"

   common:
-#    back: "Back" # When used as an action verb, like "Navigate backward"
-#    continue: "Continue" # When used as an action verb, like "Continue forward"
+    back: "Volver" # When used as an action verb, like "Navigate backward"
+    continue: "Continuar" # When used as an action verb, like "Continue forward"
     loading: "Cargando..."
     saving: "Guardando..."
     sending: "Enviando..."
@@ -335,8 +335,8 @@
     tip_source_code: "Quisiera cambiar el mundo, pero no me dan el código fuente."
     tip_javascript_java: "Java es a Javascript lo mismo que Comer es a Comercial. - Chris Heilmann"
     tip_move_forward: "Hagas lo que hagas, siempre sigue hacia delante. - Martin Luther King Jr."
-#    tip_google: "Have a problem you can't solve? Google it!"
-#    tip_adding_evil: "Adding a pinch of evil."
+    tip_google: "Tienes un problema que no puedes resolver? Googlealo!"
+    tip_adding_evil: "Agregando una pizca de maldad."
 #    tip_miss_lunch: "Anyone who has lost track of time when using a computer knows the propensity to dream, the urge to make dreams come true and the tendency to miss lunch. - Tim Berners-Lee"

   game_menu:
@@ -356,19 +356,19 @@
     multiplayer_caption: "¡Jugar con amigos!"
     auth_caption: "Guarda tu progreso."

-#  leaderboard:
-#    leaderboard: "Leaderboard"
-#    view_other_solutions: "View Other Solutions"
-#    scores: "Scores"
-#    top_solutions: "Top Solutions"
-#    day: "Today"
-#    week: "This Week"
-#    all: "All-Time"
-#    time: "Time"
-#    damage_taken: "Damage Taken"
-#    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
+  leaderboard:
+    leaderboard: "Clasificación"
+    view_other_solutions: "Ver Otras Soluciones"
+    scores: "Puntajes"
+    top_solutions: "Mejores Soluciones"
+    day: "Hoy"
+    week: "Esta semana"
+    all: "Todo el tiempo"
+    time: "Tiempo"
+    damage_taken: "Daño Recibido"
+    damage_dealt: "Daño Realizado"
+    difficulty: "Dificultad"
+    gold_collected: "Oro Recolectado"

   inventory:
     choose_inventory: "Equipar objetos"
@@ -396,15 +396,15 @@
     recovered: "Se recuperaron las anteriores compras de gemas. Por favor recarga la página"

   subscribe:
-#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
-#    feature1: "60+ basic levels across 4 worlds"
-#    feature2: "7 powerful new heroes with unique skills!"
-#    feature3: "30+ bonus levels"
-#    feature4: "3500 bonus gems every month!"
-#    feature5: "Video tutorials"
-#    feature6: "Premium email support"
-#    free: "Free"
-#    month: "month"
+    comparison_blurb: "Agudiza tus habilidades con la suscripción a CodeCombat!"
+    feature1: "Más de 60 niveles basicos a lo largo de 4 mundos"
+    feature2: "7 poderosos nuevos heroés con habilidades unicas!"
+    feature3: "Más de 30 niveles extras"
+   feature4: "3500 gemas bonus cada mes!"
+    feature5: "Video tutoriales"
+    feature6: "Soporte Premium vía email"
+    free: "Gratis"
+    month: "mes"
     subscribe_title: "Suscribirse"
     unsubscribe: "Des-suscribirse"
     confirm_unsubscribe: "Confirmar cancelacion de suscripción"
@@ -414,14 +414,14 @@
     thank_you: "Gracias por apoyar CodeCombat."
     sorry_to_see_you_go: "¡Sentimos que te vayas! Por favor, haznos saber lo que podríamos haber hecho mejor."
     unsubscribe_feedback_placeholder: "¿Pero qué hemos hecho?"
-#    parent_button: "Ask your parent"
-#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
-#    parent_email_input_invalid: "Email address invalid."
-#    parent_email_input_label: "Parent email address"
-#    parent_email_input_placeholder: "Enter parent email"
-#    parent_email_send: "Send Email"
-#    parent_email_sent: "Email sent!"
-#    parent_email_title: "What's your parent's email?"
+    parent_button: "Preguntale a tus padres"
+    parent_email_description: "Nosotros les mandaremos un email a ellos, así pueden comprarte la suscripción a CodeCombat."
+    parent_email_input_invalid: "Dirección de email invalida."
+    parent_email_input_label: "Dirección email padres"
+    parent_email_input_placeholder: "Ingresa el email de tus padres"
+    parent_email_send: "Enviar email"
+    parent_email_sent: "Email enviado!"
+    parent_email_title: "Cuál es el email de tus padres?"
     parents: "Para padres"
     parents_title: "Su hijo aprenderá a programar."
     parents_blurb1: "Con CodeCombat, su hijo aprenderá a escribiendo código real. Empezaran aprendiendo comandos simples avanzando a temas más complejos."
@@ -429,7 +429,7 @@
     parents_blurb3: "Sin Riesgo: Garantía de 100% de devolución, fácil 1-click y des- suscribirse."
     stripe_description: "Suscripción Mensual"
     subscription_required_to_play: "Necesitas una suscripción para jugar este nivel."
-#    unlock_help_videos: "Subscribe to unlock all video tutorials."
+    unlock_help_videos: "Suscribete para desbloquear todos los vídeo tutoriales."

   choose_hero:
     choose_hero: "Elige tu héroe"
@@ -593,7 +593,7 @@
     subscribe: "compra una suscripción de CodeCombat"
     subscribe_suffix: "y nosotros estaremos felices de ayudarte con tu código."
     subscriber_support: "Como estás suscrito a CodeCombat, tu email tendrá prioridad."
-#    screenshot_included: "Screenshot included."
+    screenshot_included: "Captura de pantalla incluida."
     where_reply: "¿A dónde deberíamos responder?"
     send: "Enviar Comentario"
     contact_candidate: "Contacta un Candidato" # Deprecated
@@ -736,7 +736,7 @@
     config_thang: "Doble clic para configurar un thang"
     delete: "Borrar"
     duplicate: "Duplicar"
-#    stop_duplicate: "Stop Duplicate"
+    stop_duplicate: "Parar de Duplicar"
     rotate: "Rotar"
     level_settings_title: "Opciones"
     level_component_tab_title: "Componentes Actuales"
@@ -1013,7 +1013,7 @@
     play_counts: "Conteo de juegos"
     feedback: "Feedback"
     payment_info: "Información de pago"
-#    campaigns: "Campaigns"
+    campaigns: "Campañas"

   delta:
     added: "Agregado"
@@ -1057,8 +1057,8 @@
     email_description_suffix: "o mediante links en los mails que mandamos, tu puedas cambiar tus preferencias y fácilmente desuscribirte en cualquier momento."
     cost_title: "Costo"
     cost_description: "CodeCombat es gratuito para todos sus niveles principales, con una suscripción de $9.99 USD/mes con acceso a niveles adicionales y un bonus de 3500 gemas cada mes. Puedes cancelar con un click y ofrecemos una garantía del 100%."
-#    copyrights_title: "Copyrights and Licenses"
-#    contributor_title: "Contributor License Agreement"
+    copyrights_title: "Derechos y Licencias"
+    contributor_title: "Contributor License Agreement"
 #    contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
 #    cla_url: "CLA"
 #    contributor_description_suffix: "to which you should agree before contributing."


Update play_level's tips

Update play_level's tips

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -390,7 +390,7 @@
     tip_brute_force: "Cuando tengas duda, usa la fuerza bruta. - Ken Thompson"
     tip_extrapolation: "Solo hay dos tipos de personas: Esas que pueden extrapolar desde información incompleta..."
     tip_superpower: "Programar es lo más cercano que tenemos a superpoderes."
-    tip_control_destiny: "En el código abierto real, tú tienes el derecho de controlar tu propio destino. - Linus Torvalds"
+    tip_control_destiny: "En el Open Source real, tú tienes el derecho de controlar tu propio destino. - Linus Torvalds"
     tip_no_code: "Ningún código es más rápido que nada de código."
     tip_code_never_lies: "El código nunca miente, los comentarios a veces sí. — Ron Jeffries"
     tip_reusable_software: "Antes de que el software sea reusable, primero tiene que ser usable."
@@ -408,9 +408,9 @@
     tip_strong_opponents: "Hasta los oponentes mas fuertes siempre tienen una debilidad. - Itachi Uchiha"
     tip_paper_and_pen: "Antes de comenzar a codificar, siempre puedes planear con una hoja de papel y un lapicero."
     tip_solve_then_write: "Primero, resuelve el problema. Entonces, escribe el código. - John Johnson"
-#    tip_compiler_ignores_comments: "Sometimes I think that the compiler ignores my comments."
-#    tip_understand_recursion: "The only way to understand recursion is to understand recursion."
-#    tip_life_and_polymorphism: "Open Source is like a totally polymorphic heterogeneous structure: All types are welcome."
+    tip_compiler_ignores_comments: "A veces pienso que el compilador ignora mis comentarios."
+    tip_understand_recursion: "La única manera de entender la recursividad es entender la recursividad."
+    tip_life_and_polymorphism: "El Open Source es como una estructura heterogénea totalmente polimórfica: Todos los tipos son bienvenidos."

   game_menu:
     inventory_tab: "Inventario"


Add constructed instances to array

Add constructed instances to array

--- app/scripts/route.coffee
+++ app/scripts/route.coffee
@@ -17,6 +17,8 @@
       title: 'About'
       url: '/about'
       partial: '#partial-home'
+
+  classPool: null

   constructor: ->
     navItems = document.querySelectorAll '#navigation li a'
@@ -41,8 +43,11 @@
     document.querySelector('#view').innerHTML = template()

     if @ROUTES[state].init?
+      @classPool = []
       for klass in @ROUTES[state].init
-        new klass()
+        @classPool.push new klass()
+
+    return

 window.Route = Route


Add prompt text for discussion deletion

Add prompt text for discussion deletion

--- app/talk/discussion.cjsx
+++ app/talk/discussion.cjsx
@@ -174,7 +174,8 @@
       onDeleteComment={@onDeleteComment}/>

   onClickDeleteDiscussion: ->
-    if window.confirm("Are you sure that you want to delete this discussions? All of the comments and discussions will be lost forever.")
+    deletePhrase = 'delete'
+    if window.prompt("Are you sure that you want to delete this discussion? All of the comments and discussions will be lost forever. Please type \"#{deletePhrase}\" in the box below to confirm:") is deletePhrase
       @discussionsRequest().delete()
         .then (deleted) =>
           @setComments(@props.query.page)


Output JSON-LD

Output JSON-LD

--- apps/show2/routes.coffee
+++ apps/show2/routes.coffee
@@ -116,7 +116,7 @@
     res.locals.sd.PARTNER_SHOW = data.partner_show #bootstrap
     res.locals.ViewHelpers = ViewHelpers
     res.locals.DateHelpers = DateHelpers
-    res.locals.jsonLD = ViewHelpers.toJSONLD data.partner_show
+    res.locals.jsonLD = JSON.stringify ViewHelpers.toJSONLD data.partner_show
     data.artworkColumns = ViewHelpers.groupByColumnsInOrder(data.partner_show.artworks)
     res.render 'index', data
   .catch ->


Update lib/zombie/browser.coffee

Update lib/zombie/browser.coffee

_focused is stored in the document, not window.
NTDR: _focused is not triggered for textareas, still to be fixed

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -756,7 +756,7 @@
   #
   # Returns the element in focus.
   focused: ->
-    return @window._focused
+    return @window.document._focused

   # Cookies and storage


Require admin role to send emails to users.

--- packages/rocketchat-mailer/server/methods/sendMail.coffee
+++ packages/rocketchat-mailer/server/methods/sendMail.coffee
@@ -1,5 +1,10 @@
 Meteor.methods
 	'Mailer.sendMail': (from, subject, body, dryrun, query) ->
+		if not Meteor.userId()
+			throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'Mailer.sendMail' }
+
+		unless RocketChat.authz.hasRole( Meteor.userId(), 'admin') is true
+			throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'Mailer.sendMail' }

 		return Mailer.sendMail from, subject, body, dryrun, query


context: invoke change group if entry point is a group

--- client/Main/maincontroller/groupscontroller.coffee
+++ client/Main/maincontroller/groupscontroller.coffee
@@ -21,7 +21,9 @@
       then router.handleRoute "#{pageInfo.path}"
       else router.handleRoute "#{pageInfo.path}", {entryPoint}

-    mainController.ready => @changeGroup entryPoint?.slug
+    mainController.ready =>
+      {slug} = entryPoint  if entryPoint?.type is "group"
+      @changeGroup slug

   getCurrentGroup:->
     throw 'FIXME: array should never be passed'  if Array.isArray @currentGroupData.data


Require membership for follow button procedures

--- client/landing/app/CommonViews/followbutton.coffee
+++ client/landing/app/CommonViews/followbutton.coffee
@@ -15,10 +15,14 @@
         title      : "Follow"
         cssClass   : options.stateOptions?.follow?.cssClass
         callback   : (cb)=>
-          @getData().follow (err, response)=>
-            KD.showError err, options.errorMessages
-            @getData().followee = response
-            cb? err
+          KD.requireMembership
+            onFailMsg : options.errorMessages
+            tryAgain  : yes
+            callback : =>
+              account = @getData()
+              account.follow (err, response) ->
+                account.followee = response
+                cb? err
       ,
         title      : "Following"
         cssClass   : options.stateOptions?.unfollow?.cssClass


activity/flux: add loader cleaner actions

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -148,6 +148,33 @@
     messageId : messageId
     position  : options.position
     autoload  : options.autoload
+
+
+###*
+ * Removes a message's loader marker in a channel with given options.
+ *
+ * @param {string} channelId
+ * @param {string} messageId
+ * @param {object} options
+ * @param {string} options.position - either 'before' or after
+###
+removeLoaderMarker = (channelId, messageId, options) ->
+
+  dispatch actionTypes.DEACTIVATE_LOADER_MARKER, { channelId, messageId, position: options.position }
+
+
+###*
+ * Removes given messages' loader markers in that channel. Both 'before' and
+ * 'after' markers.
+ *
+ * @param {string} channelId
+ * @param {Array.<SocialMessage>} messages
+###
+cleanLoaderMarkers = (channelId, messages) ->
+
+  messages.forEach (message) ->
+    removeLoaderMarker channelId, message.id, { position: 'before' }
+    removeLoaderMarker channelId, message.id, { position: 'after' }

 ###*
@@ -483,5 +510,7 @@
   setMessageEditMode
   unsetMessageEditMode
   changeSelectedMessageBySlug
+  putLoaderMarker
+  removeLoaderMarker
 }


testrunner: forward client to socket with forwardEvents

--- client/testrunner/lib/runnersocketconnector.coffee
+++ client/testrunner/lib/runnersocketconnector.coffee
@@ -1,3 +1,5 @@
+forwardEvents = require './util/forwardevents'
+
 module.exports = class RunnerSocketConnector

   constructor: (runner, socket) ->
@@ -13,39 +15,25 @@
     @_runner.$events.start.forEach (handler) -> handler?()

+  forwardToSocket: (name, reducer) ->
+
+    forwardEvents @_runner, @_socket, name, reducer
+
+
   connectRunnerToSocket: ->

-    @_runner.on 'start', =>
-      @emitToSocket 'start'
+    @forwardToSocket ['start', 'end', 'suite end']

-    @_runner.on 'suite', (suite) =>
-      @emitToSocket 'suite', suite.title
+    @forwardToSocket ['suite', 'pending', 'test end'], (test) -> test.title

-    @_runner.on 'suite end', =>
-      @emitToSocket 'suite end'
-
-    @_runner.on 'pending', (test) =>
-      @emitToSocket 'pending', test.title
-
-    @_runner.on 'test end', (test) =>
-      @emitToSocket 'test end', test.title
-
-    @_runner.on 'end', =>
-      @emitToSocket 'end'
-
-    @_runner.on 'pass', (test) =>
+    @forwardToSocket 'pass', (test) ->
       { speed, title, duration } = test
       slowResult = test.slow()
-      payload = JSON.stringify { speed, title, duration, slowResult }
-      @emitToSocket 'pass', payload
+      return JSON.stringify { speed, title, duration, slowResult }

-    @_runner.on 'fail', (test, err) =>
+    @forwardToSocket 'fail', (test, err) ->
       { title } = test
       fullTitleResult = test.fullTitle()
-      payload = JSON.stringify { title, err, fullTitleResult }
-      @emitToSocket 'fail', payload
+      return JSON.stringify { title, err, fullTitleResult }

-  emitToSocket: (args...) -> @_socket.emit args...
-
-


Removed "validate" option (user may leave empty about area), fixed "defaultValue".

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -158,10 +158,6 @@
       defaultValue  : if profile.about is @defaultPlaceHolder then '' else Encoder.htmlDecode profile.about
       placeholder   : if profile.about isnt @defaultPlaceHolder then null else Encoder.htmlDecode profile.about
       name          : 'about'
-      validate      :
-        rules       :
-          required  : yes
-          maxLength : 500

     @aboutInfo = new PersonalAboutView
       tooltip            :
@@ -188,10 +184,17 @@

   formCallback:(formData)->
     {profile} = @memberData
-    {about} = formData
-    if profile.about is about or about is ''
-      @unsetClass 'active'
+    {about}   = formData
+
+    if profile.about is about
+      @unsetClass "active"
       return no
+
+    if about is ""
+      @unsetClass "active"
+      profile.about = @defaultPlaceHolder
+      @aboutInfo.setData @memberData
+      @resetInputValue()

     changes = 'profile.about' : about
     @memberData.modify changes, (err)=>


fix default run path

fix default run path

--- service/index.coffee
+++ service/index.coffee
@@ -14,7 +14,7 @@
           log [repo.name, cond, 'fired']
           if act.exec?
             exec act.exec,
-              cwd: act.path ? '../script/'
+              cwd: act.path ? './script/'
             , (err, stdout, stderr) ->
               log.error err if err?
               log stdout


Fixed context menu and added missing items

--- menus/atom-sharper.cson
+++ menus/atom-sharper.cson
@@ -1,11 +1,20 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  '.overlayer':
-      'atom-sharp':
-        'Enable atom-sharp': 'omnisharp-atom:toggle'
-        'Find Usages': 'omnisharp-atom:find-usages'
-        'Rename': 'omnisharp-atom:rename'
-
+  'atom-text-editor': [
+    {
+      label: 'omnisharp-atom'
+      submenu: [
+        { 'label': 'Toggle', 'command': 'omnisharp-atom:toggle' }
+        { 'label': 'Find Usages', 'command': 'omnisharp-atom:find-usages' }
+        { 'label': 'Go to definition', 'command': 'omnisharp-atom:go-to-definition' }
+        { 'label': 'Rename', 'command': 'omnisharp-atom:rename'}
+        { 'label': 'Fix Usings', 'command': 'omnisharp-atom:fix-usings' }
+        { 'label': 'Code Format', 'command': 'omnisharp-atom:code-format' }
+        { 'label': 'Build', 'command': 'omnisharp-atom:build' }
+      ]
+    }
+  ]
+
 'menu': [
   {
     'label': 'Packages'


edits to Nora's bio

edits to Nora's bio

--- maps/contributors.coffee
+++ maps/contributors.coffee
@@ -181,7 +181,7 @@
     name: 'Nora N. Khan'
     location: 'Boston'
     links: ['[noranahidkhan.com](http://noranahidkhan.com/)', '[@noranahidkhan](https://twitter.com/noranahidkhan)']
-    bio: 'Nora N. Khan explores issues in technology, literature, games, and electronic music through fiction, essays, and reviews. A contributing editor at _Rhizome_, she writes regularly for _Kill Screen_ and has published recent work in _Conjunctions_, _DIS_, _After Us_, _AVANT.org_, and _American Literary Review_. She studied literature, history, and theory at Harvard University and fiction writing at the Iowa Writers’ Workshop.'
+    bio: 'Nora N. Khan explores issues in technology, literature, games, and electronic music through fiction, essays, and reviews. A contributing editor at _Rhizome_, she writes regularly for both _Rhizome_ and _Kill Screen_ and has published most recently in _Conjunctions_, _DIS_, _After Us_, _AVANT.org_, and _American Literary Review_. She studied literature, history, and theory at Harvard University and fiction writing at the Iowa Writers’ Workshop.'
     azone_username: ''
     azone_reviews: []
     azone_funds: []


Remove a couple annoying semicolons

Remove a couple annoying semicolons

--- snippets/arrays.cson
+++ snippets/arrays.cson
@@ -128,12 +128,12 @@
   "indexOf":
     prefix: "indexOf"
     body: """
-    ${1:array}.indexOf(${2:item}${3:, ${4:fromIndex}});
+    ${1:array}.indexOf(${2:item}${3:, ${4:fromIndex}})
     """
   "indexOf chain":
     prefix: ".indexOf"
     body: """
-    .indexOf(${1:item}${2:, ${3:fromIndex}});
+    .indexOf(${1:item}${2:, ${3:fromIndex}})
     """
   "concat":
     prefix: "concat"


Fix little typo

Fix little typo

--- examples/N1-Filters/lib/main.coffee
+++ examples/N1-Filters/lib/main.coffee
@@ -10,7 +10,7 @@
 # package to run.
 module.exports =
   # When your package is loading, the `activate` method runs. `activate` is the
-  # package's time to insert React components into the applicatio and also
+  # package's time to insert React components into the application and also
   # listen to events.
   activate: ->
     # `WorkspaceStore.defineSheet` creates an N1 "sheet," which is a large area


Handle case when resize occurs when context panel is not visible

When the browser was resized while on the query page, navigating to
the results view resulted in the context panel offset from the right.
This grossly fixes the issue by binding to the 'router:load' event to
'show' the context panel which handles adjusting the positioning.

This is a temporary fix until the context panel is refactored in #258

--- src/coffee/cilantro/ui/workflows/results.coffee
+++ src/coffee/cilantro/ui/workflows/results.coffee
@@ -100,6 +100,8 @@
             # Flag indicating the current visibility of the context panel
             @areFiltersHidden = false

+            @on 'router:load', @showContextPanel
+
         onWindowResize: =>
             @updateContextPanelOffsets()

@@ -140,6 +142,7 @@
             @ui.toggleFiltersIcon.removeClass('icon-collapse-alt')
             @ui.toggleFiltersIcon.addClass('icon-expand-alt')
             @ui.toggleFiltersText.html('Hide Filters...')
+            @updateContextPanelOffsets()
             @$('.context').stacked('restack', @$el.height())

         hideContextPanel: =>


Progressbar fix: removed "px" from setWidth

--- components/progressbar/KDProgressBarView.coffee
+++ components/progressbar/KDProgressBarView.coffee
@@ -25,7 +25,7 @@
     @bar.addSubView @lightLabel = new KDCustomHTMLView
       tagName     : "span"
       cssClass    : 'light-label'
-    @lightLabel.setWidth @getWidth(), "px"
+    @lightLabel.setWidth @getWidth()

   updateBar:(value, unit, label)->
     {determinate} = @getOptions()


No reason to expose $ manually.

No reason to expose $ manually.

--- assets/js/script.coffee
+++ assets/js/script.coffee
@@ -48,6 +48,3 @@
             setConvertedDate(dt, false)
         false
     )
-
-window.$ = $
-window.jQuery = $


Permissions: fix the role count on recursive calls.

--- client/Dashboard/views/permissionsform.coffee
+++ client/Dashboard/views/permissionsform.coffee
@@ -118,7 +118,7 @@
       cascadeData[current].defaultValue = yes
       cascadeData[current].disabled = yes
     if current and remainder.length > 0
-      cascadeData[current].nextElement = cascadeFormElements set, remainder, module, permission, roles.length
+      cascadeData[current].nextElement = cascadeFormElements set, remainder, module, permission, roleCount
     return cascadeData

   cascadeHeaderElements = (roles)->


default-apps: Added handleQuery and openFile methods to KodingAppController.

--- client/app/MainApp/kodingappcontroller.coffee
+++ client/app/MainApp/kodingappcontroller.coffee
@@ -5,3 +5,9 @@
     options.view = new KDView

     super options, data
+
+  handleQuery: (query) ->
+    @getView().emit "AppQueryPerformed", query
+
+  openFile: (file) ->
+    @getView().emit "OpenFile", file


refactor and export createProvisioner helper function

--- workers/social/testhelper/models/computeproviders/provisionerhelper.coffee
+++ workers/social/testhelper/models/computeproviders/provisionerhelper.coffee
@@ -5,7 +5,8 @@
   generateRandomString } = require '../../index'

 crypto       = require 'crypto'
-JProvisioner = require '../../../../social/lib/social/models/computeproviders/provisioner'
+JProvisioner = require \
+  '../../../../social/lib/social/models/computeproviders/provisioner'

 generateProvisionerData = (data = {}) ->
@@ -29,11 +30,12 @@
   return provisionerData

-createProvisioner = (data, callback) ->
+createProvisioner = (client, options, callback) ->

-  provisioner = new JProvisioner generateProvisionerData data
-  provisioner.save (err) ->
-    return callback err, provisioner
+  provisionerData = generateProvisionerData options
+
+  JProvisioner.create client, provisionerData, (err, provisioner) ->
+    callback err, { provisioner }

 withConvertedUserAndProvisioner = (options, callback) ->
@@ -46,13 +48,14 @@
     options.originId = account?.getId() ? new ObjectId
     options.group    = client?.context?.group

-    createProvisioner options, (err, provisioner) ->
+    createProvisioner client, options, (err, { provisioner }) ->
       expect(err).to.not.exist
       data.provisioner = provisioner
       return callback data

 module.exports = {
+  createProvisioner
   generateProvisionerData
   withConvertedUserAndProvisioner
 }


collaboration: s/PreCreated/Prepared/g

collaboration: s/PreCreated/Prepared/g

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -503,7 +503,7 @@
         Loading      : => kd.utils.defer => @onCollaborationLoading()
         Resuming     : @bound 'onCollaborationResuming'
         NotStarted   : @bound 'onCollaborationNotStarted'
-        PreCreated   : @bound 'onCollaborationPreCreated'
+        Prepared     : @bound 'onCollaborationPrepared'
         Creating     : @bound 'onCollaborationCreating'
         Active       : @bound 'onCollaborationActive'
         Ending       : @bound 'onCollaborationEnding'
@@ -556,12 +556,12 @@
         .then =>
           @workspaceData.channelId = channel.id
           @chat.ready =>
-            @stateMachine.transition 'PreCreated'
+            @stateMachine.transition 'Prepared'
         .error (err) =>
           # @stateMachine.transition 'ErrorCreating'

-  onCollaborationPreCreated: ->
+  onCollaborationPrepared: ->

     @chat.emit 'CollaborationNotInitialized'

@@ -569,7 +569,7 @@
   startCollaborationSession: ->

     switch @stateMachine.state
-      when 'PreCreated' then @stateMachine.transition 'Creating'
+      when 'Prepared' then @stateMachine.transition 'Creating'

   onCollaborationCreating: ->


collaboration: improve constraint check in loading

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -734,7 +734,7 @@
         context.setCollaborationState condition.to
         break
     setConstraint = (context, key, value) ->
-      constraints[key] = value
+      constraints[key] = Boolean value
       nextIfReady context
     ->

@@ -755,7 +755,7 @@
         setConstraint this, 'channelReady', yes

         @isRealtimeSessionActive channelId, (isActive) =>
-          result = isActive or @isInSession?
+          result = isActive or @isInSession
           setConstraint this, 'sessionActive', result


atom: update config of atom-beautify

atom: update config of atom-beautify

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -36,3 +36,5 @@
     viewedVersion: "0.89.0"
   "tree-view": {}
   minimap: {}
+  "atom-beautify":
+    _analyticsUserId: "b47ef96e-a2f6-4f69-9a8c-5043cd89e1ee"


update config

update config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,10 +1,9 @@
 "*":
   editor:
     fontFamily: "Menlo"
+    showIndentGuide: true
+    invisibles: {}
     fontSize: 15
-    showIndentGuide: true
-    autoIndentOnPaste: false
-    invisibles: {}
   core:
     ignoredNames: [
       ".bundle"
@@ -36,6 +35,7 @@
       "language-c"
       "exception-reporting"
       "autoflow"
+      "language-objective-c"
     ]
     themes: [
       "atom-dark-ui"
@@ -69,13 +69,8 @@
     autoActivationDelay: 500
     maxSuggestions: 5
     confirmCompletion: "enter"
-".js.rails.source source.jquery.js":
-  editor: {}
-".rails.rjs.ruby.source":
-  editor: {}
-".rails.ruby.source":
-  editor: {}
-".html.ruby.text":
-  editor: {}
-".ruby.source.sql":
-  editor: {}
+    fileBlacklist: [
+      ".*"
+      "*.md"
+      "*.text"
+    ]


Fixed rebinding problem on every new websocked reconnect.

--- app/assets/javascripts/app/lib/app/websocket.js.coffee
+++ app/assets/javascripts/app/lib/app/websocket.js.coffee
@@ -25,6 +25,12 @@
   supported: true

   constructor: (@args) ->
+
+    # bind to send messages
+    App.Event.bind 'ws:send', (data) =>
+      @send(data)
+
+    # inital connect
     @connect()

   send: (data) =>
@@ -140,10 +146,6 @@
             console.log( "ws:onmessage event:" + item['event'] )
             App.Event.trigger( item['event'], item['data'] )

-    # bind to send messages
-    App.Event.bind 'ws:send', (data) =>
-      @send(data)
-
     @ws.onclose = (e) =>
       console.log( 'onclose', e )


Update atom config

Update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -23,6 +23,8 @@
       "background-tips"
       "exception-reporting"
       "metrics"
+      "markdown-preview"
+      "markdown-preview-plus"
     ]
   "spell-check":
     grammars: [
@@ -34,3 +36,7 @@
     showOnStartup: false
   "release-notes":
     viewedVersion: "0.89.0"
+  "markdown-preview-plus":
+    enableLatexRenderingByDefault: true
+    useGitHubStyle: true
+    enablePandoc: true


Added docu, improved code.

Added docu, improved code.

--- app/assets/javascripts/app/lib/app_post/browser.coffee
+++ app/assets/javascripts/app/lib/app_post/browser.coffee
@@ -1,3 +1,22 @@
+###
+
+check if browser is supported
+
+  result = App.Browser.check()
+
+  result = true # true/false
+
+get used browser
+
+  browser = App.Browser.detection()
+
+  browser = {
+    browser: "Chrome",
+    version: 37,
+    OS:      "Mac"
+  }
+
+###
 class App.Browser
   @detection: ->
     data =
@@ -8,42 +27,28 @@
   @check: ->
     data = @detection()

-    # disable Crome 13 and older
-    if data.browser == 'Chrome' && data.version <= 20
-      @message(data)
-      console.log('Browser not supported')
-      return false
+    # define min. required browser version
+    map =
+      Chrome2: 37
+      Firefox: 28
+      Explorer: 10
+      Safari: 6
+      Opera: 22

-    # disable Firefox 9 and older
-    else if data.browser == 'Firefox' && data.version <= 27
-      @message(data)
-      console.log('Browser not supported')
-      return false
+    # disable id older
+    if data.browser && data.version
+      if map[data.browser] && data.version < map[data.browser]
+        @message(data, data.browser, map[data.browser])
+        console.log('Browser not supported')
+        return false

-    # disable IE 8 and older
-    else if data.browser == 'Explorer' && data.version <= 9
-      @message(data)
-      console.log('Browser not supported')
-      return false
-
-    # disable Safari 3 and older
-    else if data.browser == 'Safari' && data.version <= 6
-      @message(data)
-      console.log('Browser not supported')
-      return false
-
-    # disable Opera 10 and older
-    else if data.browser == 'Opera' && data.version <= 21
-      @message(data)
-      console.log('Browser not supported')
-      return false
-
+    # allow browser
     return true

-  @message: (data) ->
+  @message: (data, browser, version) ->
     new App.ControllerModal(
       head:     'Browser too old!'
-      message:  "Your Browser is not supported (#{data.browser} #{data.version} #{data.OS}). Please use a newer one."
+      message:  "Your Browser is not supported (#{data.browser} #{data.version} #{data.OS}). Please use a newer one (e. g. #{browser} #{version} or higher)."
       close:    false
       backdrop: false
       keyboard: false


update atom config

update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -36,7 +36,10 @@
     showOnStartup: false
   "release-notes":
     viewedVersion: "0.89.0"
-  linter: {}
+  linter:
+    subtleLinterErrors: [
+      "ruby"
+    ]
   "autocomplete-plus":
     confirmCompletion: "enter"
   minimap: {}


atom tweaks

atom tweaks

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -3,6 +3,7 @@
     general:
       _analyticsUserId: "8b9e6b6c-7ca9-4b13-8b9d-ee875e13ba81"
   "atom-material-ui":
+    colors: {}
     fonts: {}
     tabs: {}
     treeView:
@@ -64,7 +65,7 @@
       "E501"
     ]
   "linter-ui-default":
-    panelHeight: 174
+    panelHeight: 155
     showPanel: true
   metrics:
     userId: "f0927718331c072990cb750aafe7bbb7c24695ca"


fix atom workspace config to be atom 1.0 compliant

atom.workspaceView is no longer available

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -1,6 +1,5 @@
 path = require 'path'

-atom.workspaceView.eachEditorView (editorView) ->
-  editor = editorView.getEditor()
+atom.workspace.observeTextEditors (editor) ->
   if path.extname(editor.getPath()) is '.md'
     editor.setSoftWrap(true)


Set the state mode *after* the funnel event is tracked

--- components/after_inquiry/questionnaire.coffee
+++ components/after_inquiry/questionnaire.coffee
@@ -99,8 +99,8 @@
     @user.set attrs
     @user.unset 'password'
     @inquiry.unset 'session_id'
+    analytics.track.funnel "Successful #{@state.get('mode')} during after inquiry flow"
     @state.set mode: 'initial'
-    analytics.track.funnel "Successful #{@state.get('mode')} during after inquiry flow"

   toggleMode: (e) ->
     e.preventDefault()


Fix links to gaze and settings.

Fix links to gaze and settings.

--- vendor/assets/javascripts/autolink.hb.js.coffee
+++ vendor/assets/javascripts/autolink.hb.js.coffee
@@ -29,9 +29,9 @@

           linkName = url

-          if path.length == 2
+          if path.length == 2 and not ["gaze","settings"].contains(path[1])
             linkName = "@#{path[1]}"
-          else if path.length == 3 && ["clouds","users"].contains(path[1])
+          else if path.length == 3 and ["clouds","users"].contains(path[1])
             linkName = "@#{path[2]}"
             href = "/#{path[2]}"


Fixed spelling error in Cart.errors()

Fixed spelling error in Cart.errors()

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -26,7 +26,7 @@

         switch object
           when 'ship_address'
-            order.shipingAddress.errors[attr] = attrErrors
+            order.shippingAddress.errors[attr] = attrErrors
           when 'bill_address'
             order.billingAddress.errors[attr] = attrErrors
           else


set focus when switching between app center and external apps views

fixes PLAT-582

test-plan:
when you click "View App configurations" it should set focus to the "View App Center" and visa versa

Change-Id: I6fc2b2b9b2a7435bb7853ecfb99d7ef4523e4a5b
Reviewed-on: https://gerrit.instructure.com/38776
Reviewed-by: Brad Humphrey <9233b6f5388d6867a2a7be14d8b4ba53c86cfde2@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: Nathan Mills <10b0771d20c18ad0d98c1d000665e443052b40b0@instructure.com>

--- app/coffeescripts/views/ExternalTools/IndexView.coffee
+++ app/coffeescripts/views/ExternalTools/IndexView.coffee
@@ -67,6 +67,8 @@
       @$addToolLink.show()
       @externalToolsView.collection.fetch()
       @externalToolsView.show()
+      delay = (ms, func) -> setTimeout func, ms
+      delay 1, -> @$(".view_app_center_link").first().focus()

     showAppCenterView: =>
       @removeAppFullView()
@@ -75,6 +77,8 @@
       @appCenterView.show()
       @$appCenterFilterWrapper.show()
       $(document).scrollTop(@currentAppCenterPosition)
+      delay = (ms, func) -> setTimeout func, ms
+      delay 1, -> @$(".view_tools_link").first().focus()

     showAppFullView: (event) ->
       if event.type != 'keyup' || event.keyCode == 32


CNS-526: Attaching rubrics

CNS-526: Attaching rubrics

--- app/coffeescripts/widget/assignmentRubricDialog.coffee
+++ app/coffeescripts/widget/assignmentRubricDialog.coffee
@@ -33,6 +33,8 @@
         modal: false
         resizable: true
         autoOpen: false
+        close: ->
+          window.location.reload()

       $.get url, (html) ->
         # weird hackery because the server returns a <div id="rubrics" style="display:none">


Fixed expecting predictions Array test. Warning message commented.

--- yaac.coffee
+++ yaac.coffee
@@ -207,6 +207,9 @@
     {value} = evt.currentTarget

     predictions = settings.predictionsDeps.get()
+    unless Array.isArray predictions
+      # console.warn "YAAC: predictions must be defined as arrays. And no prediction to an empty array. We have #{predictions}. Make sure you fixed everything on your side. In the meantime, YAAC will be defaulting to empty array for you."
+      predictions = []

     # We try to remove last character. Useful side effect, no tag under
     # 2 characters can be made, newValue is undefined if value.length < 2
@@ -221,7 +224,7 @@
       if newValue is predictions[0][refAttribute] or (newValue isnt predictions[0][refAttribute] and settings.autoCompleteIfUnique)
         newTag = _.clone predictions[0]
     # If we have more than one prediction, try to find an exact match
-    else if Array.isArray predictions
+    else if predictions.length > 1
       hasValue = predictions.filter (prediction) -> prediction[refAttribute] is newValue
       if hasValue.length > 0
         newTag = hasValue[0]
@@ -250,7 +253,7 @@
       cleanResults = []

       {predictions} = settings
-      if _.isArray predictions
+      if Array.isArray predictions
         cleanResults = findInArray predictions, null, value, refAttribute
       else if typeof predictions is 'function'
         unsafePredictions = predictions null, value


Update public queries on sync AND destroy events on query collection

This fixes the problem where public queries could be deleted but the list of public queries was not updated until the page was reloaded. Due to recent changes in the way the delete is handled, we need to subscribe to the destroy event in addition to the sync event to account for deletion of models in the query collection.

--- src/coffee/cilantro/ui/workflows/workspace.coffee
+++ src/coffee/cilantro/ui/workflows/workspace.coffee
@@ -50,7 +50,7 @@
                 # rather than checking if the changed model is public or had
                 # its publicity changed. If this becomes too slow we can
                 # perform these checks but for now this is snappy enough.
-                @data.queries.on 'sync', (model, resp, options) =>
+                @data.queries.on 'sync destroy', (model, resp, options) =>
                     @publicQueries.currentView.collection.fetch()
                     @publicQueries.currentView.collection.reset()


fix notification popover height calculation

- get scrollHeight via .prop()
- get header height including margins by passing a true with .outerHeight()
- add 2 pixels to the popoverspacer of 2x10 for the top and bottom border of the container

--- app/assets/javascripts/app/controllers/widget/online_notification.js.coffee
+++ app/assets/javascripts/app/controllers/widget/online_notification.js.coffee
@@ -64,15 +64,16 @@
     # set height of notification popover
     notificationsContainer  = $('.js-notificationsContainer')
     heightApp               = $('#app').height()
-    heightPopoverSpacer     = 36
-    heightPopoverHeader     = notificationsContainer.find('.popover-notificationsHeader').outerHeight()
-    heightPopoverContent    = notificationsContainer.find('.popover-content').get(0).scrollHeight
+    heightPopoverSpacer     = 22
+    heightPopoverHeader     = notificationsContainer.find('.popover-notificationsHeader').outerHeight(true)
+    heightPopoverContent    = notificationsContainer.find('.popover-content').prop('scrollHeight')
     heightPopoverContentNew = heightPopoverContent
     if (heightPopoverHeader + heightPopoverContent + heightPopoverSpacer) > heightApp
       heightPopoverContentNew = heightApp - heightPopoverHeader - heightPopoverSpacer
       notificationsContainer.addClass('is-overflowing')
     else
       notificationsContainer.removeClass('is-overflowing')
+
     notificationsContainer.find('.popover-content').css('height', "#{heightPopoverContentNew}px")

     # close notification list on click


used partial for currying

used partial for currying

--- src/components/course-settings/change-period.cjsx
+++ src/components/course-settings/change-period.cjsx
@@ -18,7 +18,7 @@
   renderPeriod: (period) ->
     <AsyncButton
       className='-edit-course-confirm'
-      onClick={@updatePeriod}
+      onClick={_.partial(@updatePeriod, period.id)}
       isWaiting={RosterStore.isSaving(@props.courseId)}
       waitingText="Saving..."
       disabled={false}>


removed logging statement

removed logging statement

--- src/configuration/templates/cloudformation.coffee
+++ src/configuration/templates/cloudformation.coffee
@@ -21,9 +21,7 @@

 renderPartition = (T, path = resolve "main", "partition.yaml") ->
   ([name, partition]) ->
-    foo = [name]: await render T, partition, path
-    console.log foo
-    foo
+    [name]: await render T, partition, path

 renderPartitions = ({T, config}) ->
   config.environment.templates.partitions =


Fix recursive ids and resizing of source editor

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -164,7 +164,7 @@
     @_isOperator = []
     @variableIndex = 0
     @typeVariabeIndex = 0
-    @nameIndex = 0
+    @nameIndex = 1
     @substitution = newMap()
     @statement = []
     @cacheScopes = [[]]
@@ -1802,7 +1802,7 @@
       else
         nameCompile ctx, atom, symbol
   atom.tea = type if type
-  atom.id = id if id
+  atom.id = id if id?
   if ctx.isOperator()
     # TODO: maybe don't use label here, it's getting confusing what is its purpose
     (labelOperator atom)
@@ -1825,7 +1825,7 @@
         pattern: []
     else
       atom.label = 'name'
-      id = ctx.freshId()
+      id = (ctx.declarationId symbol) ? ctx.freshId()
       type = toConstrained ctx.freshTypeVariable star
       ctx.bindTypeVariables [type.type.name]
       ctx.addToDefinedNames {name: symbol, id: id, type: type}


Fixed docs highlighting for now in fn macro

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1061,6 +1061,7 @@
       malformed call, 'Missing function result'
     else
       [docs, defs] = partition isComment, defs
+      map labelComments, docs
       if isTypeAnnotation defs[0]
         [type, body, wheres...] = defs
       else
@@ -1098,7 +1099,7 @@
         (isName expression) and (_symbol expression) in paramNames
       labelUsedParams = (expression) ->
         map (syntaxNameAs '', 'param'), filterAst isUsedParam, expression
-      map labelUsedParams, if body then join [body], wheres else wheres
+      map labelUsedParams, join docs, (if body then join [body], wheres else wheres)

       if body and not isWellformed body
         return 'malformed'
@@ -2414,11 +2415,9 @@
 #   macro '#', ast, (node) ->
 #     node.type = 'comment'
 #     node
-# labelComments = (ast) ->
-#   typedMacro 'comment', ast, (node, words) ->
-#     for word in words
-#       word.label = 'comment' unless word.label in ['param', 'recurse']
-# node
+labelComments = (call) ->
+  for term in _terms call
+    term.label = 'comment'

 # Syntax printing to HTML
@@ -4097,7 +4096,7 @@

     empty? (fn [bag]
       (: (Fn (bag item) Bool))
-      (# A bag with no elements.)))
+      (# Whether the bag contains no elements.)))

   list-elem? (macro [what in]
     (: (Fn item (List item) Bool))
@@ -4108,6 +4107,16 @@
       (list-elem? what in)))
   """
   "(elem? 3 {1 2 3})", yes
+
+  # bag-list (instance (Bag List)
+  #   fold (fn [with initial over]
+  #     (fold-list with initial over))
+
+  #   fold (fn [with initial over]
+  #     (fold-list with initial over))
+
+  #   fold (fn [with initial over]
+  #     (fold-list with initial over)))

   # TODO: support matching with the same name
   #       to implement this we need the iife to take as arguments all variables


Added support for consts

Added support for consts

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -134,7 +134,8 @@
       ['keyword', token in keywords]
       ['numerical', /^-?\d+/.test(token)]
       #['typename', /^[A-Z]/.test token]
-      ['label', /[:;]\w*$/.test token]
+      ['label', /[:]\w*$/.test token]
+      ['const', /[;]\w*$/.test token]
       ['string', /^["']/.test token]
       ['regex', /^\/[^ \/]/.test token]
       ['paren', token in ['(', ')']]
@@ -336,9 +337,7 @@
         exps = inside node
         if isMap node
           compileMap exps
-        if isTuple
-          compileList exps
-        else if isList node
+        else if isTuple(node) or isList(node)
           compileList exps
         else
           [op, args...] = exps
@@ -351,16 +350,18 @@
             fn = compileImpl op
             # TODO: this expandMacro doesn't make sense
             "#{fn}(#{(args.map compileImpl).join ', '})"
+      else if node.label is 'const'
+        compileConst node
       else if node.token.match /^"/
         node.token
       else
         validIdentifier node.token

 isList = (node) ->
-  Array.isArray(node) and (node[0].token is '[')
+  Array.isArray(node) and node[0].token is '['

 isTuple = (node) ->
-  Array.isArray(node) and (node[0].token is '{')
+  Array.isArray(node) and node[0].token is '{'

 isMap = (node) ->
   isTuple(node) and (elems = inside node; elems.length > 0) and elems[0].label is 'label'
@@ -369,6 +370,9 @@
   [constr, args...] = elems
   items = args.map(compileImpl).join ', '
   "({\"#{constr.token}\": [#{items}]})"
+
+compileConst = (token) ->
+  "({'#{token.token}': true})"

 compileList = (elems) ->
   # "[#{elems.join ', '}]"
@@ -517,7 +521,10 @@
 patternMatchingRules = [
   (pattern) ->
     trigger: pattern.label is 'numerical'
-    cond: (exp) -> ["#{exp} == #{pattern.token}"]
+    cond: (exp) -> ["#{exp}" + " == #{pattern.token}"]
+  (pattern) ->
+    trigger: pattern.label is 'const'
+    cond: (exp) -> ["'#{pattern.token}' in #{exp}"]
   (pattern) ->
     trigger: pattern.label is 'name'
     assignTo: (exp) ->
@@ -536,28 +543,11 @@
   #       [tail, "#{exp}.tail"]
   #     ]
   (pattern) ->
+    # expect lists from here on
     if not Array.isArray pattern
       throw new Error "pattern match expected pattern but saw token #{pattern.token}"
-    elems = inside pattern
-    hasSplat = no
-    requiredElems = 0
-    for elem in elems
-      if isSplat elem
-        hasSplat = yes
-      else
-        requiredElems++
-    trigger: not isMap pattern
-    cache: true
-    cond: (exp) -> ["$sequenceSize(#{exp}) #{if hasSplat then '>=' else '=='} #{requiredElems}"]
-    assignTo: (exp) ->
-      recurse: (for elem, i in elems
-        if isSplat elem
-          [elem, "$sequenceSplat(#{i}, #{elems.length - i - 1}, #{exp})"]
-        else
-          [elem, "$sequenceAt(#{i}, #{exp})"])
-  (pattern) ->
     [constr, elems...] = inside pattern
-    label = "'#{constr.token}'"
+    label = "'#{constr.token}'" if constr
     trigger: isMap pattern
     cache: true
     cacheMore: (exp) -> if elems.length > 1 then ["#{exp}[#{label}]"] else []
@@ -567,6 +557,24 @@
       value ?= "#{exp}[#{label}]"
       recurse: (for elem, i in elems
         [elem, "#{value}[#{i}]"])
+  (pattern) ->
+    elems = inside pattern
+    hasSplat = no
+    requiredElems = 0
+    for elem in elems
+      if isSplat elem
+        hasSplat = yes
+      else
+        requiredElems++
+    trigger: isList pattern
+    cache: true
+    cond: (exp) -> ["$sequenceSize(#{exp}) #{if hasSplat then '>=' else '=='} #{requiredElems}"]
+    assignTo: (exp) ->
+      recurse: (for elem, i in elems
+        if isSplat elem
+          [elem, "$sequenceSplat(#{i}, #{elems.length - i - 1}, #{exp})"]
+        else
+          [elem, "$sequenceAt(#{i}, #{exp})"])
   (pattern) ->
     [constr, elems...] = inside pattern
     label = "'#{constr.token}'"


Working if statement

Working if statement

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1268,14 +1268,25 @@

       # Call
       else if (Array.isArray expression) and expression.length > 2
-        [op, arg] = inside expression
-        # assume call.length is 2
-        returnName = freshName nameIndex
-        [s1, nextIndex, op] = infer context, op, nameIndex + 1
-        [s2, nextIndex, arg] = infer (subContext s1, context), arg, nextIndex
-        s3 = unify (subExp s2, op.tea), [arg.tea, returnName]
-        expression.tea = (subExp s3, returnName)
-        [(concat s3, s2, s1), nextIndex, expression]
+        [op..., arg] = inside expression
+        [s, nextIndex, tea] = inferCall context, [op, arg], nameIndex
+        expression.tea = tea
+        [s, nextIndex, expression]
+
+inferCall = (context, pair, nameIndex) ->
+  [ops, arg] = pair
+  returnName = freshName nameIndex
+  if ops.length >= 2
+    [op..., prevArg] = ops
+    [s1, nextIndex, opTea] = inferCall context, [op, prevArg], nameIndex + 1
+  else
+    [op] = ops
+    [s1, nextIndex, op] = infer context, op, nameIndex + 1
+    opTea = op.tea
+  [s2, nextIndex, arg] = infer (subContext s1, context), arg, nextIndex
+  s3 = unify (subExp s2, opTea), [arg.tea, returnName]
+  [(concat s3, s2, s1), nextIndex, (subExp s3, returnName)]
+


StudyCollection now correctly uses StudyModel and API url

--- frontend/coffeescript/app/collections/study.coffee
+++ frontend/coffeescript/app/collections/study.coffee
@@ -1,12 +1,14 @@
-define( [ 'backbone' ],
+define( [ 'backbone',
+          'app/models/study' ],

-( Backbone ) ->
+( Backbone, StudyModel ) ->

     class StudyCollection extends Backbone.Collection
-        initialize: ->
-            @url = '/api/v1/repos/'
+        model: StudyModel
+        url: '/api/v1/studies/'

         parse: ( response ) ->
             return response.objects

+    return StudyCollection
 )


handle negative and inverse expressions

handle negative and inverse expressions

--- chuck.coffee
+++ chuck.coffee
@@ -1,10 +1,14 @@
 # chuck, because, why not?

-comparators = /\|\||&&|!|!=|==|!==|===|<|<=|>|>=/
+comparators = /\|\||&&|!=|==|!==|===|<|<=|>|>=/
 assigners = /\=|\+=|-=|\*=|\/=|\+\+|--/
 operators = /\+|-=|\*|\//

 calculateExpressionComplexity = (expression) ->
+  if expression?.negative?
+    return calculateExpressionComplexity expression.negative
+  if expression?.inverse?
+    return calculateExpressionComplexity expression.inverse
   if comparators.test expression?.operator
     return 1 + calculateExpressionComplexity expression.left +
                calculateExpressionComplexity expression.right


try, catch the error

try, catch the error

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -181,8 +181,11 @@
         else
           new Date lastItemData.meta.createdAt

-        if isNan lastDate.getTime()
-          _rollbar.push msg:"bucket corrupted timestamp", item:lastItemData
+        try
+          if isNaN lastDate.getTime()
+            _rollbar.push msg:"bucket corrupted timestamp", item:lastItemData
+        catch error
+          console.log "bucket corrupted timestamp error"
       else
         lastDate = new Date


removed iOS specific code.

removed iOS specific code.

--- src/bridges/Bridge.coffee
+++ src/bridges/Bridge.coffee
@@ -13,8 +13,8 @@
       TizenBridge
       WebBridge
       AndroidBridge
+      WebsocketBridge
       JSCoreBridge
-      WebsocketBridge
     ]

     unless @bestNativeBridge?
@@ -26,11 +26,6 @@

   constructor: ->

-  bindNativeBridge: ->
-    # reset the bestNativeBridge and search again.. since now the native side is ready
-    Bridge.bestNativeBridge = null;
-    steroids.nativeBridge = Bridge.getBestNativeBridge()
-
   sendMessageToNative: (options={})->
     throw "ERROR: Bridge#sendMessageToNative not overridden by subclass!"

@@ -39,11 +34,7 @@

   # Handles incoming API messages
   message_handler: (e)=>
-    msg = if e.constructor.name == "String"
-      JSON.parse(e)
-    else
-      # iOS parameters come as objects and not as Strings
-      e
+    msg = JSON.parse(e)

     if msg?.callback?
       if @callbacks[msg.callback]?


remove useless ontimeout/onerror

remove useless ontimeout/onerror

--- background_scripts/completion_search.coffee
+++ background_scripts/completion_search.coffee
@@ -48,18 +48,12 @@
     xhr = new XMLHttpRequest()
     xhr.open "GET", url, true
     xhr.timeout = 2500
-    finished = 0
-    # Note(gdh1995): Maybe the 2 events below are not needed, but I just leave it unchanged for robustness
-    xhr.ontimeout = xhr.onerror = ->
-      finished++
-      if finished == 1
-        callback null
-
+    # According to https://xhr.spec.whatwg.org/#request-error-steps,
+    # readystatechange always gets called whether a request succeeds or not,
+    # and the `readyState == 4` means an associated `state` is "done", which is true even if any error happens
     xhr.onreadystatechange = ->
       if xhr.readyState == 4
-        finished++
-        if finished == 1
-          callback if xhr.status == 200 then xhr else null
+        callback if xhr.status == 200 then xhr else null
     xhr.send()

   # Look up the completion engine for this searchUrl.  Because of DummyCompletionEngine, we know there will


put back the ugly ipython websocket reconnect code

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -4320,9 +4320,9 @@
                             # then the thing hangs and reconnecting then doesn't work (the user has to do a full frame refresh).
                             # TODO: understand this and fix it properly.  This is entirely related to the complicated proxy server
                             # stuff in SMC, not sync!
-                            #websocket_reconnect = () =>
-                            #    @nb?.kernel?.start_channels()
-                            #@_reconnect_interval = setInterval(websocket_reconnect, 15000)
+                            websocket_reconnect = () =>
+                                @nb?.kernel?.start_channels()
+                            @_reconnect_interval = setInterval(websocket_reconnect, 15000)

                             @status()
                             cb()


Cleanup sticking logic

Cleanup sticking logic

--- resources/assets/coffee/_classes/forum-topic-reply.coffee
+++ resources/assets/coffee/_classes/forum-topic-reply.coffee
@@ -136,37 +136,14 @@
       @forum.endPost().scrollIntoView()

-  stick: =>
-    return if @getState('sticking') == '1'
+  stickOrUnstick: (_e, target) =>
+    stick =
+      if osu.isMobile()
+        @box[0]?.dataset.state == 'active'
+      else
+        target == 'forum-topic-reply'

-    @setState 'sticking', '1'
-
-    $input = @$input()
-    inputFocused = $input.is(':focus')
-
-    @fixedBar[0].insertBefore(@box[0], @fixedBar[0].firstChild)
-
-    $input.focus() if inputFocused
-
-
-  unstick: (e) =>
-    return unless @getState('sticking') == '1'
-
-    @deleteState 'sticking'
-
-    @container[0].insertBefore(@box[0], @container[0].firstChild)
-
-
-  stickOrUnstick: (_e, target) =>
-    if osu.isMobile()
-      if @box[0]?.dataset.state == 'active'
-        @stick()
-      else
-        @unstick()
-    else if target == 'forum-topic-reply'
-      @stick()
-    else
-      @unstick()
+    @toggleStick(stick)

   toggle: =>
@@ -180,3 +157,23 @@
   toggleDeactivate: (e) =>
     e.stopPropagation()
     @deactivate()
+
+
+  toggleStick: (stick) =>
+    isSticking = @getState('sticking') == '1'
+
+    return if stick == isSticking
+
+    if stick
+      @setState 'sticking', '1'
+      target = @fixedBar[0]
+    else
+      @deleteState 'sticking'
+      target = @container[0]
+
+    $input = @$input()
+    inputFocused = $input.is(':focus')
+
+    target.insertBefore(@box[0], target.firstChild)
+
+    $input.focus() if inputFocused


Team: fix typo

Team: fix typo

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -71,10 +71,10 @@
       #   title     : 'Community Manager'
       ,
         username  : 'dicle'
-        title     : 'Business Analysist'
+        title     : 'Business Analyst'
       ,
         username  : 'niltuncer'
-        title     : 'Business Analysist'
+        title     : 'Business Analyst'
       ,
         username  : 'zeynep'
         title     : 'Finance Assistant'


this, arguments and local vars are initialised when calling a closure

--- src/Evaluator.coffee
+++ src/Evaluator.coffee
@@ -1,6 +1,6 @@
 'use strict'

-{ NUMBER, STRING, NULL } = jinter
+{ NUMBER, STRING, NULL, UNDEFINED } = jinter

 ev = (exp, env) ->
@@ -54,16 +54,23 @@
 Nodes['CallExpression'] = (exp, env) ->
 	closure = ev exp.callee

-	env = exp.arguments.reduce (argument, env, index) =>
-		env.con 'this', NULL
+	# this
+	newEnv = closure.env.con 'this', NULL

+	# arguments
+	newEnv = exp.arguments.reduce (argument, resultingEnv, index) ->
 		name = closure.formalArguments[index]
-		value = ev argument, @env
+		value = ev argument, env

-		env.con name, value
-	, @env
+		resultingEnv.con name, value
+	, newEnv

-	ev closure.body, closure.env
+	# vars
+	newEnv = closure.body.vars.reduce (name, resultingEnv) ->
+		resultingEnv.con name, UNDEFINED
+	, newEnv
+
+	ev closure.body, newEnv

 Nodes['FunctionDeclaration'] = (exp, env) ->
@@ -71,6 +78,14 @@
 		formalArgument.name

 	new FUNCTION exp.body, env, formalArguments
+
+
+Nodes['FunctionExpression'] = (exp, env) ->
+	formalArguments = exp.params.map (formalArgument) ->
+		formalArgument.name
+
+	new FUNCTION exp.body, env, formalArguments
+

 Nodes['Program'] = (exp, env) ->


[review] remove comment

[review] remove comment

--- client/app/router.coffee
+++ client/app/router.coffee
@@ -124,10 +124,6 @@

     showPopover: (options={}) =>
-        # @TODO Event creation is a typical core feature of the calendar
-        # app, this part should be moved directly into the app module, and
-        # managed
-        # with event handlers
         options.document = window.document
         options.parentView = @mainView
         options.start ?= helpers.getStartOfDay().hour(10)


Be paren free!

Be paren free!

--- client/edit.views.coffee
+++ client/edit.views.coffee
@@ -36,7 +36,7 @@
       @model.set theme: @themeEl.val()

     # $(@el).find('textarea').listenInput =>
-    @editor.getSession().selection.on('changeCursor', =>
+    @editor.getSession().selection.on 'changeCursor', =>
       selection = @editor.getSelectionRange()

       # Get code before selection
@@ -66,7 +66,6 @@
         data["event"] = "onSlideChange"
         data["slide"] = @currentSlide
         $("iframe").get(0).contentWindow.postMessage JSON.stringify(data), "*"
-    )

     @model.bind "initialfetch", (e) =>
       @setEditorContents @model.get "code"


Correção do problema abaixo que só ocorre no Chrome no Windows:

Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.image.onload @ avatar.coffee:21
avatar.coffee:12 Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.image.onload @ avatar.coffee:12

--- client/lib/avatar.coffee
+++ client/lib/avatar.coffee
@@ -11,7 +11,7 @@
 @getAvatarAsPng = (username, cb) ->
 	image = new Image
 	image.src = getAvatarUrlFromUsername(username)
-
+	image.setAttribute('crossOrigin', 'anonymous')
 	image.onload = ->
 		canvas = document.createElement('canvas')
 		canvas.width = image.width


KiteSubscription: remove fetchSubscription method

--- workers/social/lib/social/models/kite.coffee
+++ workers/social/lib/social/models/kite.coffee
@@ -31,8 +31,6 @@
           (signature Object, Function)
         deleteKite:
           (signature Function)
-        fetchSubscriptions:
-          (signature Function)
         fetchPlans:
           (signature Function)
         removePlan:
@@ -50,7 +48,7 @@
         return  callback new KodingError "kite couldn't saved" if err
         account = client.connection.delegate
         account.addKite (err, res)->
-          return  callback new KodingError "kite couldn't added to Account" if err
+          return  callback new KodingError "kite couldn't added to account" if err
           callback null, kite

   @list: permit 'list kites',


fix hat wobble

fix hat wobble

--- scripts/celery-man.coffee
+++ scripts/celery-man.coffee
@@ -1,5 +1,5 @@
 # Paul Rudd isn't a douchebag
-#
+#
 # load up celery man please
 # can i get a printout of oyster smiling?
 # do we have any new sequences?
@@ -8,12 +8,12 @@
 module.exports = (robot) ->
   robot.respond /load up celery man please/i, (msg) ->
     msg.send "http://www.simpleindustries.com/wp-content/uploads/2010/05/paul_sequence1.png"
-
+
   robot.respond /.*printout of oyster smiling.*/i, (msg) ->
     msg.send "http://30.media.tumblr.com/tumblr_lkg4w5D1Gi1qc39ego1_500.jpg"
-
+
   robot.hear /new sequences/i, (msg) ->
     msg.send "I have a BETA sequence I've been working on. Would you like to see it?"

-  robot.respond /hat wobble|flarhgunnstow/i, (msg) ->
+  robot.respond /.*(hat wobble|flarhgunnstow).*/i, (msg) ->
     msg.send "Yes.\nhttp://29.media.tumblr.com/tumblr_l2e4bbhrzl1qzad9ao1_400.gif"


minor message fix

minor message fix

--- jsapp/xlform_model_view/view.rowDetail.coffee
+++ jsapp/xlform_model_view/view.rowDetail.coffee
@@ -145,7 +145,7 @@
       @$el.addClass("card__settings__fields--active")
       """

-        Constraint message:
+        Error Message:

           <input type="text" name="#{@model.key}" id="#{@cid}" class="text" />


don't override specified onclick behavior

Former-commit-id: 2549f25c7d48154f04c360ccf538fba1b7876a2f [formerly b07970a5f8c12c7f9ba4d605d9c02d7f326e2d0d] [formerly 93da2035192cb292fc6b64c2f4bd4d094b4aacb7 [formerly c913e8200ce1fcdd0439083dd4597c13b1b56206]] [formerly 08cfb16a28899816ff34bb47737dbf1baae7a663 [formerly ddf8a51603e69d7dd06af1942eda638736e6a603] [formerly 90322f30b2172bcf1c1020c85a0ffbc4d51866b5 [formerly 90322f30b2172bcf1c1020c85a0ffbc4d51866b5 [formerly 76fad26080ee67cd05606d997b8b116e9b5ba1b6]]]]
Former-commit-id: 567de0a2ab21041eccabd21109c2226c13c4383e [formerly c96684aae395db2d4af1738cabf759cd25f2bb3b] [formerly 3635dd54554fd252081a0d157751f2babd47e216 [formerly 55bd0fcc25ed9f5a0e7fb0fae378c511a646034a]]
Former-commit-id: a37f398902684f8084caa39a8b87aaf47cfc4c64 [formerly 62dad0b42306dbc18a91128c7a4123b8ef092034]
Former-commit-id: aeed412fabd14fd7245486cbd917b87a0a154f14
Former-commit-id: 3fb7ba76c7c354024b81967e0c79b27060749a85

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -7,25 +7,27 @@
 old_A = A
 window.A = React.createClass
   render : ->
-    @transferPropsTo old_A
-
-      onClick: (event) =>
-        event.preventDefault()
-        if Backbone.history && Backbone.history._hasPushState
-          href = @getDOMNode().getAttribute('href') # use getAttribute rather than .href so we
-                                                    # can easily check relative vs absolute url
-
-          is_external_link = href.indexOf('//') > -1
-          opened_in_new_tab = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey
-
-          # Allow shift+click for new tabs, etc.
-          if !is_external_link && !opened_in_new_tab
-            event.preventDefault()
-            # Instruct Backbone to trigger routing events
-            window.app_router.navigate href, { trigger : true }
-            return false
-
-      @props.children
+
+    props = @props
+    if @props.href
+      _.defaults @props,
+        onClick: (event) =>
+          event.preventDefault()
+          if Backbone.history?._hasPushState
+            href = @getDOMNode().getAttribute('href') # use getAttribute rather than .href so we
+                                                      # can easily check relative vs absolute url
+
+            is_external_link = href.indexOf('//') > -1
+            opened_in_new_tab = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey
+
+            # Allow shift+click for new tabs, etc.
+            if !is_external_link && !opened_in_new_tab
+              event.preventDefault()
+              # Instruct Backbone to trigger routing events
+              window.app_router.navigate href, { trigger : true }
+              return false
+
+    old_A props, props.children


Fix problem in previous commit which broke gulp "bump" tasks.

--- build/gulpfile.coffee
+++ build/gulpfile.coffee
@@ -215,11 +215,11 @@

 # Generate tasks for bumping project versions and tagging.
 _.each {
-  'bump:patch': 'Bump, commit, and tag the package patch version.'
-  'bump:minor': 'Bump, commit, and tag the package minor version.'
-  'bump:major': 'Bump, commit, and tag the package major version.'
+  patch: 'Bump, commit, and tag the package patch version.'
+  minor: 'Bump, commit, and tag the package minor version.'
+  major: 'Bump, commit, and tag the package major version.'
 }, (description, importance) ->
-  gulp.task importance, description, ->
+  gulp.task 'bump:' + importance, description, ->
     gulp.src ['./package.json']
     .pipe bump({type: importance})
     .pipe gulp.dest './'


Fix brunch test.

Fix brunch test.

--- src/commands/watch.coffee
+++ src/commands/watch.coffee
@@ -148,7 +148,7 @@
     return logger.error "Write failed: #{error}" if error?
     logger.info "compiled in #{Date.now() - startTime}ms"
     watcher.close() unless config.persistent
-    callback
+    callback generatedFiles

 # Restart brunch watcher.
 #


Use better code style.

Use better code style.

--- src/commands/watch.coffee
+++ src/commands/watch.coffee
@@ -12,6 +12,8 @@
     plugin.pattern
   else if plugin.extension
     RegExp "\\.#{plugin.extension}$"
+  else
+    /$.^/
   pattern.test(path)

 getPluginIncludes = (plugins) ->
@@ -136,7 +138,6 @@
     fs_utils.write @fileList, @config, @joinConfig, @minifiers, @start, (error, result) =>
       return logger.error "Write failed: #{error}" if error?
       logger.info "compiled in #{Date.now() - @start}ms"
-      @start = null
       @watcher.close() unless @persistent
       @onCompile result

@@ -146,9 +147,7 @@
       @config     = helpers.loadConfig @options.configPath, @configParams
       @joinConfig = getJoinConfig @config
       plugins     = helpers.getPlugins packages, @config
-      @compilers  = plugins.filter (plugin) ->
-        propIsFunction('compile')(plugin) and
-        Boolean (plugin.pattern or plugin.extension)
+      @compilers  = plugins.filter(propIsFunction 'compile')
       @callbacks  = plugins.filter(propIsFunction 'onCompile')
       @minifiers  = plugins.filter(propIsFunction 'minify')
       @fileList   = new fs_utils.FileList @config


fix(EntryForm): pass license=undefined to EntryForm when creating new entry

--- src/components/App.coffee
+++ src/components/App.coffee
@@ -335,7 +335,7 @@
                   React.createElement EntryForm,
                     ref: 'form'
                     isEdit: form[EDIT.id]?.kvm_flag_id?
-                    license: entries[search.current].license
+                    license: entries[search.current]?.license
                     onSubmit: (data) ->
                       dispatch Actions.saveEntry
                         id          : form[EDIT.id]?.kvm_flag_id


Pinpointing server-side bug in creating comment.³

--- src/api/posts.coffee
+++ src/api/posts.coffee
@@ -57,7 +57,16 @@
 		return

 	logger.debug('commentToPost(id=%s) with comment_tree(id=%s)', parent._id, parent.comment_tree)
-
+	logger.debug('about to create comment object', {
+		author: User.toAuthorObject(me)
+		content: {
+			body: data.content.body
+		}
+		tree: parent.comment_tree
+		parent: parent._id
+		replies_to: null
+		replies_users: null
+	})
 	comment = new Comment({
 		author: User.toAuthorObject(me)
 		content: {


Start of Document Server authorization

Start of Document Server authorization

--- src/code/providers/document-store-provider.coffee
+++ src/code/providers/document-store-provider.coffee
@@ -1,4 +1,7 @@
-{div} = React.DOM
+{div, button} = React.DOM
+
+authorizeUrl  = "http://document-store.herokuapp.com/user/authenticate"
+checkLoginUrl = "http://document-store.herokuapp.com/user/info"

 tr = require '../utils/translate'
 isString = require '../utils/is-string'
@@ -8,8 +11,14 @@

 DocumentStoreAuthorizationDialog = React.createFactory React.createClass
   displayName: 'DocumentStoreAuthorizationDialog'
+
+  authenticate: ->
+    @props.provider.authorize()
+
   render: ->
-    (div {}, "TODO: DocumentStoreAuthorizationDialog")
+    (div {},
+      (button {onClick: @authenticate}, 'Authorization Needed')
+    )

 class DocumentStoreProvider extends ProviderInterface

@@ -26,6 +35,73 @@

   authorized: (@authCallback) ->

+  authorize: ->
+    @_showLoginWindow()
+    @_checkLogin()
+
+  _loginSuccessful: (data) ->
+    if @_loginWindow then @_loginWindow.close()
+    alert("Welcome #{data.name}")
+    @authCallback true
+
+  _checkLogin: ->
+    provider = @
+    $.ajax
+      dataType: 'json'
+      url: checkLoginUrl
+      xhrFields:
+        withCredentials: true
+      success: (data) ->
+        provider._loginSuccessful(data)
+      error: ->
+        # nothing yet
+
+  _loginWindow: null
+
+  _showLoginWindow: ->
+    if @_loginWindow and not @_loginWindow.closed
+      @_loginWindow.focus()
+    else
+
+      computeScreenLocation = (w, h) ->
+        screenLeft = window.screenLeft or screen.left
+        screenTop  = window.screenTop  or screen.top
+        width  = window.innerWidth  or document.documentElement.clientWidth  or screen.width
+        height = window.innerHeight or document.documentElement.clientHeight or screen.height
+
+        left = ((width / 2) - (w / 2)) + screenLeft
+        top = ((height / 2) - (h / 2)) + screenTop
+        return {left, top}
+
+      width = 1000
+      height = 480
+      position = computeScreenLocation width, height
+      windowFeatures = [
+        'width=' + width
+        'height=' + height
+        'top=' + position.top or 200
+        'left=' + position.left or 200
+        'dependent=yes'
+        'resizable=no'
+        'location=no'
+        'dialog=yes'
+        'menubar=no'
+      ]
+
+      @_loginWindow = window.open(authorizeUrl, 'auth', windowFeatures.join())
+
+      pollAction = =>
+        try
+          href = @_loginWindow.location.href
+          if (href is window.location.href)
+            clearInterval poll
+            @_loginWindow.close()
+            @_checkLogin()
+        catch e
+          console.log e
+
+      poll = setInterval pollAction, 200
+
   renderAuthorizationDialog: ->
     (DocumentStoreAuthorizationDialog {provider: @, authCallback: @authCallback})


Tests for formatDay handlebars helper.

Tests for formatDay handlebars helper.

--- src/test/coffeescript/unit/utils/handlebar-helpers.coffee
+++ src/test/coffeescript/unit/utils/handlebar-helpers.coffee
@@ -1,5 +1,13 @@
-define ['ember', 'utils/handlebar-helpers'], (Ember, Helpers) ->
+define ['utils/handlebar-helpers', 'utils/dates'], (Helpers, Dates) ->

 	run: ->
-		test 'hello', ->
-			equal true, true
+		test '4/5/2015', ->
+			day = Dates.parseYearMonthDay([2013, 12, 5])
+			equal Helpers.formatDay(day), '12/5/2013'
+
+		test 'today', ->
+			todayDate = new Date()
+			expected = (todayDate.getUTCMonth() + 1) + '/' + todayDate.getUTCDay() + '/' + todayDate.getUTCFullYear()
+
+			day = Dates.today()
+			equal Helpers.formatDay(day), expected


OverlayTrigger becomes Button/Modal on delete period

--- src/components/course-settings/delete-period.cjsx
+++ src/components/course-settings/delete-period.cjsx
@@ -18,14 +18,21 @@

   getInitialState: ->
     warning: ''
+    showModal: false
+
+  close: ->
+    @setState({showModal: false})
+
+  open: ->
+    @setState({showModal: true})

   performUpdate: ->
     if @isPeriodEmpty()
-      @refs.overlay.hide()
       # tab to be deleted cannot be activeTab unless first, so select previous or first
       @props.selectPreviousTab()
       id = @props.activeTab.id
       PeriodActions.delete(id, @props.courseId)
+      @close()
     else
       @setState(warning: EMPTY_WARNING)

@@ -54,8 +61,13 @@

     <BS.Modal
       {...@props}
-      title={title}
+      show={@state.showModal}
+      onHide={@close}
       className="teacher-edit-period-modal">
+
+      <BS.Modal.Header closeButton>
+        <BS.Modal.Title>{title}</BS.Modal.Title>
+      </BS.Modal.Header>

@@ -72,18 +84,13 @@
         {deleteButton if @isPeriodEmpty()}

-
-
     </BS.Modal>

   render: ->
-    <BS.OverlayTrigger
-      ref='overlay'
-      rootClose={true}
-      trigger='click'
-      overlay={@renderForm()}>
-        <BS.Button bsStyle='link' className='edit-period'>
-
-          Delete <CourseGroupingLabel courseId={@props.courseId}/>
-        </BS.Button>
-    </BS.OverlayTrigger>
+
+      <BS.Button onClick={@open} bsStyle='link' className='edit-period'>
+
+        Delete <CourseGroupingLabel courseId={@props.courseId}/>
+      </BS.Button>
+      {@renderForm()}
+


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -25,7 +25,9 @@
       min: 0
       max: 100

-  player.cl
+  player.clamp
+    score:
+
   </pre></code>

   @name clamp


reset all turtles

reset all turtles

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -63,7 +63,8 @@
     world.call(action['do'], action) if action?

   reset: (world, args) ->
-    turtles = world.get('up')
+    turtles = world.find_parent 'turtles'
+    assert turtles, "can't find parent"
     console.log 'reset', world, turtles
     world.put 'program_counter', 0
     turtles.map_children (child) ->


stack-editor: show correct buttons

stack-editor: show correct buttons

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -455,34 +455,50 @@
       # TMS-1919: This needs to be reimplemented, once we have multiple
       # stacktemplates set for a team this will be broken ~ GG

-      unless hasGroupTemplates
-
-        # this means that the created stack template will be assigned as team
-        # default, and it will be shared with the group.
+      if hasStack
         if canEditGroup
-          @handleSetDefaultTemplate completed = no
-
-          # this is confusing. if there are currently 20 members using this stack
-          # their stack shouldn't be changed to this one automatically
-          # they should see a notification that says "this stack has been deleted by Gokmen"
-          # new users get the default stack should. and this button shouldn't be there each time
-          # i make a new one, it should be on the list-menu.
-          @outputView.addAndWarn '''
-            Your stack script has been successfully saved and all your new team
-            members now will see this stack by default. Existing users
-            of the previous default-stack will be notified that default-stack has
-            changed.
-
-            You can now close this window or continue working with your stack.
-          '''
-          return
-
-      if hasStack
+          # admin is editing a team stack
+          if stackTemplate.isDefault
+            @handleSetDefaultTemplate completed = no
+            @outputView.addAndWarn '''
+              Your stack script has been successfully saved and all your new team
+              members now will see this stack by default. Existing users
+              of the previous default-stack will be notified that default-stack has
+              changed.
+
+              You can now close this window or continue working with your stack.
+            '''
+          # admin is editing a private stack
+          else
+            @afterProcessTemplate 'maketeamdefault'
+
+        # since this is an existing stack, show renit buttons and update
+        # sidebar no matter what.
         @afterProcessTemplate 'reinit'
-      else if canEditGroup
-        @afterProcessTemplate 'maketeamdefault'
+        computeController.checkGroupStacks()
+
       else
-        @afterProcessTemplate 'provision'
+        # admin is creating a new stack
+        if canEditGroup
+          if hasGroupTemplates
+            @afterProcessTemplate 'maketeamdefault'
+            @afterProcessTemplate 'provision'
+            computeController.checkGroupStacks()
+          else
+            @handleSetDefaultTemplate completed = no
+            @outputView.addAndWarn '''
+              Your stack script has been successfully saved and all your new team
+              members now will see this stack by default. Existing users
+              of the previous default-stack will be notified that default-stack has
+              changed.
+
+              You can now close this window or continue working with your stack.
+            '''
+            computeController.checkGroupStacks()
+        # member is creating a new stack
+        else
+          @afterProcessTemplate 'provision'
+          computeController.checkGroupStacks()

   checkAndBootstrapCredentials: (callback) ->


test/dashboard: update logoutTeam function

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -1165,11 +1165,19 @@
       .waitForElementVisible adminModal, 20000
       .click                 closeButton

-  logoutTeam: (browser) ->
-
-    logoutLink = '.avatararea-popup.team a[href="/Logout"]'
-
-    helpers.openAvatarAreaModal(browser, yes)
+
+  logoutTeam: (browser, callback) ->
+
+    browser
+      .click '#main-sidebar'
+      .waitForElementVisible '#kdmaincontainer.with-sidebar #main-sidebar .logo-wrapper .team-name', 20000
+      .click '#kdmaincontainer.with-sidebar #main-sidebar .logo-wrapper .team-name'
+      .waitForElementVisible '.SidebarMenu.kdcontextmenu .kdlistview-contextmenu.default', 20000
+      .waitForElementVisible '.SidebarMenu.kdcontextmenu .kdlistitemview-contextitem.default',2000
+      .click '.SidebarMenu.kdcontextmenu .kdlistitemview-contextitem.default:nth-of-type(4)'
+
+     .pause 2000, -> callback()
+

     browser
      .waitForElementVisible logoutLink, 20000