queue api change to send other channels

queue api change to send other channels

--- node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
+++ node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
@@ -23,7 +23,6 @@
           @kiteList[channelName] = []
         if kiteName not in @kiteList[channelName]
           @kiteList[channelName].push kiteName
-        console.log "clients", @kiteList
         if cb
           cb false, true

@@ -31,13 +30,12 @@
         @mergenData.lpop @getChannelNameForGenericName(), (err, val)=>
           if not val
             return
-          console.log ">>>>>", err, val
           try
             val = JSON.parse val
           catch E
-            console.log "couldnt parse json"
+            console.log "couldnt parse json", val
             return
-          console.log "!!!!!!!!!!!!! lpop lpop >>>>>>", val
+          # console.log "!!!!!!!!!!!!! lpop lpop >>>>>>", val
           @receivedCommand val.from, val.id, val.cmd, val.args

   getChannelNameForKite:(genericName, uniqueName)->
@@ -57,7 +55,7 @@
   receivedCommand: (from, cmdid, cmd, args)->
     if cmd.indexOf('reply-') > -1
       cmdid = cmd.split('reply-')[1]
-      console.log ">>>> reply cmd", cmd, args
+      #console.log ">>>> reply cmd", cmd, args
       @waitingReplies[cmdid]?.call @, false, {args:args, from:from}
       return

@@ -76,10 +74,15 @@
     @sendCommand(from, "reply-#{cmdid}", ['function not found', false])

-  queueCommand:(cmd, args, cb=false)->
+  queueCommand:(channel, cmd, args, cb=false)->
+
+    if not channel
+      channel = @getChannelNameForGenericName()
+    else
+      channel = @getChannelNameForGenericName(channel)
+
     # sendCommand broadcasts, with this one, we first push to queue
     # then broadcast and one of them consumes
-    console.log "args", args
     @callbackid++
     data = {
       cmd: cmd,
@@ -92,9 +95,9 @@
       @waitingReplies[@callbackid] = cb

     datastr = JSON.stringify(data)
-    @mergenData.rpush "#{@getChannelNameForGenericName()}", datastr, ()=>
-      console.log "pushed to ", @getChannelNameForGenericName(), datastr
-      @sendCommand @getChannelNameForGenericName(), "kite.consume", datastr
+    @mergenData.rpush channel, datastr, ()=>
+      #console.log "pushed to ", @getChannelNameForGenericName(), datastr
+      @sendCommand channel, "kite.consume", datastr

   sendCommand: (channel, cmd, args, cb=false)->
     # this broadcasts command to the said channel
@@ -114,8 +117,6 @@
     @mergen.on "error", (err)->
       console.log("Error ",err)

-    # coffeescript gives Users/aybarsbadur/ping.kite/index.coffee:105:53: error: unexpected CALL_END
-    # if these are on one line idk why
     subscribeToChannelNames = [@getChannelNameForAll(), @getChannelNameForGenericName(), @getChannelNameForKite()]

     for chan in subscribeToChannelNames
@@ -129,7 +130,7 @@
     # we have to wait for all before we emit ready
     @mergen.on "subscribe", (channel, count)->
       subscribeToChannelNames = _.without(subscribeToChannelNames, channel)
-      console.log ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> connected to channel", channel, subscribeToChannelNames
+      #console.log ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> connected to channel", channel, subscribeToChannelNames

     # we have a second connection because, redis client in node doesnt send any commands
     # when it enters subscribe mode, though redis docs says you can publish if you are in subcribe
@@ -152,11 +153,8 @@

     @sendCommand @getChannelNameForGenericName(), "kite.join", [@getChannelNameForGenericName(), @getChannelNameForKite()]

-      # @queueCommand "hello", ["world"], (ret)->
-      #   console.log "queue command returned"
-
     startQueueChecker = ()=>
-      setInterval ()=>
+      startQueueCheckerInterval = setInterval ()=>
         @commands['kite.consume']()
       , 10

@@ -182,8 +180,8 @@
     stack    : err.stack

   group: (groupName, cmd, args, callback)->
-    console.log "group:::", groupName, cmd, args, callback
-    console.log "publishing to:", @communicator.getGroupName(groupName)
+    # console.log "group:::", groupName, cmd, args, callback
+    # console.log "publishing to:", @communicator.getGroupName(groupName)
     @communicator.sendCommand @communicator.getGroupName(groupName), cmd, args, callback

   everyone: (cmd, args, callback)->
@@ -196,8 +194,8 @@
     channame = chanName
     @communicator.sendCommand channame, cmd, args, callback

-  queue: (cmd, args, callback)->
-    @communicator.queueCommand cmd, args, callback
+  queue: (channel, cmd, args, callback)->
+    @communicator.queueCommand channel, cmd, args, callback

   constructor:(@kiteName, @api, @publicKey)->
@@ -325,7 +323,7 @@
     @api._leave? messageJson

   join:(messageData)->
-    console.log "auth.join", messageData
+    #console.log "auth.join", messageData
     {routingKey, username, correlationName, serviceGenericName} = messageData
     console.log "#{username}@#{correlationName} has joined to service #{serviceGenericName}"
     @clients[routingKey] = username
@@ -406,7 +404,6 @@
         @getRabbitMqResourceName(), @api, mergen_host, mergen_port)

     @communicator.connectToMessageBus ()=>
-      console.log "emitting ready"
       checkrabbit = setInterval ()=>
         if rabbitReady
           clearInterval checkrabbit


Node Modules Koding: replace daisy with async.series

--- node_modules_koding/koding-zips-importer/src/zips.coffee
+++ node_modules_koding/koding-zips-importer/src/zips.coffee
@@ -1,9 +1,9 @@
 fs            = require 'fs'
 csv           = require 'csv'
+async         = require 'async'
 nodePath      = require 'path'
 {Stream}      = require 'stream'
 {MongoClient} = require 'mongodb'
-{daisy}       = require 'sinkrow'

 fetchDb = (mongo, callback) ->
@@ -35,9 +35,9 @@
     queue = []

     if dropFirst
-      queue.push -> collection.drop -> queue.next()
+      queue.push (next) -> collection.drop -> next()

-    queue.push ->
+    queue.push (next) ->
       csv()
         .from.path(path, { delimiter, encoding })
         .to.stream(stream)
@@ -64,11 +64,9 @@
           collection.insert doc, (err) ->
             return stream.emit 'error', err  if err
         )
-        .on 'close', -> queue.next()
+        .on 'close', -> next()

-    queue.push ->
+    async.series queue, ->
       collection.createIndex 'zip'

-    daisy queue
-
   return stream


Fix/update

Fix/update

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -341,52 +341,37 @@
 work_with_site = () ->
     page("\nРабота с сайтом, отправка решений и просмотр моих комментариев", String.raw"""

Работа с сайтом

+
Теоретические материалы

+
По многим темам на сайте представлен некоторый теоретический материал. Прежде чем приступать к решению задач, прочитайте соответствующую теорию.
+        Например, зайдите на уровень 1А (внутри уровня 1), там есть раздел "Арифметические операции", и в этом разделе две ссылки — "Начало работы c питоном и Wing IDE" и "Задачи на арифметические операции". Вот "Начало работы c питоном и Wing IDE" — это соответствующая теория (в данном случае самые основы работы на питоне, включая базовые арифметические операции). Прочитайте эту теорию, прежде чем решать соответствующие задачи. (Конечно, если вы основы своего языка программирования знаете, то нет смысла читать самую базовую теорию, но тогда вы и начинаете, наверное, со следующих тем).

+
Задачи

Основное, что вы будете делать на наших занятиях — это решать задачи. Большинство задач будут требовать написания программы, которая считывает (с клавиатуры или из файла) некоторые данные, по ним вычисляет новые данные, и результат выводит на экран или сохраняет в файл.

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

Пример задачи

Зайдите на уровень 1А (внутри уровня 1). Там есть ссылка "Задачи на арифметические операции". Зайдите туда. Вы увидите список задач: "Дележ яблок - 1", "Дележ яблок - 2", и т.д., до "Разность времен". Это и есть набор задач (коротко говорят "контест"), которые вам предлагается прорешать для закрепления знаний по первой теме курса — арифметическим операциям.

Все названия задач — ссылки, по ним открывается собственно задача. Откройте задачу "Дележ яблок - 1". Вы видите ее условие: "N школьников делят K яблок поровну, неделящийся остаток остается в корзинке. Сколько яблок достанется каждому школьнику? Программа получает на вход числа N и K и должна вывести искомое количество яблок." Ниже идет пример: при вводе данных "3 14" программа должна вывести "4".

-
Условие задачи четко описывает, что должна делать ваша программа: для любых значений N и K она должна вычислить и вывести ответ на задачу. В более серьезных задачах в условии всегда указываются ограничения на допустимые N и K, но в задачах для начинающих на этом сайте считается, что N и K "разумные" (в частности, помещаются в тип данных integer в паскале); в "разумность" в данном случае входит и то, что оба числа положительны.

+
Условие задачи четко описывает, что должна делать ваша программа: для любых значений N и K она должна вычислить и вывести ответ на задачу. В более серьезных задачах в условии всегда указываются ограничения на допустимые N и K, но в задачах для начинающих на этом сайте считается, что N и K "разумные" (в частности, помещаются в тип данных int в C/C++); в "разумность" в данном случае входит и то, что оба числа положительны.

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

Написание решения

         Данная задача решается достаточно легко. Нам надо, конечно, завести две переменные, N и K, считать их с клавиатуры, и вывести неполное частное от деления K на N (обратите внимание, именно K на N, а не N на K!). Программа будет выглядеть так:
-

-

Паскаль	Питон

-

-
{$mode delphi}
-        var n,k:integer;
-        begin
-        read(n,k);
-        writeln(k div n);
-        end.

-

n = int(input())
         k = int(input())
         print(k // n)

-

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

-
(паскаль) Написано {$mode delphi}. Пока вы не знаете, какие разные режимы компилятора бывают, всегда пишите эту конструкцию. Потом, когда узнаете, сможете выбирать, какой режим компилятора вам нужен.

-
(паскаль) Не используется конструкция program ...;. В современном программировании она не нужна вообще.

-
(паскаль) Вся программа пишется маленькими буквами. Поскольку в Pascal все равно, использовать маленькие или заглавные буквы, то сейчас принято использовать маленькие.

Мы не выводим никаких "приглашений" типа "введите N". В условии задачи четко написано, что именно наша программа должна выводить на экран, и там ничего не сказано про то, что надо выводить приглашения. Поэтому любой лишний вывод на экран будет рассмотрен как нарушение формата вывода.

Нет никаких "проверок на дурака", например, нет проверки "а вдруг N=0?", или "а вдруг ввели не число?". Аналогично предыдущему, это в наших задачах не требуется. В более серьезных задачах в условии будут стоять ограничения на допустимые значения N, и никто не будет тестировать вашу программу с N, не удовлетворяющим этим ограничениям. В этой задаче считается, что N>0 всегда.

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

-
Аналогично, мы не ставим в конце программы пустой readln; или readkey;, или input() в питоне, или getch(); в c++ или другую задержку работы. Это тоже не требуется; информация, выведенная на экран, все равно сохраняется и ее все равно можно посмотреть.

+
Vы не ставим в конце программы пустой input() (или getch(); в c++) или другую задержку работы. Это тоже не требуется; информация, выведенная на экран, все равно сохраняется и ее все равно можно посмотреть.

Мы посчитали и сразу вывели ответ, не сохраняя его в промежуточную переменную. Можно было сохранить:
-
// паскаль
-        ans:=k div n;
-        writeln(ans);
-        // питон
-        ans = k / n
+
ans = k / n
         print(ans)

         В этой простой программе проще вывести сразу, но если выражение будет сложнее, то можно и сохранить.

-
Сохраните эту программу в какой-нибудь файл. Запомните как имя файла, так и ту директорию (папку), куда вы сохранили файл (в free pascal она отображается в нижней части диалога сохранения файла).

+
Сохраните эту программу в какой-нибудь файл. Запомните как имя файла, так и ту директорию (папку), куда вы сохранили файл.

Теперь протестируйте программу. Запустите ее и повводите разные тесты.

@@ -413,7 +398,7 @@

Бывают еще статусы "Зачтено" и "Проигнорировано", но они не ставятся автоматически, поэтому про них ниже.

-
Справа в таблице есть колонка ссылок "Подробнее". Если вы нажмете на такую ссылку, то откроется окошко с подробной информацией о посылке. Во-первых, там будет полный код вашей программы, поэтому вы всегда сможете проверить, отправили ли вы именно то, что хотели; если вы решали задачи из школы, то придя домой, вы сможете посмотреть коды всех ваших программ, и т.д.

+
На кажую строчку в таблице посылок можно нажать, после чего откроется окошко с подробной информацией о посылке. Во-первых, там будет полный код вашей программы, поэтому вы всегда сможете проверить, отправили ли вы именно то, что хотели; если вы решали задачи из школы, то придя домой, вы сможете посмотреть коды всех ваших программ, и т.д.

Во-вторых, в открывшемся окне есть закладка "Протокол", где отображается протокол тестирования вашей программы. Если ваша программа не скомпилировалась (статус "Ошибка компиляции"), то там будет протокол компиляции: будут указаны ошибки, найденные в программе. Если же ваша программа скомпилировалась, то там будет протокол тестирования: по каждому тесту будет приведена информация о том, прошел этот тест (OK) или нет. В задаче "Дележ яблок - 1" всего 6 тестов, поэтому в таблице должно быть 6 строк. Если ваша посылка получила статус "ОК", то все 6 строк будут ОК; иначе какие-то тесты будут иметь другой статус (из названия статуса обычно понятно, что это значит).

@@ -432,8 +417,9 @@

Если к вашей программе у меня будут какие-либо замечания или комментарии, я их напишу, и вы сможете их увидеть. Во-первых, они появятся на сайте в правой колонке в разделе "Комментарии"; во-вторых, в каждой посылке, в окошке, открываемом по ссылке "подробнее", есть специальная закладка "комментарии". Соответственно, как только я прокомментирую вашу посылку, вы увидите комментарий. Обычно я стараюсь просматривать все посылки в течение суток, хотя иногда бывают и бОльшие задержки, до нескольких дней.

Помимо собственно написания комментария, я по каждой вашей посылке принимаю решение — либо я принимаю это решение (пусть и с небольшими замечаниями), либо я потребую от вас это решение переписать с учетом моих замечаний. В первом случае я меняю статус посылки на "Зачтено" (т.е. вы будете видеть именно "Зачтено" вместо "ОК" в таблице с вашими посылками), во втором случае я меняю статус на "Проигнорировано". Соответственно, в первом случае просто примите к сведению замечания, которые я написал (а бывает так, что я и никаких замечаний не пишу), хотя можете и переписать решение, если хотите. Во втором случае обязательно перепишите решение с учетом этих замечаний.

-
-
В сводной таблице решения "ОК" (т.е. которые я еще не посмотрел) отображаются с желтым фоном, решения "Зачтено" (т.е. которые я принял) — с зеленым фоном, проигнорированные решения не учитываются в общем количестве решенных вами задач, просто соответствующая задача в сводной таблице отображаются с красным фоном.

+
(Подробнее про то, за что я игнорирую решения.)

+
+
В сводной таблице решения "ОК" (т.е. которые я еще не посмотрел) отображаются с желтым фоном, решения "Зачтено" (т.е. которые я принял) — с зеленым фоном, проигнорированные решения не учитываются в общем количестве решенных вами задач, просто соответствующая задача в сводной таблице отображаются с синим фоном.

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

@@ -468,7 +454,7 @@

Желтый фон — статус "ОК", т.е. я еще не смотрел эту задачу;

Зеленый фон — статус "Зачтено";

-
Красный фон — статус "Проигнорировано", при этом сама проигнорированная попытка считается неуспешной.

+
Синий фон — статус "Проигнорировано", при этом сама проигнорированная попытка считается неуспешной.

В последних двух колонках таблицы указывается общее количество решенных задач и общее количество неуспешных ("штрафных") попыток (учитываются только те задачи, по которым в итоге была получена правильная программа, проигнорированные попытки не учитываются). Таблица отсортирована по общему количеству решенных задач, а при равном числе решенных задач — по количеству штрафных попыток.


Fix typo

Fix typo

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -642,7 +642,7 @@

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

-
Последние пять года задачи районной олимпиады составляет другое жюри, поэтому в первую очередь смотрите на задачи последних 5 лет, а не более старые.

+
Последние пять лет задачи районной олимпиады составляет другое жюри, поэтому в первую очередь смотрите на задачи последних 5 лет, а не более старые.

Обязательно убедитесь заранее (спросите учителей в школе, чтобы они спросили организаторов районной олимпиады), что вам предоставят нужное ПО (нужный Питон, или Паскаль, или c++ с нужной средой и т.д.). Если есть какие-то проблемы, звоните, но это очень хорошо бы сделать заранее.


Remove leftover template cruft, fix folding in TableOfContents

--- client/message.coffee
+++ client/message.coffee
@@ -123,23 +123,8 @@

 RootHeader.displayName = 'RootHeader'

-Template.registerHelper 'formatTitle', ->
-  formatTitleOrFilename @, false
-
-Template.registerHelper 'formatTitleOrUntitled', ->
-  formatTitleOrFilename @, true
-
-Template.registerHelper 'formatTitleBold', ->
-  formatTitleOrFilename @, false, false, true
-
 Template.registerHelper 'formatTitleOrUntitledBold', ->
   formatTitleOrFilename @, true, false, true
-
-Template.registerHelper 'formatBody', ->
-  formatBody @format, @body
-
-Template.registerHelper 'formatFile', ->
-  formatFile @

 Template.messageBad.helpers
   message: -> Router.current().params.message
@@ -870,14 +855,6 @@
   private: 'panel-info'
   minimized: 'panel-success'
   published: 'panel-primary'
-Template.registerHelper 'messagePanelClass', ->
-  #console.log 'rendering', @_id, @
-  classes = []
-  classes.push mclass = messageClass.call @
-  classes.push panelClass[mclass]
-  if Template.instance().editing?.get()
-    classes.push 'editing'
-  classes.join ' '
 messagePanelClass = (message, editing) ->
   classes = []
   classes.push mclass = messageClass.call message
@@ -886,27 +863,8 @@
     classes.push 'editing'
   classes.join ' '

-Template.registerHelper 'foldedClass', @foldedClass = ->
-  if (not routeHere @_id) and messageFolded.get @_id
-    'folded'
-  else
-    ''
-
-Template.registerHelper 'formatCreator', ->
-  tooltipUpdate()
-  linkToAuthor @group, @creator
-
 Template.registerHelper 'creator', ->
   displayUser @creator
-
-Template.registerHelper 'formatAuthors', formatAuthors = ->
-  tooltipUpdate()
-  a = for own author, date of @authors
-        author = unescapeUser author
-        continue if author == @creator and date.getTime() == @created?.getTime()
-        "#{linkToAuthor @group, author} #{formatDate date, 'on '}"
-  if a.length > 0
-    ', edited by ' + a.join ", "

 KeyboardSelector = React.memo ({messageID, tabindex}) ->
   keyboard = useTracker ->
@@ -1350,6 +1308,11 @@
     Meteor.user()
   , []
   editing = editingMessage message, user
+  folded = useTracker ->
+    (messageFolded.get message._id) and
+    not (routeHere message._id) and           # never fold if top-level message
+    not editing                               # never fold if editing
+  , [message._id, editing]
   creator = useTracker ->
     displayUser message.creator
   , [message.creator]
@@ -1405,14 +1368,14 @@

-

+

           {inner}

       {renderedChildren}

   else
-

+

       {inner}
       {renderedChildren}


un-pend first test of palindrome-products

--- palindrome-products/palindrome_products_test.spec.coffee
+++ palindrome-products/palindrome_products_test.spec.coffee
@@ -2,7 +2,7 @@

 describe "Palindrome", ->

-  xit "largest palindrome from single digit factors", ->
+  it "largest palindrome from single digit factors", ->
     palindromes = new Palindromes({maxFactor: 9})
     palindromes.generate()
     largest = palindromes.largest()


refactor: upgrade exec command

refactor: upgrade exec command

--- lib/commands/exec.coffee
+++ lib/commands/exec.coffee
@@ -15,12 +15,10 @@
       return
     fs.writeFileSync('/tmp/masson_exec', '#!/bin/sh\n# Edit your Masson script to execute')
     exec_sync(env['EDITOR']+' /tmp/masson_exec', {stdio: 'inherit', detached: true})
-    cmd = fs.readFileSync('/tmp/masson_exec', 'utf8');
+    command = fs.readFileSync('/tmp/masson_exec', 'utf8');
     fs.unlinkSync('/tmp/masson_exec')
   else
-    cmd = params.subcommand.map((c) -> "\"#{c}\"").join ' '
-
-  config.nikita.no_ssh = true
+    command = params.subcommand.map((c) -> "\"#{c}\"").join ' '
   write = (msg) -> process.stdout.write msg
   each config.nodes
   .parallel true
@@ -29,27 +27,23 @@
     for tag in params.tags or {}
       [key, value] = tag.split '='
       return callback() if multimatch(node.tags[key] or [], value.split(',')).length is 0
-    n = nikita merge config.nikita
-    n.ssh.open host: node.ip or node.fqdn, node.ssh
-    n.call ({options}, callback) ->
-      isRoot = config.nikita.ssh.username is 'root' or not config.nikita.ssh.username
-      @system.execute
-        relax: true
-        sudo: not isRoot
-        cmd: cmd
-      , (err, {stdout, stderr}) ->
-        write if err
-        then "\x1b[31m#{node.fqdn} (exit code #{err.code})\x1b[39m\n"
-        else "\x1b[32m#{node.fqdn}\x1b[39m\n"
-        write "\n" if stdout.length or stderr.length
-        write "\x1b[36m#{stdout.trim()}\x1b[39m\n" if stdout.length
-        write "\x1b[35m#{stderr.trim()}\x1b[39m\n" if stderr.length
-        write "\n"
-        callback()
-    n.next (err) ->
-      n.ssh.close header: 'SSH Close' #unless params.command is 'prepare' # params.end and
-      n.next ->
-        process.stdout.write err.message if err
+    nikita
+      $ssh: merge host: node.ip or node.fqdn, node.ssh
+    , ->
+      {error, stdout, stderr} = await @execute
+        $debug: true
+        $relax: true
+        $sudo: true
+        command: command
+      write if error
+      then "\x1b[31m#{node.fqdn} (exit code #{error.exit_code})\x1b[39m\n"
+      else "\x1b[32m#{node.fqdn}\x1b[39m\n"
+      write "\n" if stdout.length or stderr.length
+      write "\x1b[36m#{stdout.trim()}\x1b[39m\n" if stdout.length
+      write "\x1b[35m#{stderr.trim()}\x1b[39m\n" if stderr.length
+      write "\n"
+    .then callback
+    .catch callback
   .next (err) ->
     process.stdout.write err.message if err
     # Done


Make a start on a headline refactor

Make a start on a headline refactor

--- server/mixins/indicator_headlines.coffee
+++ server/mixins/indicator_headlines.coffee
@@ -27,101 +27,102 @@
     .subtract('days', 1)
     .format("D MMM YYYY")

-module.exports = {
-  statics:
-    calculateNarrativeRecency: (indicators) ->
-      deferred = Q.defer()
+class HeadlineService
+  constructor: (@indicator) ->

-      async.each indicators, calculateRecency, (err) ->
-        if err?
-          deferred.reject(err)
-        else
-          deferred.resolve()
+  @calculateNarrativeRecency: (indicators) ->
+    deferred = Q.defer()

-      return deferred.promise
+    async.each indicators, calculateRecency, (err) ->
+      if err?
+        deferred.reject(err)
+      else
+        deferred.resolve()

-    roundHeadlineValues: (headlines) ->
+    return deferred.promise
+
+  @roundHeadlineValues: (headlines) ->
+    for headline in headlines
+      unless isNaN(headline.value)
+        headline.value = Math.round(headline.value*10)/10
+
+    return headlines
+
+  convertDataToHeadline: (data) ->
+    Indicator = require('../models/indicator.coffee').model
+
+    data = @parseDateInHeadlines(data)
+    data = Indicator.roundHeadlineValues(data)
+    return data
+
+  parseDateInHeadlines: (headlines) ->
+    xAxis = @indicatorDefinition?.xAxis
+
+    if xAxis?
       for headline in headlines
-        unless isNaN(headline.value)
-          headline.value = Math.round(headline.value*10)/10
+        headline.periodEnd = getPeriodEnd(
+          headline[xAxis],
+          @indicatorDefinition.period
+        )

-      return headlines
+    return headlines

-  methods:
-    convertDataToHeadline: (data) ->
-      Indicator = require('../models/indicator.coffee').model
+  calculateRecencyOfHeadline: ->
+    deferred = Q.defer()

-      data = @parseDateInHeadlines(data)
-      data = Indicator.roundHeadlineValues(data)
-      return data
+    @populatePage().then( =>
+      @getNewestHeadline()
+    ).then( (dataHeadline) =>

-    parseDateInHeadlines: (headlines) ->
-      xAxis = @indicatorDefinition?.xAxis
+      unless dataHeadline?
+        return deferred.resolve("No Data")

-      if xAxis?
-        for headline in headlines
-          headline.periodEnd = getPeriodEnd(
-            headline[xAxis],
-            @indicatorDefinition.period
-          )
+      pageHeadline = @page.headline

-      return headlines
+      unless pageHeadline? && pageHeadline.periodEnd?
+        return deferred.resolve("Out of date")

-    calculateRecencyOfHeadline: ->
-      deferred = Q.defer()
+      if moment(pageHeadline.periodEnd).isBefore(dataHeadline.periodEnd)
+        deferred.resolve("Out of date")
+      else
+        deferred.resolve("Up to date")

-      @populatePage().then( =>
-        @getNewestHeadline()
-      ).then( (dataHeadline) =>
+    ).fail( (err) ->
+      deferred.reject(err)
+    )

-        unless dataHeadline?
-          return deferred.resolve("No Data")
+    return deferred.promise

-        pageHeadline = @page.headline
+  getRecentHeadlines: (amount) ->
+    Indicator = require('../models/indicator.coffee').model

-        unless pageHeadline? && pageHeadline.periodEnd?
-          return deferred.resolve("Out of date")
+    deferred = Q.defer()

-        if moment(pageHeadline.periodEnd).isBefore(dataHeadline.periodEnd)
-          deferred.resolve("Out of date")
-        else
-          deferred.resolve("Up to date")
+    Q.nsend(
+      @, 'getIndicatorData'
+    ).then( (data) =>

-      ).fail( (err) ->
-        deferred.reject(err)
-      )
+      headlineData = data
+      headlineData = _.last(data, amount) if amount?
+      headlines = @convertDataToHeadline(headlineData)

-      return deferred.promise
+      deferred.resolve(headlines.reverse())

-    getRecentHeadlines: (amount) ->
-      Indicator = require('../models/indicator.coffee').model
+    ).fail( (err) ->
+      deferred.reject(err)
+    )

-      deferred = Q.defer()
+    return deferred.promise

-      Q.nsend(
-        @, 'getIndicatorData'
-      ).then( (data) =>
+  getNewestHeadline: ->
+    deferred = Q.defer()

-        headlineData = data
-        headlineData = _.last(data, amount) if amount?
-        headlines = @convertDataToHeadline(headlineData)
+    @getRecentHeadlines(1).then((headlines) ->
+      deferred.resolve headlines[0]
+    ).fail( (err) ->
+      deferred.reject err
+    )

-        deferred.resolve(headlines.reverse())
+    return deferred.promise

-      ).fail( (err) ->
-        deferred.reject(err)
-      )
-
-      return deferred.promise
-
-    getNewestHeadline: ->
-      deferred = Q.defer()
-
-      @getRecentHeadlines(1).then((headlines) ->
-        deferred.resolve headlines[0]
-      ).fail( (err) ->
-        deferred.reject err
-      )
-
-      return deferred.promise
-}
+module.exports = HeadlineService


commandstream: Complete split of initial graph functions

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -120,6 +120,7 @@
     port: port
   }

+# TODO: move all these down into "protocol", along with the inverse functions
 addNode = (payload, componentLib, buffer, index) ->
   nodeName = payload.id
   componentName = payload.component
@@ -162,13 +163,114 @@
   index += writeCmd buffer, index, cmdBuf
   return index

-cmdStreamBuildGraph = (currentNodeId, buffer, index, componentLib, graph) ->
-  messages = protocol.graphToFbpMessages graph, 'default'
-
-  nodeMap = graph.nodeMap
+clearGraph = (payload, buffer, index) ->
+  # Clear existing graph
+  index += writeCmd(buffer, index, cmdFormat.commands.Reset.id)
+  return index
+
+startNetwork = (payload, buffer, index) ->
+  index += writeCmd(buffer, index, cmdFormat.commands.StartNetwork.id)
+  return index
+
+# The following are MicroFlo specific, not part of FBP runtime protocol
+configureDebug = (payload, buffer, index) ->
+  debugLevel = payload.level
+  index += writeCmd(buffer, index, cmdFormat.commands.ConfigureDebug.id, cmdFormat.debugLevels[debugLevel].id)
+  return index
+
+openCommunication = (payload, buffer, index) ->
+  index += writeString(buffer, index, cmdFormat.magicString)
+  return index
+
+closeCommunication = (payload, buffer, index) ->
+  console.log 'b', buffer, index
+  index += writeCmd(buffer, index, cmdFormat.commands.End.id)
+  return index
+
+# TODO: implement the inverse, getting a FBP protocol message from CS
+toCommandStreamBuffer = (message, componentLib, nodeMap, componentMap, buffer, index) ->
+  console.log 'm', message.command, index
+  if message.protocol == 'graph'
+    # TODO: also support removenode/removeedge/removeinitial
+    if message.command == 'clear'
+      index = clearGraph message.payload, buffer, index
+    else if message.command == 'addnode'
+      index = addNode message.payload, componentLib, buffer, index
+    else if message.command == 'addedge'
+      index = addEdge message.payload, componentLib, nodeMap, componentMap, buffer, index
+    else if message.command == 'addinitial'
+      index = addInitial message.payload, componentLib, nodeMap, componentMap, buffer, index
+    else
+      throw new Error "Unknown FBP runtime graph command #{message.command}"
+
+  else if message.protocol == 'network'
+    if message.command == 'start'
+      index = startNetwork message.payload, buffer, index
+    else
+      throw new Error "Unknown FBP runtime network command #{message.command}"
+
+  else if message.protocol == 'microflo'
+    if message.command == 'opencommunication'
+      index = openCommunication message.payload, buffer, index
+    else if message.command == 'closecommunication'
+      index = closeCommunication message.payload, buffer, index
+    else if message.command == 'configuredebug'
+      index = configureDebug message.payload, buffer, index
+    else
+      throw new Error "Unknown FBP runtime microflo command #{message.command}"
+  else
+      throw new Error "Unknown FBP runtime sub-protocol #{message.protocol}"
+
+  return index
+
+# As a list of FBP runtime messages
+initialGraphMessages = (graph, graphName, debugLevel, openclose) ->
+  messages = []
+
+  # Start comm
+  if openclose
+    messages.push
+      protocol: 'microflo'
+      command: 'opencommunication'
+      payload: null
+
+  # Clear graph
+  messages.push
+    protocol: 'graph'
+    command: 'clear'
+    payload:
+      id: graphName
+
+  # Config
+  messages.push
+    protocol: 'microflo'
+    command: 'configuredebug'
+    payload:
+      level: debugLevel
+
+  # Actual graph
+  graphMessages = protocol.graphToFbpMessages graph, 'default'
+  messages = messages.concat graphMessages
+
+  # Start the network
+  messages.push
+    protocol: 'network'
+    command: 'start'
+    payload: null
+
+  # Stop comm
+  if openclose
+    messages.push
+      protocol: 'microflo'
+      command: 'closecommunication'
+      payload: null
+
+  return messages
+
+cmdStreamBuildGraph = (messages, currentNodeId, buffer, index, componentLib, nodeMap, componentMap) ->
   startIndex = index

-  componentMap = {}
+  # Build component/node mapping
   for message in messages
     if message.command == 'addnode'
       name = message.payload.id
@@ -178,49 +280,30 @@
       componentMap[name] = message.payload.component

   for message in messages
-    if message.protocol != 'graph'
-      throw new Error "Unknown FBP runtime sub-protocol #{message.protocol}"
-    else if message.command == 'addnode'
-      index = addNode message.payload, componentLib, buffer, index
-    else if message.command == 'addedge'
-      index = addEdge message.payload, componentLib, nodeMap, componentMap, buffer, index
-    else if message.command == 'addinitial'
-      index = addInitial message.payload, componentLib, nodeMap, componentMap, buffer, index
-    else
-      throw new Error "Unknown FBP runtime graph command #{message.command}"
+    index = toCommandStreamBuffer message, componentLib, nodeMap, componentMap, buffer, index

   r =
     index: index - startIndex
     nodeId: currentNodeId
   return r

-# TODO: actually add observers to graph, and emit a command stream for the changes
-
 cmdStreamFromGraph = (componentLib, graph, debugLevel, openclose) ->
   debugLevel = debugLevel or 'Error'
-  buffer = new Buffer(1024)
-  # FIXME: unhardcode
+  buffer = new Buffer(10*1024) # FIXME: unhardcode
   index = 0
   currentNodeId = 1
   if !graph.nodeMap
     graph.nodeMap = {}
-  # Start comm
-  if openclose
-    index += writeString(buffer, index, cmdFormat.magicString)
-  # Clear existing graph
-  index += writeCmd(buffer, index, cmdFormat.commands.Reset.id)
-  # Config
-  index += writeCmd(buffer, index, cmdFormat.commands.ConfigureDebug.id, cmdFormat.debugLevels[debugLevel].id)
-  # Actual graph
-  r = cmdStreamBuildGraph(currentNodeId, buffer, index, componentLib, graph)
-  index += r.index
-  currentNodeId = r.nodeId
-  # Start the network
-  index += writeCmd(buffer, index, cmdFormat.commands.StartNetwork.id)
-  if openclose
-    index += writeCmd(buffer, index, cmdFormat.commands.End.id)
+  graphName = 'default'
+
+  graph.componentMap = {}
+
+  messages = initialGraphMessages graph, graphName, debugLevel, openclose
+  r = cmdStreamBuildGraph messages, currentNodeId, buffer, index, componentLib, graph.nodeMap, graph.componentMap
+  index = r.index
+
   buffer = buffer.slice(0, index)
-  buffer
+  return buffer

 nodeNameById = (nodeMap, wantedId) ->
   for name of nodeMap
@@ -235,6 +318,8 @@
   r = if parent != undefined then graph.processes[nodeNameById(graph.nodeMap, parent)].graph.processes else graph.processes
   r[nodeName]

+# TODO: move each parsing into individual function and merge logic deviceResponseToFbpProtocol
+# to return a FBP runtime message directly. Should then be the inverse of toCommandStreamBuffer/addNode/addInitial etc.
 parseReceivedCmd = (componentLib, graph, cmdData, handler) ->
   `var targetPort`
   `var targetNode`


no message

no message

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -4,7 +4,6 @@
 #   hiraoka

-_ = require('hubot:new-channel-bot')

 module.exports = (robot) ->
   robot.hear /疲れた/i, (msg) ->


Added Libraries.io search command

Added Libraries.io search command

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -12,6 +12,18 @@

   robot.hear /morning/i, (res) ->
     res.send "Morning!"
+
+  robot.respond /search (.*)/i, (msg) ->
+    robot.http("https://libraries.io/api/search?q=#{msg.match[1]}")
+      .get() (err, res, body) ->
+        results = JSON.parse(body)
+        body = ''
+        for i in [0...3]
+          body += "#{results[i].name} - #{results[i].platform}\n"
+          body += "#{results[i].description}\n"
+          body += "#{results[i].package_manager_url}\n\n"
+
+        msg.send body

   # robot.respond /open the (.*) doors/i, (res) ->
   #   doorType = res.match[1]


Enable the task list in the constructor

Enable the task list in the constructor

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -104,13 +104,14 @@
   event

 class TaskList
-  constructor: (@el, method) ->
+  constructor: (@el) ->
     @container = closest @el, 'js-task-list-container'
     @field = @container.querySelector '.js-task-list-field'
     # When the task list item checkbox is updated, submit the change
     @container.addEventListener 'change', (event) =>
       if event.target.classList.contains 'task-list-item-checkbox'
         @updateTaskList(event.target)
+    @.enable()

   enable: ->
     if @container.querySelectorAll('.js-task-list-field').length > 0
@@ -253,5 +254,7 @@
       if !taskList
         taskList = new TaskList el
         jQuery(el).data 'task-list', taskList
+        if !method || method == 'enable'
+          return

       taskList[method || 'enable']()


We're all geniuses :bulb:

We're all geniuses :bulb:

--- scripts/morning.coffee
+++ scripts/morning.coffee
@@ -35,5 +35,8 @@
             msg.send hello.replace "%", msg.message.user.name

     robot.hear /(^(good )?m(a|o)rnin(g)?)/i, (msg) ->
-        hello = msg.random mornings
-        msg.send hello.replace "%", msg.message.user.name
+        now = Date.now()
+        if now - lastTimeHaytHadToRespondToAHelloLikeAnAnnoyingAsshole > ONE_DAY
+            lastTimeHaytHadToRespondToAHelloLikeAnAnnoyingAsshole = now
+            hello = msg.random mornings
+            msg.send hello.replace "%", msg.message.user.name


conversations: fix auto-linking of www. in messages

fixes CNVS-2630

test plan:
  * open conversations
  * send a message containing a URL with no protocol
      starting with www.
  * confirm that the URL becomes a link which works correctly

Change-Id: I028e4eb386eed3307d10fc60884f0de008f2601c
Reviewed-on: https://gerrit.instructure.com/36444
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Drew Bowman <43bd25f9469d5056946537962fe98ec04e66c401@instructure.com>
QA-Review: Trevor deHaan <85df72112e8a9484fec7b3db2e31789c2c2d4e07@instructure.com>
Product-Review: Braden Anderson <5d57265d16cfe2e0f2e6eb7f13e876bb378ef0f1@instructure.com>

--- app/coffeescripts/str/TextHelper.coffee
+++ app/coffeescripts/str/TextHelper.coffee
@@ -67,7 +67,7 @@
             AUTO_LINKIFY_PLACEHOLDER
           else
             link = match
-            link = "http://" + link if link[0..3] == 'www'
+            link = "http://" + link if link[0..3] == 'www.'
             link = encodeURI(link).replace(/'/g, '%27')
             links.push link
             "#{htmlEscape(match)}"


不要なセミコロンを削除

不要なセミコロンを削除

--- scripts/shuffle.coffee
+++ scripts/shuffle.coffee
@@ -8,7 +8,7 @@
   arr = arr.slice()
   i = arr.length
   if i == 0
-    return arr;
+    return arr
   while --i
     j = Math.floor(Math.random() * (i + 1))
     tmp = arr[i]


format

format

--- scripts/tonypai.coffee
+++ scripts/tonypai.coffee
@@ -30,8 +30,8 @@
               mt = pad.substring(0, pad.length - mt.length) + mt
               dt = pad.substring(0, pad.length - dt.length) + dt
               # result
-              weather += yr + '-' + mt + '-' + dt + ' ' + val.weather[0].main + '(' + val.weather[0].description + ') ' + val.deg / 10 + '°C'
+              weather += yr + '-' + mt + '-' + dt + ' ' + val.weather[0].main + '(' + val.weather[0].description + ') ' + val.deg / 10 + '°C\n'

             result.send weather
           else
-            result.send url+"("+data.cod+")\nweather [Taiwan city name]"
+            result.send "GET "+url+"("+data.cod+")\nweather [taiwan city name]"


Slicing reverted.

Slicing reverted.

--- scripts/watched.coffee
+++ scripts/watched.coffee
@@ -79,9 +79,9 @@
             hourcount = msg.match[1]
             savedhours = robot.brain.get 'topwatchers'

-            watchedscores = [timename + " has " + hours[timename] / 100 + " hours watched."] for timename of savedhours
+            watchedscores = [timename + " (" + hours[timename] / 100 + " hours)"] for timename of savedhours

-            hourscore = savedhours.slice(0, hourcount)
+            hourscore = watchedscores.slice(0, hourcount)

             # msg.send "The top #{hourcount} users with the most hours are: #{hourscore}"
             # Output of savedhours is "[object Object]"


speed up detection

speed up detection

--- chrome/src/gmailr.coffee
+++ chrome/src/gmailr.coffee
@@ -142,7 +142,7 @@
           @bootstrap cb
         return

-      @delayedLoader = setInterval load, 500
+      @delayedLoader = setInterval load, 200
       return

     ###


Add tooltip to dev mode view

Add tooltip to dev mode view

--- lib/dev-mode-view.coffee
+++ lib/dev-mode-view.coffee
@@ -4,3 +4,6 @@
 class DevModeView extends View
   @content: ->
     @span class: 'inline-block icon icon-color-mode text-error'
+
+  initialize: ->
+    @setTooltip('This window is in dev mode.')


Fix pagination being broken on navigation

--- app/javascript/legacy/pagination.coffee
+++ app/javascript/legacy/pagination.coffee
@@ -1,4 +1,4 @@
-jQuery ->
+$(document).on 'ready turbolinks:load', ->
   if $('#pagination').length > 0
     $('#pagination').hide()
     loading_posts = false


embedly: add video type

embedly: add video type

--- client/app/lib/util/getEmbedType.coffee
+++ client/app/lib/util/getEmbedType.coffee
@@ -11,10 +11,9 @@
     when 'photo','image'
       return 'image'

-    # rich is a html object for things like twitter posts
-    # link is fallback for things that may or may not have any kind of preview
-    # or are links explicitly
-    # also captures 'rich content' and makes regular links from that data
+    when 'video'
+      return 'video'
+
     when 'link', 'html'
       return 'link'


members can announce defaults to true

members can announce defaults to true

--- client/shared/models/group_model.coffee
+++ client/shared/models/group_model.coffee
@@ -22,6 +22,7 @@
     groupPrivacy: 'closed'
     discussionPrivacyOptions: 'private_only'
     membershipGrantedUpon: 'approval'
+    membersCanAnnounce: true
     membersCanAddMembers: true
     membersCanEditDiscussions: true
     membersCanEditComments: true


Prepares elements for slider through functions

--- coffee/suraido.coffee
+++ coffee/suraido.coffee
@@ -55,11 +55,7 @@

       #set the main element
-      @$el.css(
-        width: @maxSize.width
-        height: @items.first().outerHeight()
-        overflow: 'hidden'
-      )
+      @_setsMainElement()

       #Set relative widths
       @itemsWrapper.css(
@@ -68,10 +64,8 @@
         width: "#{@itemsLength * 100}%"
       )

-      @items.css(
-        float: 'left'
-        width: "#{(100 / @itemsLength)}%"
-      )
+      #sets the styling for each slider item
+      @_setsItems()

       #autoslide
       @_enablesAutoPlay() if @options.autoplay
@@ -88,6 +82,17 @@
       if $.event.special['swipe'] or $.Event 'swipe'
         @$el.on 'swipeleft swiperight swipeLeft swipeRight', (e) =>
          if e.type.toLowerCase() is 'swipeleft' then @next() else @prev()
+
+    _setsItems: =>
+      @items.css
+        float: 'left'
+        width: "#{100 / @itemsLength}%"
+
+    _setsMainElement: =>
+      @$el.css
+        width: @maxSize.width
+        height: @items.first().outerHeight()
+        overflow: 'hidden'

     _enablesAutoPlay: =>
       setTimeout(=>


Removes projection parameter for turbase.list()

--- coffee/turbase.coffee
+++ coffee/turbase.coffee
@@ -50,7 +50,7 @@

     # @TODO add endret paramter
     # @TODO add support for queries
-    collection.find(query,{navn:1, endret:1},opts).toArray (err, result) ->
+    collection.find(query,opts).toArray (err, result) ->
       # @TODO move to propper error handling
       return res.jsonp err if err
       return res.jsonp documents: result, count: result.length if result


email address find

email address find

--- chrome/src/gmailr.coffee
+++ chrome/src/gmailr.coffee
@@ -228,9 +228,13 @@
     ###
     emailAddress: ->
       @intercept()
-      # First, try old Gmail header
-      # Try the new one
-      selectors = ["#guser b", ".gbmp1", "#gbi4t"] # Google+ (as of 2012-10-05)
+
+      selectors = [
+        "#guser b" # First, try old Gmail header
+        ".gbmp1" # Try the new one
+        '.gbps2'
+        "#gbi4t" # Google+ (as of 2012-10-05)
+      ]
       el = @elements.canvas.find selectors.join ','
       el.first().html()


Typos

Typos

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -6,7 +6,7 @@

 info = Url.parse process.env.POO_REDIS_URL or "redis://localhost:6379/0"
 redis_client = Redis.createClient(info.port, info.hostname)
-redis_clent.auth info.auth.split(":")[1] if info.auth
+redis_client.auth info.auth.split(":")[1] if info.auth

 module.exports = (robot) ->
   checkRedisForShit = ->
@@ -18,8 +18,8 @@
       robot.messageRoom room, "#{reply}"

       # clean up
-      redis_clent.lrem POO_TRACKER_KEY, 0
-      redis_clent.set POO_LATEST_KEY, reply
+      redis_client.lrem POO_TRACKER_KEY, 0
+      redis_client.set POO_LATEST_KEY, reply

   robot.respond /poo tracker( me)?/i, (res) ->
     redis_client.get POO_LATEST_KEY, (err, reply) ->


Remove message prefix

Remove message prefix

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -18,7 +18,7 @@
       return console.error("Failed get of #{POO_TRACKER_KEY}: #{err}") if err
       return if reply == null

-      robot.messageRoom "#arrakis", "We got a poo over here: #{reply}"
+      robot.messageRoom "#arrakis", "#{reply}"
       redis_clent.del POO_TRACKER_KEY
       redis_clent.set POO_LATEST_KEY, reply


Fixed lightbox plans analytics impression

--- coffee/lightbox-plans.controller.coffee
+++ coffee/lightbox-plans.controller.coffee
@@ -56,7 +56,7 @@
             @.selectedPlan = 'valid'
             @.validPlan = project
             @analytics.addEcStep("select-plan", @.myPlan?.current_plan?.plan_id, @.validPlan)
-            @analytics.addEcAddImpresion(@.validPlan, "Plan detail", 1)
+            @analytics.addEcImpresion(@.validPlan, "Plan detail", 1)
             @analytics.addEcSetAction("add", "Plan detail")
             if @.myPlan && @.validPlan.name == @.myPlan.current_plan.name
                 if @.myPlan.interval == "month"


remove helpers we don’t use

remove helpers we don’t use

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -103,16 +103,6 @@

 	grunt.registerTask "check:redis", "Check that redis is installed and running", () ->
 		Helpers.checkRedisConnect @async()
-	grunt.registerTask "check:latexmk", "Check that latexmk is installed", () ->
-		Helpers.checkLatexmk @async()
-	grunt.registerTask "check:s3", "Check that Amazon S3 credentials are configured", () ->
-		Helpers.checkS3 @async()
-	grunt.registerTask "check:fs", "Check that local filesystem options are configured", () ->
-		Helpers.checkFS @async()
-	grunt.registerTask "check:aspell", "Check that aspell is installed", () ->
-		Helpers.checkAspell @async()
-	grunt.registerTask "check:make", "Check that make is installed", () ->
-		Helpers.checkMake @async()

 	grunt.registerTask "check:mongo", "Check that make is installed", () ->
 		Helpers.checkMongoConnect @async()
@@ -180,176 +170,6 @@
 			proc = spawn "grunt", ["install"], stdio: "inherit", cwd: dir
 			proc.on "close", () ->
 				callback()
-
-		checkRedis: (callback = (error) ->) ->
-			grunt.log.write "Checking Redis is running... "
-			exec "redis-cli info", (error, stdout, stderr) ->
-				if error? and error.message.match("Could not connect")
-					grunt.log.error "FAIL. Redis is not running"
-					return callback(error)
-				else if error?
-					return callback(error)
-				else
-					m = stdout.match(/redis_version:(.*)/)
-					if !m?
-						grunt.log.error "FAIL."
-						grunt.log.error "Unknown redis version"
-						error = new Error("Unknown redis version")
-					else
-						version = m[1]
-						if semver.gte(version, "2.6.12")
-							grunt.log.writeln "OK."
-							grunt.log.writeln "Running Redis version #{version}"
-						else
-							grunt.log.error "FAIL."
-							grunt.log.error "Redis version is too old (#{version}). Must be 2.6.12 or greater."
-							error = new Error("Redis version is too old (#{version}). Must be 2.6.12 or greater.")
-				callback(error)
-
-		checkLatexmk: (callback = (error) ->) ->
-			grunt.log.write "Checking latexmk is installed... "
-			exec "latexmk --version", (error, stdout, stderr) ->
-				if error? and error.message.match("not found")
-					grunt.log.error "FAIL."
-					grunt.log.errorlns """
-					Either latexmk is not installed or is not in your PATH.
-
-					latexmk comes with TexLive 2013, and must be a version from 2013 or later.
-					If you have already have TeXLive installed, then make sure it is
-					included in your PATH (example for 64-bit linux):
-
-						export PATH=$PATH:/usr/local/texlive/2014/bin/x86_64-linux/
-
-					This is a not a fatal error, but compiling will not work without latexmk.
-					"""
-					return callback(error)
-				else if error?
-					return callback(error)
-				else
-					m = stdout.match(/Version (.*)/)
-					if !m?
-						grunt.log.error "FAIL."
-						grunt.log.error "Unknown latexmk version"
-						error = new Error("Unknown latexmk version")
-					else
-						version = m[1]
-						if semver.gte(version + ".0", "4.39.0")
-							grunt.log.writeln "OK."
-							grunt.log.writeln "Running latexmk version #{version}"
-						else
-							grunt.log.error "FAIL."
-							grunt.log.errorlns """
-							latexmk version is too old (#{version}). Must be 4.39 or greater.
-							This is a not a fatal error, but compiling will not work without latexmk
-							"""
-							error = new Error("latexmk is too old")
-				callback(error)
-
-		checkAspell: (callback = (error) ->) ->
-			grunt.log.write "Checking aspell is installed... "
-			exec "aspell dump dicts", (error, stdout, stderr) ->
-				if error? and error.message.match("not found")
-					grunt.log.error "FAIL."
-					grunt.log.errorlns """
-					Either aspell is not installed or is not in your PATH.
-
-					On Ubuntu you can install aspell with:
-
-						sudo apt-get install aspell
-
-					Or on a mac:
-
-						brew install aspell
-
-					This is not a fatal error, but the spell-checker will not work without aspell
-					"""
-					return callback(error)
-				else if error?
-					return callback(error)
-				else
-					grunt.log.writeln "OK."
-					grunt.log.writeln "The following spell check dictionaries are available:"
-					grunt.log.write stdout
-					callback()
-				callback(error)
-
-		checkS3: (callback = (error) ->) ->
-			Settings = require "settings-sharelatex"
-			if Settings.filestore.backend==""
-				grunt.log.writeln "No backend specified. Assuming Amazon S3"
-				Settings.filestore.backend = "s3"
-			if Settings.filestore.backend=="s3"
-				grunt.log.write "Checking S3 credentials... "
-				try
-					client = knox.createClient({
-						key: Settings.filestore.s3.key
-						secret: Settings.filestore.s3.secret
-						bucket: Settings.filestore.stores.user_files
-					})
-				catch e
-					grunt.log.error "FAIL."
-					grunt.log.errorlns """
-					Please configure your Amazon S3 credentials in config/settings.development.coffee
-
-					Amazon S3 (Simple Storage Service) is a cloud storage service provided by
-					Amazon. ShareLaTeX uses S3 for storing binary files like images. You can
-					sign up for an account and find out more at:
-
-							http://aws.amazon.com/s3/
-
-					"""
-					return callback()
-				client.getFile "does-not-exist", (error, response) ->
-					unless response? and response.statusCode == 404
-						grunt.log.error "FAIL."
-						grunt.log.errorlns """
-						Could not connect to Amazon S3. Please check your credentials.
-						"""
-					else
-						grunt.log.writeln "OK."
-					callback()
-			else
-				grunt.log.writeln "Filestore other than S3 configured. Not checking S3."
-				callback()
-
-		checkFS: (callback = (error) ->) ->
-			Settings = require "settings-sharelatex"
-			if Settings.filestore.backend=="fs"
-				grunt.log.write "Checking FS configuration... "
-				fs = require("fs")
-				fs.exists Settings.filestore.stores.user_files, (exists) ->
-					if exists
-						grunt.log.writeln "OK."
-					else
-						grunt.log.error "FAIL."
-						grunt.log.errorlns """
-						Could not find directory "#{Settings.filestore.stores.user_files}".
-						Please check your configuration.
-						"""
-					callback()
-			else
-				grunt.log.writeln "Filestore other than FS configured. Not checking FS."
-				callback()
-
-		checkMake: (callback = (error) ->) ->
-			grunt.log.write "Checking make is installed... "
-			exec "make --version", (error, stdout, stderr) ->
-				if error? and error.message.match("not found")
-					grunt.log.error "FAIL."
-					grunt.log.errorlns """
-					Either make is not installed or is not in your path.
-
-					On Ubuntu you can install make with:
-
-					    sudo apt-get install build-essential
-
-					"""
-					return callback(error)
-				else if error?
-					return callback(error)
-				else
-					grunt.log.write "OK."
-					return callback()

 		checkMongoConnect: (callback = (error) ->) ->


Update message format

Update message format

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -213,7 +213,12 @@
 					Either latexmk is not installed or is not in your PATH.

 					latexmk comes with TexLive 2013, and must be a version from 2013 or later.
-					This is a not a fatal error, but compiling will not work without latexmk
+					If you have already have TeXLive installed, then make sure it is
+					included in your PATH (example for 64-bit linux):
+
+						export PATH=$PATH:/usr/local/texlive/2014/bin/x86_64-linux/
+
+					This is a not a fatal error, but compiling will not work without latexmk.
 					"""
 					return callback(error)
 				else if error?
@@ -254,8 +259,7 @@

 						brew install aspell

-					This is not a fatal error, but the spell-checker will not work
-					without aspell
+					This is not a fatal error, but the spell-checker will not work without aspell
 					"""
 					return callback(error)
 				else if error?


added logging

added logging

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -122,6 +122,7 @@

 	Helpers =
 		installService: (service, callback = (error) ->) ->
+			console.log "Installing #{service.name}"
 			Helpers.cloneGitRepo service, (error) ->
 				return callback(error) if error?
 				Helpers.installNpmModules service, (error) ->
@@ -130,6 +131,7 @@
 						return callback(error) if error?
 						Helpers.runGruntInstall service, (error) ->
 							return callback(error) if error?
+							console.log "Finished installing #{service.name}"
 							callback()


Add a missing level link to BrainPOP's Ogre Encounter series.

--- app/views/play/CampaignView.coco.coffee
+++ app/views/play/CampaignView.coco.coffee
@@ -523,7 +523,7 @@
         return level.requiresSubscription
     if features.brainPop
       context.levels = _.filter context.levels, (level) ->
-        level.slug in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'true-names']
+        level.slug in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'enemy-mine', 'true-names']
     @annotateLevels(context.levels)
     count = @countLevels context.levels
     if @courseStats?


Make match non greedy

Make match non greedy

--- src/public/lookup.coffee
+++ src/public/lookup.coffee
@@ -56,7 +56,7 @@
                     r += '
'

                     # Parse {...} to links
-                    r = r.replace /{(.+)}/g, "$1"
+                    r = r.replace /{(.+?)}/g, "$1"

                     return r
             )


set a default date if not set on albums

set a default date if not set on albums

--- server/models/album.coffee
+++ server/models/album.coffee
@@ -19,6 +19,10 @@
                         .replace /<br>/g, ""
                         .replace /<div>/g, ""
                         .replace /<\/div>/g, ""
+
+    # Set default date if not set.
+    data.date = new Date() unless data.date?
+
     next()

 Album::getPublicURL = (callback) ->


Fixed: alarms were not properly triggered for all day event and recurring events

--- server/models/event.coffee
+++ server/models/event.coffee
@@ -40,22 +40,27 @@
 Event::getAlarms = (defaultTimezone) ->

     alarms = []
+    ALLDAY_FORMAT = 'YYYY-MM-DD'

     timezone = @timezone or defaultTimezone
-
     for alarm in @alarms?.items

         startDates = []

-        if @isAllDay()
-            startDates = [moment.tz @start, timezone]
-        else if @rrule?
+        if @rrule? and @rrule.length > 0
             now = moment().tz timezone
             in24h = moment(now).add 1, 'days'
             rrule = RRule.parseString @rrule
             occurrences = new RRule(rrule).between now.toDate(), in24h.toDate()
             for occurrence in occurrences
-                startDates.push moment.tz occurrence, timezone
+                if @isAllDay()
+                    date = moment.tz occurrence, ALLDAY_FORMAT, timezone
+                else
+                    date = moment.tz occurrence, timezone
+                startDates.push date
+
+        else if @isAllDay()
+            startDates = [moment.tz @start, ALLDAY_FORMAT, timezone]

         else
             startDates = [moment.tz @start, 'UTC']


Alopex UI(v2.10.8)(v2.10.9) - 수정

Alopex UI(v2.10.8)(v2.10.9) - 수정

--- snippets/AJ-Component.cson
+++ snippets/AJ-Component.cson
@@ -1228,15 +1228,6 @@
 		'description':'[Tootip] 컴포넌트에 설정된 옵션값을 가져옵니다.'
 		'descriptionMoreURL':'http://ui.alopex.io/development/component/tooltip#Functions_getOptionkey'
 		'body': """
-$('#tooltip').getOptions();
-"""
- 'getOption(String key)' :
-		'prefix': 'getOption(String key)'
-		'leftLabelHTML':'void'
-		'rightLabelHTML':'AlopexUI'
-		'description':'[Tootip] 컴포넌트에 설정된 옵션값을 가져옵니다.'
-		'descriptionMoreURL':'http://ui.alopex.io/development/component/tooltip#Functions_getOptionkey'
-		'body': """
 $('#tooltip').getOption('activate');
 """
  'getOptions()' :


Use dynamic hostname with browser

Use dynamic hostname with browser

--- spec/RemoteRuntimes.coffee
+++ spec/RemoteRuntimes.coffee
@@ -19,20 +19,6 @@
     c = null
     server = null
     port = 3888
-
-    before (done) ->
-      if noflo.isBrowser()
-        port = 3889
-        console.log "WebSocket runtime should have been set up on #{port}"
-        done()
-      else
-        utils.createServer port, (err, s) ->
-          server = s
-          done()
-    after (done) ->
-      server.close() if server
-      done()
-
     def =
       label: "NoFlo 222"
       description: "The first remote component in the world"
@@ -43,6 +29,22 @@
       id: "2ef763ff-1f28-49b8-b58f-5c6a5c23af2d"
       user: "3f3a8187-0931-4611-8963-239c0dff1931"
       seenHoursAgo: 11
+
+    before (done) ->
+      if noflo.isBrowser()
+        port = 3889
+        def.address = "ws://#{window.location.hostname}:#{port}"
+        console.log "WebSocket runtime should have been set up on #{def.address}"
+        done()
+      else
+        utils.createServer port, (err, s) ->
+          console.log "WebSocket runtime running in port #{port}"
+          server = s
+          done()
+    after (done) ->
+      server.close() if server
+      done()
+
     meta = {}
     readyEmitted = false

@@ -157,11 +159,22 @@
     """
     meta = {}
     readyEmitted = false
+    def =
+      label: "NoFlo node.js websocket"
+      description: ""
+      type: "noflo"
+      protocol: "websocket"
+      address: "ws://localhost:"+port
+      secret: "my-super-secret2s"
+      id: "2ef763ff-1f28-49b8-b58f-5c6a5c23af23"
+      user: "3f3a8187-0931-4611-8963-239c0dff1934"
+      seenHoursAgo: 11

     before (done) ->
       if noflo.isBrowser()
         port = 3892
-        console.log "WebSocket NoFlo runtime should have been set up on #{port}"
+        def.address = "ws://#{window.location.hostname}:#{port}"
+        console.log "WebSocket NoFlo runtime should have been set up on #{def.address}"
         done()
       else
         utils.createNoFloServer port, (err, s) ->
@@ -173,16 +186,6 @@
       done()

     it 'should be instantiable', (done) ->
-      def =
-        label: "NoFlo node.js websocket"
-        description: ""
-        type: "noflo"
-        protocol: "websocket"
-        address: "ws://localhost:"+port
-        secret: "my-super-secret2s"
-        id: "2ef763ff-1f28-49b8-b58f-5c6a5c23af23"
-        user: "3f3a8187-0931-4611-8963-239c0dff1934"
-        seenHoursAgo: 11
       c = (RemoteSubGraph.getComponentForRuntime def)(meta)
       chai.expect(c).to.be.an.instanceof noflo.Component
       c.once 'ready', () ->


added spec

added spec

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -194,6 +194,16 @@
     beforeEach ->
       editor.getBuffer().setText("012 345\nabc\n")
       editor.setCursorScreenPosition([0, 4])
+
+    describe "when selected lines in visual linewise mode", ->
+      beforeEach ->
+        keydown('V')
+        keydown('k')
+        keydown('y')
+
+      it "saves the lines to the default register in linewise mode", ->
+        expect(vimState.getRegister('"').text).toBe "012 345\nabc\n"
+        expect(vimState.getRegister('"').type).toBe 'linewise'

     describe "when followed by a second y ", ->
       beforeEach ->


test/stacks: add createCredentials func to create stack

--- client/test/lib/dashboard/stacks.coffee
+++ client/test/lib/dashboard/stacks.coffee
@@ -19,7 +19,9 @@
       targetUser1

     teamsHelpers.inviteAndJoinWithUsers browser, [ users ], (result) ->
-      done()
+      teamsHelpers.createCredential browser, 'aws', 'test credential' no,  ->
+        teamsHelpers.createStack browser, ->
+          done()

   redirectStackTemplate: (browser) ->


cleanup controls

cleanup controls

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -36,6 +36,16 @@
       _CHILDREN: [{_LABEL: "step"}, {_LABEL: "run"}, {_LABEL: "stop"}, {_LABEL: "reset"}]
     }
     {
+      _LABEL: "turtle_controls"
+      _AUTHORITY: (world) -> world.get('BUTTON')
+      _CHILDREN: [{_LABEL: "left"}, {_LABEL: "front"}, {_LABEL: "right"}]
+    }
+    {
+      _LABEL: "active_program"
+      _AUTHORITY: (world) -> world.get('BUTTON')
+      _CHILDREN: [{_LABEL: "step"}, {_LABEL: "run"}, {_LABEL: "stop"}, {_LABEL: "reset"}]
+    }
+    {
       _LABEL: "current_selection"
       stroke: "white"
       fill: "white"
@@ -43,16 +53,6 @@
         current = world.get('current')
         "#{current} #{current.get('i')}x#{current.get('j')} -> #{current.get('v_i')}x#{current.get('v_j')}"
     }
-    {
-      _LABEL: "available_commands"
-      _AUTHORITY: (world) -> world.get('BUTTON')
-      _CHILDREN: [
-        {_LABEL: "left"}
-        {_LABEL: "front"}
-        {_LABEL: "right"}
-      ]
-    }
-    {_LABEL: "active_program"}
     {_LABEL: "program_selector"}
   ]
 }


Sub-children over-transform

Sub-children over-transform

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -21,8 +21,14 @@
       fill: "white"
       name: (world, args) ->
         cur = world.get("current")
-        "Current turtle: #{cur}"
+        "For: #{cur}"
     }
-    {col: 0, row: 1}
+    {
+      row: 1
+      _CHILDREN: [
+        {col: 0}
+        {col: 1}
+      ]
+    }
   ]
 }


SocialApi: cache every channel/message for later use

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -95,6 +95,8 @@
           catch e then null

     new MessageEventManager {}, m
+
+    KD.singletons.socialapi.cacheItem m

     return m

@@ -152,7 +154,12 @@
     data.unreadCount         = channel.unreadCount
     data.lastMessage         = mapActivity channel.lastMessage  if channel.lastMessage

-    return new KD.remote.api.SocialChannel data
+
+    channelInstance = new KD.remote.api.SocialChannel data
+
+    KD.singletons.socialapi.cacheItem channelInstance
+
+    return channelInstance

   mapChannels = (channels)->
@@ -187,6 +194,7 @@
       for socialApiChannel in socialApiChannels
         channelName = generateChannelName socialApiChannel
         continue  if socialapi.openedChannels[channelName]
+        socialapi.cacheItem socialApiChannel
         socialapi.openedChannels[channelName] = {} # placeholder to avoid duplicate registration

         subscriptionData =
@@ -267,7 +275,6 @@

   cacheable: (type, id, force, callback) ->
-
     [callback, force] = [force, no]  unless callback

     if not force and item = @retrieveCachedItem(type, id)


Handle Filter with methods/logic (and not global var)

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -22,12 +22,7 @@

     _messages = Immutable.OrderedMap()

-    _sortField   = 'date'
-    _sortOrder   = 1
-    _filterType   = '-'
-    _filterValue   = 'nofilter'
-    _filterBefore  = '-'
-    _filterAfter  = '-'
+    _currentFilter = null

     _fetching     = 0

@@ -46,6 +41,50 @@
     _inFlightByRef = {}
     _inFlightByMessageID = {}
     _undoable = {}
+
+    _difference = (obj0, obj1) ->
+        result = {}
+        _.filter obj0, (value, key) ->
+            unless value is obj1[key]
+                result[key] = value
+        result
+
+    _getURISort = (filter) ->
+        filter = _getFilter() unless filter
+        "#{filter.order}#{filter.field}"
+
+    _getFilter = (getDefault) ->
+        return if not _currentFilter or getDefault
+            field: 'date'
+            order: '+'
+            type: '-'
+            value: 'nofilter'
+            before: '-'
+            after: '-'
+        return _currentFilter
+
+    _setFilter = (params) ->
+        _defaultValue = _getFilter()
+
+        # Update Filter
+        _currentFilter =
+            field: if params.sort then params.sort.substr(1) else _defaultValue.field
+            order: if params.sort then params.sort.substr(0, 1) else _defaultValue.order
+            type: params.type or _defaultValue.type
+            value: params.flag or _defaultValue.value
+            before: params.before or _defaultValue.before
+            after: params.after or _defaultValue.after
+
+        # Update context
+        _queryRef++
+        _noMore = false
+        _nextUrl = null
+
+        return _currentFilter
+
+    _resetFilter = ->
+        value = _getFilter true
+        _setFilter value

     _addInFlight = (request) ->
         _inFlightByRef[request.ref] = request
@@ -369,34 +408,15 @@
             @emit 'change'

         handle ActionTypes.QUERY_PARAMETER_CHANGED, (parameters) ->
-
-            # _messages  = _messages.clear()
-            # "sort/-date/flag/unseen/before/-/after/-"
-
-            {type, flag, sort, before, after} = parameters
-
-
-
-            if type isnt _filterType or _filterValue isnt flag or
-            _filterBefore isnt before or _filterAfter isnt after or
-            _sortOrder + _sortField isnt sort
-
-                _queryRef++
-                _sortOrder = sort.substr(0, 1)
-                _sortField = sort.substr(1)
-                _filterType = type
-                _filterValue = flag
-                _filterBefore = before
-                _filterAfter = after
-                _noMore = false
-                _nextUrl = null
-
-                if _filterType in ['from', 'dest'] or type in ['from', 'dest']
-                    # we cant properly filter messages by dest or from in the
-                    # client, instead we clear message cache and dont filter
-                    # on display
-                    _messages = _messages.clear()
-                    _conversationMemoize = null
+            return if _.isEmpty (params = _difference parameters, _getFilter())
+
+            filter = _setFilter params
+            if filter.type in ['from', 'dest']
+                # we cant properly filter messages by dest or from in the
+                # client, instead we clear message cache and dont filter
+                # on display
+                _messages = _messages.clear()
+                _conversationMemoize = null

             @emit 'change'

@@ -407,14 +427,7 @@

         handle ActionTypes.SELECT_ACCOUNT, (value) ->
             @setCurrentID null
-            _sortOrder = '+'
-            _sortField = 'date'
-            _filterType = 'nofilter'
-            _filterValue = '-'
-            _filterBefore = '-'
-            _filterAfter = '-'
-            _noMore = false
-            _nextUrl = null
+            _resetFilter()

         handle ActionTypes.RECEIVE_MESSAGE_DELETE, (id) ->
             _messages = _messages.remove id
@@ -449,7 +462,8 @@
                 return true

     _matchFlag = (message) ->
-        switch _filterValue
+        filter = _getFilter()
+        switch filter.value
             when MessageFilter.FLAGGED
                 MessageFlags.FLAGGED in message.get('flags')
             when MessageFilter.ATTACH
@@ -461,8 +475,9 @@

     _matchRangeDate = (message) ->
-        date = message.get(_filterType)
-        moment(date).isBefore(_filterAfter) and moment(date).isAfter(_filterBefore)
+        filter = _getFilter()
+        date = message.get filter.type
+        moment(date).isBefore(filter.after) and moment(date).isAfter(filter.before)

     ###*
     * Get messages from mailbox, with optional pagination
@@ -477,21 +492,23 @@

         sequence = _messagesWithInFlights()
         sequence = sequence.filter inMailbox mailboxID
-        if _filterType is 'flag'
+
+        filter = _getFilter()
+        if filter.type is 'flag'
             sequence = sequence.filter _matchFlag

-        else if _filterType is 'date'
+        else if filter.type is 'date'
             sequence = sequence.filter _matchRangeDate

-        # We dont filter for _filterType from and dest because it is
+        # We dont filter for type from and dest because it is
         # complicated by collation and name vs address.
         # Instead we clear the message, see QUERY_PARAMETER_CHANGED handler.

         if useConversations
             sequence = sequence.filter dedupConversation()

-        sequence = sequence.sort getSortFunction _sortField, _sortOrder
+        sequence = sequence.sort getSortFunction filter.field, filter.order
         _currentMessages = sequence.toOrderedMap()

         return _currentMessages
@@ -607,29 +624,33 @@
         _undoable[ref]

     getQueryParams: ->
-        sort: "#{_sortOrder}#{_sortField}"
-        type: _filterType
-        filter: _filterValue
-        before: _filterBefore
-        after: _filterAfter
-        hasNextPage: not _noMore
+        filter = _getFilter()
+        params =
+            sort: _getURISort()
+            type: filter.type
+            filter: filter.value
+            before: filter.before
+            after: filter.after
+            hasNextPage: not _noMore
+        return params

     getCurrentURL: ->
+        filter = _getFilter()
         mailboxID = AccountStore.getSelectedMailbox().get 'id'
-        sort = if _filterType in ['from', 'dest']
-            encodeURIComponent "+#{_filterType}"
+        sort = if filter.type in ['from', 'dest']
+            encodeURIComponent "+#{filter.type}"
         else
-            encodeURIComponent "#{_sortOrder}#{_sortField}"
+            encodeURIComponent _getURISort()

         url = "mailbox/#{mailboxID}/?sort=#{sort}"
-        if _filterType is 'flag' and _filterValue isnt '-'
-            url += "&flag=#{_filterValue}"
-
-        if _filterBefore isnt '-'
-            url += "&before=#{encodeURIComponent _filterBefore}"
-
-        if _filterAfter isnt '-'
-            url += "&after=#{encodeURIComponent _filterAfter}"
+        if filter.type is 'flag' and filter.value isnt '-'
+            url += "&flag=#{filter.value}"
+
+        if filter.before isnt '-'
+            url += "&before=#{encodeURIComponent filter.before}"
+
+        if filter.after isnt '-'
+            url += "&after=#{encodeURIComponent filter.after}"
         return url

     getNextUrl: ->
@@ -641,5 +662,4 @@
             _currentUrl = url
             return url

-
 module.exports = self = new MessageStore()


fix slowness of deletion

fix slowness of deletion

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -300,6 +300,7 @@
             trashBoxID = account?.get? 'trashMailbox'
             _addInFlight {type: 'trash', trashBoxID, messages, ref}
             _fixCurrentMessage target
+            @emit 'change'

         handle ActionTypes.MESSAGE_TRASH_SUCCESS, ({target, updated, ref}) ->
             _undoable[ref] =_removeInFlight ref


fix spec

fix spec

--- spec/autocomplete-snippets-spec.coffee
+++ spec/autocomplete-snippets-spec.coffee
@@ -64,31 +64,29 @@

   it "loads matched snippets in user snippets", ->
     runs ->
-      editorView = atom.workspaceView.getActiveView()
-      editorView.attachToDom()
-      editor = editorView.getEditor()
+      editor.moveToBottom()

-      expect(editorView.find(".autocomplete-plus")).not.toExist()
+      expect(editorView.querySelector(".autocomplete-plus")).not.toExist()

       editor.insertText "b"
       editor.insertText "f"

       advanceClock completionDelay + 1000

-      expect(editorView.find(".autocomplete-plus")).toExist()
-      expect(editorView.find(".autocomplete-plus span.word:eq(0)")).toHaveText "bf"
-      expect(editorView.find(".autocomplete-plus span.label:eq(0)")).toHaveText "BarFoo"
+      expect(editorView.querySelector(".autocomplete-plus")).toExist()
+      expect(editorView.querySelector(".autocomplete-plus span.word")).toHaveText "bf"
+      expect(editorView.querySelector(".autocomplete-plus span.label")).toHaveText "BarFoo"

       editor.insertText " "
-      expect(editorView.find(".autocomplete-plus")).not.toExist()
+      expect(editorView.querySelector(".autocomplete-plus")).not.toExist()

-      editor.moveCursorToBottom()
+      editor.moveToBottom()
       editor.insertText "f"
       editor.insertText "b"

       advanceClock completionDelay + 1000

-      expect(editorView.find(".autocomplete-plus")).not.toExist()
+      expect(editorView.querySelector(".autocomplete-plus")).not.toExist()

   # it "does not crash when typing an invalid folder", ->
   #   waitsForPromise ->


Add support for `.yamllint.{yml,yaml}` (#837)

--- config.cson
+++ config.cson
@@ -7167,7 +7167,7 @@

 	YAMLLint:
 		icon: "yamllint"
-		match: /^\.yamllint$/i
+		match: /^\.yamllint(\.ya?ml)?$/i
 		colour: "medium-green"
 		priority: 2


Remove break between variable names

Remove break between variable names

[#133442273]

--- src/code/views/link-relation-view.coffee
+++ src/code/views/link-relation-view.coffee
@@ -18,10 +18,9 @@
 QuantStart = React.createFactory React.createClass
   render: ->
     start = tr "~NODE-RELATION-EDIT.SEMI_QUANT_START"
-    (div {},
+    (div {style: {width: "95%"}},
       (span {}, "#{tr "~NODE-RELATION-EDIT.AN_INCREASE_IN"} ")
       (span {className: "source"}, @props.source)
-      (br {})
       (span {}, " #{tr "~NODE-RELATION-EDIT.CAUSES"} ")
       (span {className: "target"}, @props.target)
     )


Use Math.max over if-statement, in both directions

--- src/coffee/utilities/color-picker.coffee
+++ src/coffee/utilities/color-picker.coffee
@@ -139,7 +139,8 @@

   selectColor: (e) ->
     @selected = new Posn(e).subtract(@offset).subtract(new Posn(10, 12))
-    @selected.x = 0 if @selected.x < 0
+    @selected.x = Math.max(0, @selected.x)
+    @selected.x = Math.min(260, @selected.x)
     @selected.y += @poolContainer.scrollTop
     color = @getColorAt @selected
     @set color


Hopefully permanent fix for the buttons not working problem

--- SingularityUI/app/views/dashboard.coffee
+++ SingularityUI/app/views/dashboard.coffee
@@ -122,22 +122,42 @@
     changeUser: =>
         app.deployUserPrompt()

+    getRequest: (id) =>
+        maybeRequest = @collection.models.filter (model) ->
+            model.id is id
+        if maybeRequest
+            return maybeRequest[0]
+        else
+            return
+
     viewJson: (e) ->
         id = $(e.target).parents('tr').data 'request-id'
-        utils.viewJSON @collection.get id
+        request = @getRequest id
+        unless request
+            console.error ("Could not find request #{id}. Perhaps someone removed it?")
+            return
+        utils.viewJSON request

     removeRequest: (e) ->
         $row = $(e.target).parents 'tr'
         id = $row.data('request-id')
-
-        @collection.get(id).promptRemove =>
+        request = @getRequest id
+        unless request
+            console.error ("Could not find request #{id}. Perhaps someone removed it first?")
+            return
+        request.promptRemove =>
             $row.remove()

     unpauseRequest: (e) ->
         $row = $(e.target).parents 'tr'
         id = $row.data('request-id')

-        @collection.get(id).promptUnpause =>
+        request = @getRequest id
+        unless request
+            console.error ("Could not find request #{id}. Perhaps someone removed it?")
+            return
+
+        request.promptUnpause =>
             $row.remove()
             @trigger 'refreshrequest'


reverting to something simpler

reverting to something simpler

--- src/scripts/drush.coffee
+++ src/scripts/drush.coffee
@@ -9,16 +9,12 @@

 module.exports = (robot) ->
   robot.respond /drush sa$/i, (msg) ->
-    if not process.env.DRUSH_UID?
-      msg.send "DRUSH_UID is not set. Cannot complete this action."
-      return
+    #    if not process.env.DRUSH_UID?
+    # msg.send "DRUSH_UID is not set. Cannot complete this action."
+    #  return

     sitelist = 'Site Aliases Available: \n'
-    sitealias = spawn("bash", ["drush", "sa"],
-      cwd: undefined,
-      env: process.env,
-      uid: Number process.env.DRUSH_UID
-    )
+    sitealias = spawn("drush", ["sa"])
     sitealias.stdout.on "data", (data) ->
       sitelist = sitelist + data

@@ -28,11 +24,7 @@

   robot.respond /drush cc (.*)$/i, (msg) ->
     ccout = ''
-    clearcache = spawn("drush", [msg.match[1], "cc", "all"],
-      cwd: undefined,
-      env: process.env,
-      uid: Number process.env.DRUSH_UID
-    )
+    clearcache = spawn("drush", [msg.match[1], "cc", "all"])
     msg.send "This may take a minute, please be patient..."
     clearcache.stdout.on "data", (data) ->
       ccout = ccout + data


a little more comment/code cleanup

a little more comment/code cleanup

--- src/scripts/drush.coffee
+++ src/scripts/drush.coffee
@@ -13,7 +13,8 @@
 # Notes:
 #   It would have been easier and more elegant to simply allow the user to funnel drush commands directly
 #   to the spawn method.  However that is a colossal security risk, so I opted to limit the commands that
-#   can be executed as well as the options provided to those commands.
+#   can be executed as well as the options provided to those commands. By default this is limited to
+#   relatively harmless "info" commands.
 #
 # Commands:
 #   hubot drush sa - show the list of available sites ( --update-aliases will refresh this list )
@@ -28,7 +29,6 @@
 # Author:
 #   rh0

-util = require("util")
 spawn = require("child_process").spawn

 drush_interface = ->
@@ -67,6 +67,8 @@
       msg.send output

   # the commands that we are allowing drush to execute
+  # NOTE: If you decide to augment the commands here plese carefully consider what you are opening to the people
+  #       interacting with hubot.
   allowed_commands =
     drush_sa: (msg, command) ->
       if command.args.indexOf('--update-aliases') is -1


Removed else from hound checking, was catching block punches as false positives

--- src/scripts/hound.coffee
+++ src/scripts/hound.coffee
@@ -116,12 +116,6 @@
               endOfBlock = moment(lastPunch.date).add(lastPunch.times.block, 'hours')
               if not now.isBetween(lastPunch.date, endOfBlock)
                 user.hound houndMessage('in'), Logger
-          else
-            Logger.debug "Considering hounding #{user.slack} because it's during their active period with no in punch"
-            if now.isAfter(start) and timeSinceStart >= 0.5 and not passive
-              user.hound houndMessage('in'), Logger
-            else if now.isAfter(end) and timeSinceEnd >= 0.5 and not passive
-              user.hound houndMessage('out'), Logger
         else # Part-timer-only hounding
           if lastPunch and lastPunch.mode is 'in' and timeSinceLastPunch > 4
             user.hound houndMessage('tempout'), Logger


Fixed spacers, added markdown-preview

Fixed spacers, added markdown-preview

--- toolbar.cson
+++ toolbar.cson
@@ -14,7 +14,7 @@
     callback: "application:open-file"
   }
   {
-    type: "separator"
+    type: "spacer"
   }
   {
     type: "button"
@@ -36,4 +36,17 @@
     tooltip: "Macros"
     callback: "macros:edit-macros"
   }
+  {
+    type: "spacer"
+  }
+  {
+    type: "button"
+    icon: "markdown"
+    tooltip: "Toggle markdown preview"
+    callback: "markdown-preview:toggle"
+  }
+  # spacer to separate 'updated toolbar' button at end
+  {
+    type: "spacer"
+  }
 ]


Added some basic operations

Added some basic operations

--- toolbar.cson
+++ toolbar.cson
@@ -5,7 +5,23 @@
     callback: "application:new-file"
     tooltip: "New File"
     iconset: "ion"
-    mode: "dev"
+  }
+  {
+    type: "button"
+    icon: "folder"
+    iconset: "ion"
+    tooltip: "Open..."
+    callback: "application:open-file"
+  }
+  {
+    type: "separator"
+  }
+  {
+    type: "button"
+    icon: "navicon-round"
+    iconset: "ion"
+    tooltip: "Command Palette"
+    callback: "command-palette:toggle"
   }
   {
     type: "button"
@@ -14,4 +30,10 @@
     style:
       color: "#FA4F28"
   }
+  {
+    type: "button"
+    icon: "tools"
+    tooltip: "Macros"
+    callback: "macros:edit-macros"
+  }
 ]


Fix the redirection at the end of import

--- client/app/views/leave_google_log.coffee
+++ client/app/views/leave_google_log.coffee
@@ -35,7 +35,7 @@
             @model.photos.numberPhotos = data.number
             @model.photos.total = data.total
             @render()
-        window.sio.on "calendars", (data)=>
+        window.sio.on "calendars", (data) =>
             @model.events.number = data.number
             @model.events.total = data.total
             @render()
@@ -62,7 +62,7 @@
             @model.invalidToken = false

     afterRender: ->
-        #app.router.navigate '', trigger: false
+        app.router.navigate '', trigger: false

     getRenderData: ->
         return @model


Fix bound helper snippet key

Fix bound helper snippet key

--- snippets/file-skeletons.cson
+++ snippets/file-skeletons.cson
@@ -77,7 +77,7 @@
       });
     """

-  'Helper Skeleton':
+  'Bound Helper Skeleton':
     'prefix': 'bound-helper'
     'leftLabel': 'Ember'
     'body': """


prefs: relay "resize" events to tab impls

--- client/prefs-ui.coffee
+++ client/prefs-ui.coffee
@@ -33,10 +33,13 @@
       .tabs()
       .on 'keydown', 'input', 'return', ok
       .on 'dragstart', -> false
-      .dialog autoOpen: 0, title: 'Preferences', width: 600, height: 450, buttons: [
-        {text: 'OK', click: ok}
-        {text: 'Cancel', click: -> $d.dialog 'close'; return}
-      ]
+      .dialog
+        autoOpen: 0, title: 'Preferences', width: 600, height: 450
+        resize: -> (for t in tabs when t.resize then t.resize()); return
+        buttons: [
+          {text: 'OK', click: ok}
+          {text: 'Cancel', click: -> $d.dialog 'close'; return}
+        ]
     for t in tabs then t.init $ "#prefs-tab-#{safe t.name}"
   $d.dialog('option', 'position', at: 'center').dialog 'open'
   if tabName then $d.tabs active: $("#prefs-tabs-nav a[href='#prefs-tab-#{tabName}']").parent().index()


enable gamepad

enable gamepad

--- src/javascript/game/metroid_clone_delegate.coffee
+++ src/javascript/game/metroid_clone_delegate.coffee
@@ -92,6 +92,18 @@

   update: (dt) ->
     controllerEvents = @controllerEventMux.next()
+    # Dumb temp hack: smush gamepad events onto keyboard events  XXX
+    gpe = controllerEvents.get('p1Gamepad')
+    if gpe?
+      console.log gpe.toJS()
+      controllerEvents = controllerEvents.update 'p1Keyboard', (k) ->
+        if k?
+          k.merge(gpe)
+        else
+          gpe
+
+
+
     devUIEvents = @devUI.getEvents()

     priorAdminState = @adminState
@@ -180,6 +192,7 @@
     "DPAD_DOWN": 'down'
     "FACE_1": 'action2'
     "FACE_3": 'action1'
+    "START_FORWARD": 'start'

   adminController = new KeyboardController


Workspace: Old `JMachine` records has label as its slug

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -29,8 +29,9 @@

     kallback = (workspaces) ->

-      for machine in KD.userMachines when machine.slug is machineLabel
-        machineUId = machine.uid
+      for machine in KD.userMachines \
+        when (machine.slug or machine.label) is machineLabel
+          machineUId = machine.uid

       for workspace in workspaces
         continue  unless workspace.machineUId is machineUId


Workspace: Refresh sidebar asynchronously

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -210,12 +210,12 @@

     '/:name?/IDE/:machineLabel/:workspaceSlug': (routeInfo) ->

+      refreshWorkspaces noop
+
       {params} = routeInfo

-      refreshWorkspaces ->
-
-        findWorkspace params, (workspace) ->
-
-          params.username = KD.nick()
-
-          loadWorkspace params, workspace
+      findWorkspace params, (workspace) ->
+
+        params.username = KD.nick()
+
+        loadWorkspace params, workspace


fix timelines icon to accept '-' in username.

--- scripts/timelines.coffee
+++ scripts/timelines.coffee
@@ -16,7 +16,7 @@
     robot.brain.set("timelines-webhook-url", url)
     msg.reply "timelines の WebHook URL を #{url} に設定しました"

-  robot.respond /timelines\s+icon\s+(\w+)\s+:?([a-zA-Z0-9_-]+):?\s*$/, (msg) ->
+  robot.respond /timelines\s+icon\s+([a-zA-Z0-9_-]+)\s+:?([a-zA-Z0-9_-]+):?\s*$/, (msg) ->
     username = msg.match[1]
     icon_emoji = ":#{msg.match[2]}:"
     icons = (robot.brain.get("timelines-icons") || {})


welcome: added markup

welcome: added markup

--- client/welcome/lib/welcomeappview.coffee
+++ client/welcome/lib/welcomeappview.coffee
@@ -1,89 +1,40 @@
-JView = require 'app/jview'
-FooterView = require 'app/commonviews/footerview'
+kd = require 'kd'

-
-module.exports = class WelcomeAppView extends JView
+module.exports = class WelcomeAppView extends kd.View

   constructor:->

     super

-    @once 'viewAppended', -> global.scrollTo 0, 0

-    @footer = new FooterView
-
-  pistachio : ->
-    """
-

-
Welcome Koding

-

-        Koding is a developer community and cloud development environment where
-        developers come together and code in the browser – with a real development
-        server to run their code. Developers can work, collaborate, write and
-        run apps without jumping through hoops and spending unnecessary money.
-

-

-        Koding gives developers three essentials that they need as they begin their
-        cloud based development projects:
-

-

-
A robust development environment in the form of virtual machines(VMs)

-
Amazing development tools in the form of an online IDE and browser based Terminal to operate on this VM

-
A vibrant community where developers can exchange ideas and learn

-

-

-        So if you have a project that you are thinking of starting or if you want to learn
-        a new technology or if you just want to tinker, Koding is right place to do it.
-        Say goodbye to your localhost and develop in the cloud.
-

-

-

-

-
18 Million+ VMs spun up

-
1 Billion+lines of code

-
500,000+developers

-

-

-

-

-
Get in touch with us

-        business@koding.com
-        support@koding.com
-        apply@koding.com
-

-

-

-
Where we are?

-      <address>358 Brannan Street, San Francisco, CA, 94107</address>
-

-
-

-

-

-

-
Brand Assets

-

-

-            Koding Logo

-            .pdf .ai
-

-

-

-

-

-

-

-            Koding Font

-            Google webfonts
-

-

-

-            google.com/fonts/specimen/Asap
-

-

-

-

-    {{> @footer}}
-    """
-
-
+    @addSubView new kd.CustomHTMLView
+      tagName : 'section'
+      partial : """
+

+
Welcome! let's get started.

+

+          Koding lets your team collaborate and
+          work faster, with a development
+          environment in the cloud
+

+

+

+
+              1
+

+                Configure user environment
+                Setup machine stacks & add their codebase
+

+
+

+

+
+              2
+

+                Invite your team
+                Send out invites to your developers
+

+
+

+

+        """


Less confusing variable name

Less confusing variable name

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -45,7 +45,7 @@
     @state =
       editing: false
       showNewReply: false
-      showReplies: !@isDeleted()
+      expandReplies: !@isDeleted()

   render: =>
@@ -58,7 +58,7 @@

     repliesClass = 'comment__replies'
     repliesClass += ' comment__replies--indented' if @props.depth < MAX_DEPTH
-    repliesClass += ' comment__replies--hidden' if !@state.showReplies
+    repliesClass += ' comment__replies--hidden' if !@state.expandReplies

     div
       className: osu.classWithModifiers 'comment', modifiers
@@ -68,7 +68,7 @@
           className: 'comment__top-show-replies'
           type: 'button'
           onClick: @toggleReplies
-          span className: "fas #{if @state.showReplies then 'fa-angle-up' else 'fa-angle-down'}"
+          span className: "fas #{if @state.expandReplies then 'fa-angle-up' else 'fa-angle-down'}"

       div className: "comment__main #{if @isDeleted() then 'comment__main--deleted' else ''}",
         if user.id?
@@ -175,7 +175,7 @@
                     type: 'button'
                     className: 'comment__action'
                     onClick: @toggleReplies
-                    "[#{if @state.showReplies then '-' else '+'}] "
+                    "[#{if @state.expandReplies then '-' else '+'}] "
                     osu.trans('comments.replies')
                     " (#{@props.comment.replies_count.toLocaleString()})"
                 else
@@ -327,4 +327,4 @@

   toggleReplies: =>
-    @setState showReplies: !@state.showReplies
+    @setState expandReplies: !@state.expandReplies


Referral: move referral code to Account app

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -91,7 +91,12 @@
       Privacy policy
       """

-  showReferrerModal:-> new ReferrerModal
+  showReferrerModal:(options={})->
+    options.top         or= 50
+    options.left        or= 35
+    options.arrowMargin or= 110
+
+    return new ReferrerModal options

   displayConfirmEmailModal:(name, username, callback=noop)->
@@ -130,3 +135,100 @@
         title     : "Check your email"
         content   : "We've sent you a confirmation mail."
         duration  : 4500
+
+  redeemReferralPoint:(modal)->
+    {vmToResize, sizes} = modal.modal.modalTabs.forms.Redeem.inputs
+
+    data = {
+      vmName : vmToResize.getValue(),
+      size   : sizes.getValue(),
+      type   : "disk"
+    }
+
+    KD.remote.api.JReferral.redeem data, (err, refRes)->
+      return KD.showError err if err
+      modal.modal.destroy()
+      KD.getSingleton("vmController").resizeDisk data.vm, (err, res)->
+        return KD.showError err if err
+        KD.getSingleton("vmController").emit "ReferralCountUpdated"
+
+        KD.notify_ """
+            #{refRes.addedSize} #{refRes.unit} extra #{refRes.type} is successfully added to your #{refRes.vm} VM.
+          """
+
+        appManager = KD.getSingleton "appManager"
+        appManager.tell "Account", "showReferrerModal",
+          title       : "Want more?"
+
+
+  showRedeemReferralPointModal:->
+    vmController = KD.getSingleton("vmController")
+    vmController.fetchVMs yes, (err, vms)=>
+      return KD.showError err if err
+      return KD.notify_ "You don't have any VMs. Please create one VM" if not vms or vms.length < 1
+
+      KD.remote.api.JReferral.fetchRedeemableReferrals { type: "disk" }, (err, referals)=>
+        return KD.showError err if err
+        return KD.notify_ "You dont have any referrals" if not referals or referals.length < 1
+
+        @modal = modal = new KDModalViewWithForms
+          title                   : "Redeem Your Referral Points"
+          cssClass                : "redeem-modal"
+          content                 : ""
+          overlay                 : yes
+          width                   : 500
+          height                  : "auto"
+          tabs                    :
+            forms                 :
+              Redeem               :
+                callback          : =>
+                  @modal.modalTabs.forms.Redeem.buttons.redeemButton.showLoader()
+                  @redeemReferralPoint @
+                buttons           :
+                  redeemButton    :
+                    title         : "Redeem"
+                    style         : "modal-clean-gray"
+                    type          : "submit"
+                    loader        :
+                      color       : "#444444"
+                      diameter    : 12
+                    callback      : -> @hideLoader()
+                  cancel          :
+                    title         : "Cancel"
+                    style         : "modal-cancel"
+                    callback      : (event)-> modal.destroy()
+                fields            :
+                  vmToResize    :
+                    label         : "Select a WM to resize"
+                    cssClass      : "clearfix"
+                    itemClass     : KDSelectBox
+                    type          : "select"
+                    name          : "vmToResize"
+                    validate      :
+                      rules       :
+                        required  : yes
+                      messages    :
+                        required  : "You must select a VM!"
+                    selectOptions : (cb)->
+                      options = for vm in vms
+                        ( title : vm, value : vm)
+                      cb options
+                  sizes           :
+                    label         : "Select Size"
+                    cssClass      : "clearfix"
+                    itemClass     : KDSelectBox
+                    type          : "select"
+                    name          : "size"
+                    validate      :
+                      rules       :
+                        required  : yes
+                      messages    :
+                        required  : "You must select a size!"
+                    selectOptions : (cb)=>
+                      options = []
+                      previousTotal = 0
+                      referals.forEach (referal, i)->
+                        previousTotal += referal.amount
+                        options.push ( title : "#{previousTotal} #{referal.unit}" , value : previousTotal)
+                      cb options
+


Account: minor routing improvement for account settings

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -2,7 +2,10 @@

   handler = (callback)->
     if KD.isLoggedIn()
-      KD.singleton('appManager').open 'Account', callback
+      appManager = KD.singleton('appManager')
+      if appManager.getFrontApp()?.getOption('name') is 'Account'
+        callback appManager.getFrontApp()
+      else appManager.open 'Account', callback
     else
       KD.singletons.router.handleRoute '/'


loggedout: fix youtube embed link

loggedout: fix youtube embed link

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -231,7 +231,7 @@
       Every Koding user gets a private virtual machine with root access. Let's give you one in 10 seconds so that you can
       code, collaborate and have fun! :)

-
+

       Click play to see what Koding is all about in 2 minutes!
       """


Remove more console.logs

Remove more console.logs

--- test/bodyPartTest.coffee
+++ test/bodyPartTest.coffee
@@ -3,8 +3,6 @@
 chai.should()
 require('app-module-path').addPath(__dirname + '/../src')
 require("bodyPart")
-
-

 describe "BodyPart", ->
   harnessPart = ->


group all the io into a single object

group all the io into a single object

--- led-server.coffee
+++ led-server.coffee
@@ -1,10 +1,13 @@
 express = require 'express'
 gpio = require 'gpio'
 app = express()
-gpio18 = gpio.export 18
-gpio23 = gpio.export 23
-gpio22 = gpio.export 22, {
-   direction: 'in'
+
+io = {
+  18: gpio.export 18
+  23: gpio.export 23
+  22: gpio.export 22, {
+     direction: 'in'
+  }
 }

 beatsInterval = null
@@ -18,20 +21,20 @@

 app.get '/on/18', (req, res) ->
-  gpio18.set()
+  io[18].set()
   res.send('on')

 app.get '/off/18', (req, res) ->
-  gpio18.reset()
+  io[18].reset()
   clearInterval beatsInterval
   res.send('off')

 app.get '/beats/18', (req, res) ->
   beatsInterval = setInterval ->
-    gpio18.set()
+    io[18].set()
     setTimeout ->
-      gpio18.reset()
+      io[18].reset()
     , 200
   , 1000
   res.send('beats started')
@@ -39,23 +42,23 @@

 app.get '/toggle/18', (req, res) ->
-  if gpio18.state == 0
-    gpio18.set()
+  if io[18].state == 0
+    io[18].set()
   else
-    gpio18.reset()
-  res.send("toggled to #{gpio18.state == 0 ? 'on' : 'off'}")
+    io[18].reset()
+  res.send("toggled to #{io[18].state == 0 ? 'on' : 'off'}")

-gpio18.on 'change', (val) ->
+io[18].on 'change', (val) ->
   # value will report either 1 or 0 (number) when the value changes
-   gpio18.state = val
+   io[18].state = val

-gpio22.on 'change', (val) ->
+io[22].on 'change', (val) ->
   console.log "22 changed to #{val}"

-gpio22.on 'change', (val) ->
-  gpio23.set(val)
+io[22].on 'change', (val) ->
+  io[23].set(val)

 app.listen app.get('port'), ->