diff --git a/Gemfile.lock b/Gemfile.lock
index 6f5dce38b..975954b24 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,7 +5,6 @@ PATH
active_model_serializers (~> 0.9.3)
ansi_stream (~> 0.0.6)
autoprefixer-rails (~> 6.4.1)
- coffee-rails (~> 5.0)
explicit-parameters (~> 0.4.0)
faraday (~> 1.3)
faraday-http-cache (~> 2.2)
@@ -126,13 +125,6 @@ GEM
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- coffee-rails (5.0.0)
- coffee-script (>= 2.2.0)
- railties (>= 5.2.0)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.12.2)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crack (1.0.0)
@@ -176,13 +168,13 @@ GEM
faraday-http-cache (2.5.0)
faraday (>= 0.8)
faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
+ faraday-multipart (1.2.0)
+ multipart-post (~> 2.0)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
- faraday-retry (1.0.3)
+ faraday-retry (1.0.4)
gemoji (2.1.0)
globalid (1.2.1)
activesupport (>= 6.1)
@@ -215,7 +207,10 @@ GEM
marcel (1.0.4)
method_source (1.0.0)
mini_mime (1.1.5)
- minitest (5.25.5)
+ mini_portile2 (2.8.9)
+ minitest (6.0.2)
+ drb (~> 2.0)
+ prism (~> 1.5)
mocha (2.4.5)
ruby2_keywords (>= 0.0.5)
msgpack (1.7.1)
@@ -232,6 +227,9 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
+ nokogiri (1.18.9)
+ mini_portile2 (~> 2.8.2)
+ racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu)
@@ -263,7 +261,7 @@ GEM
ast (~> 2.4.1)
racc
pg (1.3.3)
- prism (1.4.0)
+ prism (1.9.0)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -373,6 +371,8 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
spy (1.0.2)
+ sqlite3 (2.6.0)
+ mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu)
state_machines (0.5.0)
diff --git a/app/assets/javascripts/merge_status.coffee b/app/assets/javascripts/merge_status.coffee
deleted file mode 100644
index da00694c8..000000000
--- a/app/assets/javascripts/merge_status.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-class MergeStatusPoller
- POLL_INTERVAL = 3000
-
- constructor: ->
- @request = {abort: ->}
- @previousLastModified = null
- @timeoutId = null
-
- start: ->
- @timeoutId = setTimeout(@refreshPage, POLL_INTERVAL)
- @
-
- stop: ->
- @request.abort()
- clearTimeout(@timeoutId)
- @
-
- onPageChange: =>
- window.parent.postMessage({event: 'hctw:height:change', height: document.body.clientHeight, service: 'shipit'}, '*')
- window.parent.postMessage({event: 'hctw:stack:info', queue_enabled: @isMergeQueueEnabled(), status: @mergeStatus(), service: 'shipit'}, '*')
-
- fetchPage: (url, callback) ->
- request = @request = new XMLHttpRequest()
- request.onreadystatechange = ->
- if request.readyState == XMLHttpRequest.DONE
- callback(request.status == 200 && request.responseText, request)
- request.open('GET', url, true)
- request.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
- request.send()
-
- previousLastModified = null
- refreshPage: =>
- @fetchPage window.location.toString(), (html, response) =>
- @updateDocument(html, response)
- setTimeout(@refreshPage, POLL_INTERVAL)
-
- updateDocument: (html, response) =>
- lastModified = response.getResponseHeader('last-modified')
- if !lastModified || lastModified != @previousLastModified
- @previousLastModified = lastModified
- if html && container = document.querySelector('[data-layout-content]')
- container.innerHTML = html
- @onPageChange()
-
- isMergeQueueEnabled: =>
- document.querySelector('.merge-status-container .js-details-container')?.hasAttribute('data-queue-enabled')
-
- mergeStatus: =>
- document.querySelector('.merge-status-container .js-details-container')?.getAttribute('data-merge-status') || 'unknown'
-
-class AjaxAction
- constructor: (@poller) ->
- document.addEventListener('submit', @submit, false)
-
- submit: (event) =>
- return unless event.target.getAttribute('data-remote') == 'true'
-
- event.preventDefault()
-
- @poller.stop()
- @disableButtons(event.target)
- @submitFormAsynchronously event.target, (html, request) =>
- @poller.updateDocument(html, request)
- @poller.start()
-
- submitFormAsynchronously: (form, callback) ->
- request = new XMLHttpRequest()
- request.onreadystatechange = ->
- if request.readyState == XMLHttpRequest.DONE
- callback(request.status == 200 && request.responseText, request)
- request.open(form.method.toLocaleUpperCase(), form.action, true)
- request.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
- request.send(new FormData(form))
-
- disableButtons: (form) ->
- for button in form.querySelectorAll('[data-disable-with]')
- button.disabled = true
- button.textContent = button.getAttribute('data-disable-with')
-
-poller = new MergeStatusPoller
-poller.onPageChange()
-poller.start()
-new AjaxAction(poller)
diff --git a/app/assets/javascripts/merge_status.js b/app/assets/javascripts/merge_status.js
new file mode 100644
index 000000000..0a2b844e7
--- /dev/null
+++ b/app/assets/javascripts/merge_status.js
@@ -0,0 +1,155 @@
+// Generated by CoffeeScript 1.12.7
+var AjaxAction, MergeStatusPoller, poller,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+MergeStatusPoller = (function() {
+ var POLL_INTERVAL, previousLastModified;
+
+ POLL_INTERVAL = 3000;
+
+ function MergeStatusPoller() {
+ this.mergeStatus = bind(this.mergeStatus, this);
+ this.isMergeQueueEnabled = bind(this.isMergeQueueEnabled, this);
+ this.updateDocument = bind(this.updateDocument, this);
+ this.refreshPage = bind(this.refreshPage, this);
+ this.onPageChange = bind(this.onPageChange, this);
+ this.request = {
+ abort: function() {}
+ };
+ this.previousLastModified = null;
+ this.timeoutId = null;
+ }
+
+ MergeStatusPoller.prototype.start = function() {
+ this.timeoutId = setTimeout(this.refreshPage, POLL_INTERVAL);
+ return this;
+ };
+
+ MergeStatusPoller.prototype.stop = function() {
+ this.request.abort();
+ clearTimeout(this.timeoutId);
+ return this;
+ };
+
+ MergeStatusPoller.prototype.onPageChange = function() {
+ window.parent.postMessage({
+ event: 'hctw:height:change',
+ height: document.body.clientHeight,
+ service: 'shipit'
+ }, '*');
+ return window.parent.postMessage({
+ event: 'hctw:stack:info',
+ queue_enabled: this.isMergeQueueEnabled(),
+ status: this.mergeStatus(),
+ service: 'shipit'
+ }, '*');
+ };
+
+ MergeStatusPoller.prototype.fetchPage = function(url, callback) {
+ var request;
+ request = this.request = new XMLHttpRequest();
+ request.onreadystatechange = function() {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ return callback(request.status === 200 && request.responseText, request);
+ }
+ };
+ request.open('GET', url, true);
+ request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ return request.send();
+ };
+
+ previousLastModified = null;
+
+ MergeStatusPoller.prototype.refreshPage = function() {
+ return this.fetchPage(window.location.toString(), (function(_this) {
+ return function(html, response) {
+ _this.updateDocument(html, response);
+ return setTimeout(_this.refreshPage, POLL_INTERVAL);
+ };
+ })(this));
+ };
+
+ MergeStatusPoller.prototype.updateDocument = function(html, response) {
+ var container, lastModified;
+ lastModified = response.getResponseHeader('last-modified');
+ if (!lastModified || lastModified !== this.previousLastModified) {
+ this.previousLastModified = lastModified;
+ if (html && (container = document.querySelector('[data-layout-content]'))) {
+ container.innerHTML = html;
+ return this.onPageChange();
+ }
+ }
+ };
+
+ MergeStatusPoller.prototype.isMergeQueueEnabled = function() {
+ var ref;
+ return (ref = document.querySelector('.merge-status-container .js-details-container')) != null ? ref.hasAttribute('data-queue-enabled') : void 0;
+ };
+
+ MergeStatusPoller.prototype.mergeStatus = function() {
+ var ref;
+ return ((ref = document.querySelector('.merge-status-container .js-details-container')) != null ? ref.getAttribute('data-merge-status') : void 0) || 'unknown';
+ };
+
+ return MergeStatusPoller;
+
+})();
+
+AjaxAction = (function() {
+ function AjaxAction(poller1) {
+ this.poller = poller1;
+ this.submit = bind(this.submit, this);
+ document.addEventListener('submit', this.submit, false);
+ }
+
+ AjaxAction.prototype.submit = function(event) {
+ if (event.target.getAttribute('data-remote') !== 'true') {
+ return;
+ }
+ event.preventDefault();
+ this.poller.stop();
+ this.disableButtons(event.target);
+ return this.submitFormAsynchronously(event.target, (function(_this) {
+ return function(html, request) {
+ _this.poller.updateDocument(html, request);
+ return _this.poller.start();
+ };
+ })(this));
+ };
+
+ AjaxAction.prototype.submitFormAsynchronously = function(form, callback) {
+ var request;
+ request = new XMLHttpRequest();
+ request.onreadystatechange = function() {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ return callback(request.status === 200 && request.responseText, request);
+ }
+ };
+ request.open(form.method.toLocaleUpperCase(), form.action, true);
+ request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ return request.send(new FormData(form));
+ };
+
+ AjaxAction.prototype.disableButtons = function(form) {
+ var button, i, len, ref, results;
+ ref = form.querySelectorAll('[data-disable-with]');
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ button = ref[i];
+ button.disabled = true;
+ results.push(button.textContent = button.getAttribute('data-disable-with'));
+ }
+ return results;
+ };
+
+ return AjaxAction;
+
+})();
+
+poller = new MergeStatusPoller;
+
+poller.onPageChange();
+
+poller.start();
+
+new AjaxAction(poller);
diff --git a/app/assets/javascripts/shipit.js b/app/assets/javascripts/shipit.js
new file mode 100644
index 000000000..bf701e806
--- /dev/null
+++ b/app/assets/javascripts/shipit.js
@@ -0,0 +1,52 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// compiled file.
+//
+// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
+// about supported directives.
+//
+//= require jquery
+//= require jquery_ujs
+//= require rails-timeago
+//= require jquery-notify
+//= require_tree ./shipit
+//= require_self
+
+$(document).on('click', '.disabled, .btn--disabled', function(event) {
+ event.preventDefault();
+});
+
+$(document).on('click', '.banner__dismiss', function(event) {
+ $(event.target).closest('.banner').addClass('hidden');
+});
+
+$(document).on('click', '.enable-notifications .banner__dismiss', function(event) {
+ localStorage.setItem("dismissed-enable-notifications", true);
+});
+
+$(document).on('click', '.github-status .banner__dismiss', function(event) {
+ localStorage.setItem("dismissed-github-status", true);
+});
+
+jQuery(function() {
+ var $button, $notificationNotice;
+ if (!(localStorage.getItem("dismissed-enable-notifications"))) {
+ $notificationNotice = $('.enable-notifications');
+ if ($.notifyCheck() === $.NOTIFY_NOT_ALLOWED) {
+ $button = $notificationNotice.find('button');
+ $button.on('click', function() {
+ $.notifyRequest();
+ $notificationNotice.addClass('hidden');
+ });
+ $notificationNotice.removeClass('hidden');
+ }
+ }
+ if (!(localStorage.getItem("dismissed-github-status"))) {
+ $('.github-status').removeClass('hidden');
+ }
+});
diff --git a/app/assets/javascripts/shipit.js.coffee b/app/assets/javascripts/shipit.js.coffee
deleted file mode 100644
index 0caa0aab1..000000000
--- a/app/assets/javascripts/shipit.js.coffee
+++ /dev/null
@@ -1,45 +0,0 @@
-# This is a manifest file that'll be compiled into application.js, which will include all the files
-# listed below.
-#
-# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-#
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# compiled file.
-#
-# Read Sprockets README (https:#github.com/sstephenson/sprockets#sprockets-directives) for details
-# about supported directives.
-#
-#= require jquery
-#= require jquery_ujs
-#= require rails-timeago
-#= require jquery-notify
-#= require_tree ./shipit
-#= require_self
-
-$(document).on 'click', '.disabled, .btn--disabled', (event) ->
- event.preventDefault()
-
-$(document).on 'click', '.banner__dismiss', (event) ->
- $(event.target).closest('.banner').addClass('hidden')
-
-$(document).on 'click', '.enable-notifications .banner__dismiss', (event) ->
- localStorage.setItem("dismissed-enable-notifications", true)
-
-$(document).on 'click', '.github-status .banner__dismiss', (event) ->
- localStorage.setItem("dismissed-github-status", true)
-
-jQuery ->
- unless(localStorage.getItem("dismissed-enable-notifications"))
- $notificationNotice = $('.enable-notifications')
-
- if $.notifyCheck() == $.NOTIFY_NOT_ALLOWED
- $button = $notificationNotice.find('button')
- $button.on 'click', ->
- $.notifyRequest()
- $notificationNotice.addClass('hidden')
- $notificationNotice.removeClass('hidden')
-
- unless(localStorage.getItem("dismissed-github-status"))
- $('.github-status').removeClass('hidden')
-
diff --git a/app/assets/javascripts/shipit/checklist.js b/app/assets/javascripts/shipit/checklist.js
new file mode 100644
index 000000000..f782990bc
--- /dev/null
+++ b/app/assets/javascripts/shipit/checklist.js
@@ -0,0 +1,13 @@
+var $document, toggleDeployButton;
+
+$document = $(document);
+
+toggleDeployButton = function() {
+ $('.trigger-deploy').toggleClass('disabled btn--disabled', !!$(':checkbox.required:not(:checked)').length);
+};
+
+$document.on('change', ':checkbox.required', toggleDeployButton);
+
+jQuery(function($) {
+ toggleDeployButton();
+});
diff --git a/app/assets/javascripts/shipit/checklist.js.coffee b/app/assets/javascripts/shipit/checklist.js.coffee
deleted file mode 100644
index db7394f36..000000000
--- a/app/assets/javascripts/shipit/checklist.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-$document = $(document)
-
-toggleDeployButton = ->
- $('.trigger-deploy').toggleClass('disabled btn--disabled', !!$(':checkbox.required:not(:checked)').length)
-
-$document.on('change', ':checkbox.required', toggleDeployButton)
-
-jQuery ($) ->
- toggleDeployButton()
diff --git a/app/assets/javascripts/shipit/continuous_delivery_schedule.js b/app/assets/javascripts/shipit/continuous_delivery_schedule.js
new file mode 100644
index 000000000..c54dade24
--- /dev/null
+++ b/app/assets/javascripts/shipit/continuous_delivery_schedule.js
@@ -0,0 +1,17 @@
+$(document).on("click", ".continuous-delivery-schedule [data-action='copy-to-all']", function(event) {
+ var form, mondayEnd, mondayStart;
+ form = event.target.closest("form");
+ mondayStart = form.elements.namedItem("continuous_delivery_schedule[monday_start]").value;
+ mondayEnd = form.elements.namedItem("continuous_delivery_schedule[monday_end]").value;
+ Array.from(form.elements).forEach(function(formElement) {
+ if (formElement.type !== "time") {
+ return;
+ }
+ if (formElement.name.endsWith("_start]")) {
+ formElement.value = mondayStart;
+ }
+ if (formElement.name.endsWith("_end]")) {
+ formElement.value = mondayEnd;
+ }
+ });
+});
diff --git a/app/assets/javascripts/shipit/continuous_delivery_schedule.js.coffee b/app/assets/javascripts/shipit/continuous_delivery_schedule.js.coffee
deleted file mode 100644
index b8750ffb0..000000000
--- a/app/assets/javascripts/shipit/continuous_delivery_schedule.js.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-$(document)
- .on "click", ".continuous-delivery-schedule [data-action='copy-to-all']", (event) ->
- form = event.target.closest("form");
-
- mondayStart = form.elements.namedItem("continuous_delivery_schedule[monday_start]").value
- mondayEnd = form.elements.namedItem("continuous_delivery_schedule[monday_end]").value
-
- Array.from(form.elements).forEach (formElement) ->
- return unless formElement.type == "time"
-
- if formElement.name.endsWith("_start]")
- formElement.value = mondayStart
-
- if formElement.name.endsWith("_end]")
- formElement.value = mondayEnd
diff --git a/app/assets/javascripts/shipit/deploy.js b/app/assets/javascripts/shipit/deploy.js
new file mode 100644
index 000000000..0e3ceff37
--- /dev/null
+++ b/app/assets/javascripts/shipit/deploy.js
@@ -0,0 +1,59 @@
+var AbortButton,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+AbortButton = (function() {
+ var SELECTOR;
+
+ SELECTOR = '[data-action="abort"]';
+
+ AbortButton.listen = function() {
+ return $(document).on('click', SELECTOR, this.handle);
+ };
+
+ AbortButton.handle = function(event) {
+ var button;
+ event.preventDefault();
+ button = new AbortButton($(event.currentTarget));
+ button.trigger();
+ };
+
+ function AbortButton($button) {
+ this.$button = $button;
+ this.reenable = bind(this.reenable, this);
+ this.waitForCompletion = bind(this.waitForCompletion, this);
+ this.url = this.$button.attr('href');
+ this.shouldRollback = this.$button.data('rollback');
+ }
+
+ AbortButton.prototype.trigger = function() {
+ if (this.isDisabled()) {
+ return false;
+ }
+ this.disable();
+ this.waitForCompletion();
+ $.post(this.url).success(this.waitForCompletion).error(this.reenable);
+ };
+
+ AbortButton.prototype.waitForCompletion = function() {
+ setTimeout(this.reenable, 3000);
+ };
+
+ AbortButton.prototype.reenable = function() {
+ this.$button.removeClass('pending btn-disabled');
+ this.$button.siblings(SELECTOR).removeClass('btn-disabled');
+ };
+
+ AbortButton.prototype.disable = function() {
+ this.$button.addClass('pending btn-disabled');
+ this.$button.siblings(SELECTOR).addClass('btn-disabled');
+ };
+
+ AbortButton.prototype.isDisabled = function() {
+ return this.$button.hasClass('btn-disabled');
+ };
+
+ return AbortButton;
+
+})();
+
+AbortButton.listen();
diff --git a/app/assets/javascripts/shipit/deploy.js.coffee b/app/assets/javascripts/shipit/deploy.js.coffee
deleted file mode 100644
index b7317d04a..000000000
--- a/app/assets/javascripts/shipit/deploy.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-class AbortButton
- SELECTOR = '[data-action="abort"]'
-
- @listen: ->
- $(document).on('click', SELECTOR, @handle)
-
- @handle: (event) =>
- event.preventDefault()
- button = new this($(event.currentTarget))
- button.trigger()
-
- constructor: (@$button) ->
- @url = @$button.attr('href')
- @shouldRollback = @$button.data('rollback')
-
- trigger: ->
- return false if @isDisabled()
-
- @disable()
- @waitForCompletion()
- $.post(@url).success(@waitForCompletion).error(@reenable)
-
- waitForCompletion: =>
- setTimeout(@reenable, 3000)
-
- reenable: =>
- @$button.removeClass('pending btn-disabled')
- @$button.siblings(SELECTOR).removeClass('btn-disabled')
-
- disable: ->
- @$button.addClass('pending btn-disabled')
- @$button.siblings(SELECTOR).addClass('btn-disabled')
-
- isDisabled: ->
- @$button.hasClass('btn-disabled')
-
-AbortButton.listen()
diff --git a/app/assets/javascripts/shipit/flash.js b/app/assets/javascripts/shipit/flash.js
new file mode 100644
index 000000000..66c6e4e6a
--- /dev/null
+++ b/app/assets/javascripts/shipit/flash.js
@@ -0,0 +1,5 @@
+jQuery(function($) {
+ setTimeout((function() {
+ $('.flash-success').remove();
+ }), 3000);
+});
diff --git a/app/assets/javascripts/shipit/flash.js.coffee b/app/assets/javascripts/shipit/flash.js.coffee
deleted file mode 100644
index 6751bee97..000000000
--- a/app/assets/javascripts/shipit/flash.js.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-jQuery ($) ->
- setTimeout((-> $('.flash-success').remove()), 3000)
diff --git a/app/assets/javascripts/shipit/page_updater.js b/app/assets/javascripts/shipit/page_updater.js
new file mode 100644
index 000000000..9a138700e
--- /dev/null
+++ b/app/assets/javascripts/shipit/page_updater.js
@@ -0,0 +1,120 @@
+var PageUpdater,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+PageUpdater = (function() {
+ var DEBOUNCE, MAX_RETRIES, RETRY_DELAY;
+
+ DEBOUNCE = 100;
+
+ RETRY_DELAY = 5000;
+
+ MAX_RETRIES = 5;
+
+ PageUpdater.callbacks = [];
+
+ PageUpdater.afterUpdate = function(callback) {
+ this.callbacks.push(callback);
+ };
+
+ function PageUpdater(channel, selectors) {
+ this.channel = channel;
+ this.selectors = selectors;
+ this.updatePage = bind(this.updatePage, this);
+ this.fetchPage = bind(this.fetchPage, this);
+ this.scheduleUpdate = bind(this.scheduleUpdate, this);
+ this.requestUpdate = bind(this.requestUpdate, this);
+ this.parser = new DOMParser();
+ this.source = this.listen();
+ this.previousLastModified = null;
+ }
+
+ PageUpdater.prototype.requestUpdate = function() {
+ this.updateRequested = true;
+ this.scheduleUpdate();
+ };
+
+ PageUpdater.prototype.scheduleUpdate = function() {
+ if (this.updateScheduled) {
+ return;
+ }
+ if (!this.updateRequested) {
+ return;
+ }
+ setTimeout(this.fetchPage, DEBOUNCE);
+ this.updateScheduled = true;
+ };
+
+ PageUpdater.prototype.fetchPage = function(message) {
+ this.updateRequested = false;
+ jQuery.get(window.location.toString()).done(this.updatePage).fail((function(_this) {
+ return function() {
+ _this.updateScheduled = false;
+ };
+ })(this));
+ };
+
+ PageUpdater.prototype.updatePage = function(html, status, response) {
+ var callback, i, j, lastModified, len, len1, newDocument, ref, ref1, selector;
+ lastModified = response.getResponseHeader('last-modified');
+ if ((lastModified != null) && lastModified !== this.previousLastModified) {
+ this.previousLastModified = lastModified;
+ newDocument = this.parser.parseFromString(html, 'text/html');
+ ref = this.selectors;
+ for (i = 0, len = ref.length; i < len; i++) {
+ selector = ref[i];
+ $(selector).html(newDocument.querySelectorAll(selector + " > *"));
+ }
+ ref1 = PageUpdater.callbacks;
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ callback = ref1[j];
+ callback();
+ }
+ }
+ this.updateScheduled = false;
+ };
+
+ PageUpdater.prototype.listen = function() {
+ this.source = new EventSource(this.channel);
+ this.source.addEventListener('update', this.requestUpdate);
+ this.retries = MAX_RETRIES;
+ this.interval = setInterval((function(_this) {
+ return function() {
+ switch (_this.source.readyState) {
+ case _this.source.CLOSED:
+ clearInterval(_this.interval);
+ if (_this.retries > 0) {
+ _this.retries -= 1;
+ _this.listen();
+ }
+ break;
+ default:
+ _this.retries = MAX_RETRIES;
+ }
+ };
+ })(this), RETRY_DELAY);
+ };
+
+ return PageUpdater;
+
+})();
+
+jQuery(function($) {
+ var channel, e, selectors;
+ PageUpdater.afterUpdate(function() {
+ $('time[data-time-ago]').timeago();
+ });
+ channel = $('meta[name=subscription-channel]').attr('content');
+ selectors = (function() {
+ var i, len, ref, results;
+ ref = $('meta[name=subscription-selector]');
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ e = ref[i];
+ results.push(e.content);
+ }
+ return results;
+ })();
+ if (channel && selectors) {
+ new PageUpdater(channel, selectors);
+ }
+});
diff --git a/app/assets/javascripts/shipit/page_updater.js.coffee b/app/assets/javascripts/shipit/page_updater.js.coffee
deleted file mode 100644
index 26f60635c..000000000
--- a/app/assets/javascripts/shipit/page_updater.js.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-class PageUpdater
- DEBOUNCE = 100
- RETRY_DELAY = 5000
- MAX_RETRIES = 5
-
- @callbacks: []
- @afterUpdate: (callback) ->
- @callbacks.push(callback)
-
- constructor: (@channel, @selectors) ->
- @parser = new DOMParser()
- @source = @listen()
- @previousLastModified = null
-
- requestUpdate: =>
- @updateRequested = true
- @scheduleUpdate()
-
- scheduleUpdate: =>
- return if @updateScheduled
- return unless @updateRequested
- setTimeout(@fetchPage, DEBOUNCE)
- @updateScheduled = true
-
- fetchPage: (message) =>
- @updateRequested = false
- jQuery.get(window.location.toString()).done(@updatePage).fail(=> @updateScheduled = false)
-
- updatePage: (html, status, response) =>
- lastModified = response.getResponseHeader('last-modified')
- if lastModified? and lastModified != @previousLastModified
- @previousLastModified = lastModified
-
- newDocument = @parser.parseFromString(html, 'text/html')
- for selector in @selectors
- $(selector).html(newDocument.querySelectorAll("#{selector} > *"))
- for callback in PageUpdater.callbacks
- callback()
-
- @updateScheduled = false
-
- listen: ->
- @source = new EventSource(@channel)
- @source.addEventListener('update', @requestUpdate)
- @retries = MAX_RETRIES
- @interval = setInterval =>
- switch @source.readyState
- when @source.CLOSED
- clearInterval(@interval)
- if @retries > 0
- @retries -= 1
- @listen()
- else
- @retries = MAX_RETRIES
- , RETRY_DELAY
-
-jQuery ($) ->
- PageUpdater.afterUpdate -> $('time[data-time-ago]').timeago()
-
- channel = $('meta[name=subscription-channel]').attr('content')
- selectors = (e.content for e in $('meta[name=subscription-selector]'))
- if channel and selectors
- new PageUpdater(channel, selectors)
diff --git a/app/assets/javascripts/shipit/repositories_search.js b/app/assets/javascripts/shipit/repositories_search.js
new file mode 100644
index 000000000..689fcd967
--- /dev/null
+++ b/app/assets/javascripts/shipit/repositories_search.js
@@ -0,0 +1,96 @@
+var KEY, RepositorySearch, search,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+KEY = {
+ UP: 38,
+ DOWN: 40,
+ ENTER: 13
+};
+
+RepositorySearch = (function() {
+ function RepositorySearch(root) {
+ this.onKeyUp = bind(this.onKeyUp, this);
+ this.$root = $(root);
+ this.$root.on('keyup', '.repository-search', this.onKeyUp);
+ this.$root.on('click', '.show-all-repositories', (function(_this) {
+ return function(event) {
+ _this.$root.find('.not-matching').removeClass('not-matching');
+ event.preventDefault();
+ };
+ })(this));
+ }
+
+ RepositorySearch.prototype.onKeyUp = function(event) {
+ this.$items = this.$root.find('[data-search]');
+ switch (event.keyCode) {
+ case KEY.ENTER:
+ event.preventDefault();
+ this.goToSelectedRepository();
+ break;
+ case KEY.UP:
+ event.preventDefault();
+ this.selectPrevious();
+ break;
+ case KEY.DOWN:
+ event.preventDefault();
+ this.selectNext();
+ break;
+ default:
+ this.filterResults($.trim($(event.target).val()).toLowerCase());
+ }
+ };
+
+ RepositorySearch.prototype.filterResults = function(query) {
+ var $item, i, item, len, ref;
+ if (query) {
+ ref = this.$items;
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ $item = $(item);
+ $item.toggleClass('not-matching', indexOf.call($item.attr('data-search').toLowerCase(), query) < 0);
+ }
+ this.selectFirst();
+ } else {
+ this.$items.removeClass('not-matching');
+ }
+ };
+
+ RepositorySearch.prototype.selectFirst = function() {
+ this.$items.removeClass('selected').first(':not(.not-matching)').addClass('selected');
+ };
+
+ RepositorySearch.prototype.selectNext = function() {
+ var $next;
+ $next = this.$items.filter('.selected').removeClass('selected').nextAll(':not(.not-matching)').first();
+ if (!$next.length) {
+ $next = this.$items.filter(':not(.not-matching)').first();
+ }
+ $next.addClass('selected');
+ };
+
+ RepositorySearch.prototype.selectPrevious = function() {
+ var $previous;
+ $previous = this.$items.filter('.selected').removeClass('selected').prevAll(':not(.not-matching)').first();
+ if (!$previous.length) {
+ $previous = this.$items.filter(':not(.not-matching)').last();
+ }
+ $previous.addClass('selected');
+ };
+
+ RepositorySearch.prototype.goToSelectedRepository = function() {
+ var repository;
+ if (repository = this.$items.filter('.selected').filter(':not(.not-matching)').find('.commits-path').attr('href')) {
+ window.location = repository;
+ }
+ };
+
+ return RepositorySearch;
+
+})();
+
+search = new RepositorySearch(document);
+
+jQuery(function() {
+ $('.repository-search').focus();
+});
diff --git a/app/assets/javascripts/shipit/repositories_search.js.coffee b/app/assets/javascripts/shipit/repositories_search.js.coffee
deleted file mode 100644
index c16cdb12d..000000000
--- a/app/assets/javascripts/shipit/repositories_search.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-KEY =
- UP: 38
- DOWN: 40
- ENTER: 13
-
-class RepositorySearch
-
- constructor: (root) ->
- @$root = $(root)
- @$root.on('keyup', '.repository-search', @onKeyUp)
- @$root.on('click', '.show-all-repositories', (event) =>
- @$root.find('.not-matching').removeClass('not-matching')
- event.preventDefault()
- )
-
- onKeyUp: (event) =>
- @$items = @$root.find('[data-search]')
- switch event.keyCode
- when KEY.ENTER
- event.preventDefault()
- @goToSelectedRepository()
- when KEY.UP
- event.preventDefault()
- @selectPrevious()
- when KEY.DOWN
- event.preventDefault()
- @selectNext()
- else
- @filterResults($.trim($(event.target).val()).toLowerCase())
-
- filterResults: (query) ->
- if query
- for item in @$items
- $item = $(item)
- $item.toggleClass('not-matching', query not in $item.attr('data-search').toLowerCase())
- @selectFirst()
- else
- @$items.removeClass('not-matching')
-
- selectFirst: ->
- @$items.removeClass('selected').first(':not(.not-matching)').addClass('selected')
-
- selectNext: ->
- $next = @$items.filter('.selected').removeClass('selected').nextAll(':not(.not-matching)').first()
- $next = @$items.filter(':not(.not-matching)').first() unless $next.length
- $next.addClass('selected')
-
- selectPrevious: ->
- $previous = @$items.filter('.selected').removeClass('selected').prevAll(':not(.not-matching)').first()
- $previous = @$items.filter(':not(.not-matching)').last() unless $previous.length
- $previous.addClass('selected')
-
- goToSelectedRepository: ->
- if repository = @$items.filter('.selected').filter(':not(.not-matching)').find('.commits-path').attr('href')
- window.location = repository
-
-search = new RepositorySearch(document)
-
-jQuery ->
- $('.repository-search').focus()
diff --git a/app/assets/javascripts/shipit/stack_search.js b/app/assets/javascripts/shipit/stack_search.js
new file mode 100644
index 000000000..8a9c62af6
--- /dev/null
+++ b/app/assets/javascripts/shipit/stack_search.js
@@ -0,0 +1,104 @@
+var KEY, StackSearch, search,
+ slice = [].slice,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+if (!String.prototype.contains) {
+ String.prototype.contains = function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return this.indexOf.apply(this, args) !== -1;
+ };
+}
+
+KEY = {
+ UP: 38,
+ DOWN: 40,
+ ENTER: 13
+};
+
+StackSearch = (function() {
+ function StackSearch(root) {
+ this.onKeyUp = bind(this.onKeyUp, this);
+ this.$root = $(root);
+ this.$root.on('keyup', '.stack-search', this.onKeyUp);
+ this.$root.on('click', '.show-all-stacks', (function(_this) {
+ return function(event) {
+ _this.$root.find('.not-matching').removeClass('not-matching');
+ event.preventDefault();
+ };
+ })(this));
+ }
+
+ StackSearch.prototype.onKeyUp = function(event) {
+ this.$items = this.$root.find('[data-search]');
+ switch (event.keyCode) {
+ case KEY.ENTER:
+ event.preventDefault();
+ this.goToSelectedStack();
+ break;
+ case KEY.UP:
+ event.preventDefault();
+ this.selectPrevious();
+ break;
+ case KEY.DOWN:
+ event.preventDefault();
+ this.selectNext();
+ break;
+ default:
+ this.filterResults($.trim($(event.target).val()).toLowerCase());
+ }
+ };
+
+ StackSearch.prototype.filterResults = function(query) {
+ var $item, i, item, len, ref;
+ if (query) {
+ ref = this.$items;
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ $item = $(item);
+ $item.toggleClass('not-matching', !$item.attr('data-search').toLowerCase().contains(query));
+ }
+ this.selectFirst();
+ } else {
+ this.$items.removeClass('not-matching');
+ }
+ };
+
+ StackSearch.prototype.selectFirst = function() {
+ this.$items.removeClass('selected').first(':not(.not-matching)').addClass('selected');
+ };
+
+ StackSearch.prototype.selectNext = function() {
+ var $next;
+ $next = this.$items.filter('.selected').removeClass('selected').nextAll(':not(.not-matching)').first();
+ if (!$next.length) {
+ $next = this.$items.filter(':not(.not-matching)').first();
+ }
+ $next.addClass('selected');
+ };
+
+ StackSearch.prototype.selectPrevious = function() {
+ var $previous;
+ $previous = this.$items.filter('.selected').removeClass('selected').prevAll(':not(.not-matching)').first();
+ if (!$previous.length) {
+ $previous = this.$items.filter(':not(.not-matching)').last();
+ }
+ $previous.addClass('selected');
+ };
+
+ StackSearch.prototype.goToSelectedStack = function() {
+ var stack;
+ if (stack = this.$items.filter('.selected').filter(':not(.not-matching)').find('.commits-path').attr('href')) {
+ window.location = stack;
+ }
+ };
+
+ return StackSearch;
+
+})();
+
+search = new StackSearch(document);
+
+jQuery(function() {
+ $('.stack-search').focus();
+});
diff --git a/app/assets/javascripts/shipit/stack_search.js.coffee b/app/assets/javascripts/shipit/stack_search.js.coffee
deleted file mode 100644
index 2baf24409..000000000
--- a/app/assets/javascripts/shipit/stack_search.js.coffee
+++ /dev/null
@@ -1,64 +0,0 @@
-unless String::contains
- String::contains = (args...) ->
- @indexOf(args...) != -1
-
-KEY =
- UP: 38
- DOWN: 40
- ENTER: 13
-
-class StackSearch
-
- constructor: (root) ->
- @$root = $(root)
- @$root.on('keyup', '.stack-search', @onKeyUp)
- @$root.on('click', '.show-all-stacks', (event) =>
- @$root.find('.not-matching').removeClass('not-matching')
- event.preventDefault()
- )
-
- onKeyUp: (event) =>
- @$items = @$root.find('[data-search]')
- switch event.keyCode
- when KEY.ENTER
- event.preventDefault()
- @goToSelectedStack()
- when KEY.UP
- event.preventDefault()
- @selectPrevious()
- when KEY.DOWN
- event.preventDefault()
- @selectNext()
- else
- @filterResults($.trim($(event.target).val()).toLowerCase())
-
- filterResults: (query) ->
- if query
- for item in @$items
- $item = $(item)
- $item.toggleClass('not-matching', !$item.attr('data-search').toLowerCase().contains(query))
- @selectFirst()
- else
- @$items.removeClass('not-matching')
-
- selectFirst: ->
- @$items.removeClass('selected').first(':not(.not-matching)').addClass('selected')
-
- selectNext: ->
- $next = @$items.filter('.selected').removeClass('selected').nextAll(':not(.not-matching)').first()
- $next = @$items.filter(':not(.not-matching)').first() unless $next.length
- $next.addClass('selected')
-
- selectPrevious: ->
- $previous = @$items.filter('.selected').removeClass('selected').prevAll(':not(.not-matching)').first()
- $previous = @$items.filter(':not(.not-matching)').last() unless $previous.length
- $previous.addClass('selected')
-
- goToSelectedStack: ->
- if stack = @$items.filter('.selected').filter(':not(.not-matching)').find('.commits-path').attr('href')
- window.location = stack
-
-search = new StackSearch(document)
-
-jQuery ->
- $('.stack-search').focus()
diff --git a/app/assets/javascripts/shipit/stacks.js b/app/assets/javascripts/shipit/stacks.js
new file mode 100644
index 000000000..f263cd1a3
--- /dev/null
+++ b/app/assets/javascripts/shipit/stacks.js
@@ -0,0 +1,81 @@
+var $document;
+
+$document = $(document);
+
+$document.on('click', '.commit-lock a', function(event) {
+ var $commit, $link, locked;
+ event.preventDefault();
+ $commit = $(event.target).closest('.commit');
+ $link = $(event.target).closest('a');
+ locked = $commit.hasClass('locked');
+ $commit.toggleClass('locked');
+ $.ajax($link.attr('href'), {
+ method: 'PATCH',
+ data: {
+ commit: {
+ locked: !locked
+ }
+ }
+ });
+});
+
+$document.on('click', '.action-set-release-status', function(event) {
+ var $deploy, $link, newStatus;
+ event.preventDefault();
+ $link = $(event.target).closest('a');
+ $deploy = $link.closest('.deploy');
+ newStatus = $link.data('status');
+ if ($deploy.attr('data-release-status') === newStatus) {
+ return;
+ }
+ $.ajax($link.attr('href'), {
+ method: 'POST',
+ data: {
+ status: newStatus
+ }
+ }).success(function(last_status) {
+ $deploy.attr('data-release-status', last_status.state);
+ });
+});
+
+jQuery(function($) {
+ var dismissIgnoreCiMessage, displayIgnoreCiMessage, getLocalStorageKey;
+ displayIgnoreCiMessage = function() {
+ var ignoreCiMessage;
+ ignoreCiMessage = $(".ignoring-ci");
+ if (!ignoreCiMessage) {
+ return;
+ }
+ $('.dismiss-ignore-ci-warning').click(function(event) {
+ event.preventDefault();
+ dismissIgnoreCiMessage();
+ });
+ if (localStorage.getItem(getLocalStorageKey())) {
+ ignoreCiMessage.hide();
+ }
+ };
+ dismissIgnoreCiMessage = function() {
+ var ignoreCiMessage;
+ localStorage.setItem(getLocalStorageKey(), true);
+ ignoreCiMessage = $(".ignoring-ci");
+ if (ignoreCiMessage) {
+ ignoreCiMessage.hide();
+ }
+ };
+ getLocalStorageKey = function() {
+ var stackName;
+ stackName = $('.repo-name').data('repo-full-name');
+ return "ignoreCIDismissed" + stackName;
+ };
+ displayIgnoreCiMessage();
+ $(document).on('click', '.setting-ccmenu input[type=submit]', function(event) {
+ event.preventDefault();
+ $(event.target).prop('disabled', true);
+ $.get(event.target.dataset.remote).done(function(data) {
+ $('#ccmenu-url').val(data.ccmenu_url).removeClass('hidden');
+ $(event.target).addClass('hidden');
+ }).fail(function() {
+ $(event.target).prop('disabled', false);
+ });
+ });
+});
diff --git a/app/assets/javascripts/shipit/stacks.js.coffee b/app/assets/javascripts/shipit/stacks.js.coffee
deleted file mode 100644
index 84bcc55be..000000000
--- a/app/assets/javascripts/shipit/stacks.js.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-$document = $(document)
-
-$document.on 'click', '.commit-lock a', (event) ->
- event.preventDefault()
- $commit = $(event.target).closest('.commit')
- $link = $(event.target).closest('a')
-
- locked = $commit.hasClass('locked')
- $commit.toggleClass('locked')
-
- $.ajax($link.attr('href'), method: 'PATCH', data: {commit: {locked: !locked}})
-
-$document.on 'click', '.action-set-release-status', (event) ->
- event.preventDefault()
- $link = $(event.target).closest('a')
- $deploy = $link.closest('.deploy')
- newStatus = $link.data('status')
-
- return if $deploy.attr('data-release-status') == newStatus
-
- $.ajax($link.attr('href'), method: 'POST', data: {status: newStatus}).success((last_status) ->
- $deploy.attr('data-release-status', last_status.state)
- )
-
-jQuery ($) ->
- displayIgnoreCiMessage = ->
- ignoreCiMessage = $(".ignoring-ci")
- return unless ignoreCiMessage
- $('.dismiss-ignore-ci-warning').click (event) ->
- event.preventDefault()
- dismissIgnoreCiMessage()
-
- if localStorage.getItem(getLocalStorageKey())
- ignoreCiMessage.hide()
-
- dismissIgnoreCiMessage = ->
- localStorage.setItem(getLocalStorageKey(), true)
- ignoreCiMessage = $(".ignoring-ci")
- ignoreCiMessage.hide() if ignoreCiMessage
-
- getLocalStorageKey = ->
- stackName = $('.repo-name').data('repo-full-name')
- "ignoreCIDismissed" + stackName
-
- displayIgnoreCiMessage()
-
- $(document).on 'click', '.setting-ccmenu input[type=submit]', (event) ->
- event.preventDefault()
- $(event.target).prop('disabled', true)
- $.get(event.target.dataset.remote).done((data) ->
- $('#ccmenu-url').val(data.ccmenu_url).removeClass('hidden')
- $(event.target).addClass('hidden')
- ).fail(->
- $(event.target).prop('disabled', false)
- )
diff --git a/app/assets/javascripts/task.js b/app/assets/javascripts/task.js
new file mode 100644
index 000000000..0e9dce08e
--- /dev/null
+++ b/app/assets/javascripts/task.js
@@ -0,0 +1,35 @@
+//= require string_includes
+//= require mousetrap
+//= require mousetrap-global-bind
+//= require lodash
+//= require clusterize
+//= require_tree ./task
+//= require_self
+
+this.OutputStream = new Stream;
+
+jQuery(function() {
+ var $code, initialOutput, search, task, tty;
+ $code = $('code');
+ initialOutput = $code.attr('data-output');
+ $code.removeAttr('data-output');
+ search = new SearchBar($('.search-bar'));
+ OutputStream.addEventListener('status', function(status, response) {
+ $('[data-status]').attr('data-status', status);
+ if (status === 'aborted' && response.rollback_url) {
+ window.location = response.rollback_url;
+ }
+ });
+ tty = new TTY($('body'));
+ search.addEventListener('query', tty.filterOutput);
+ search.immediateBroadcastQueryChange();
+ OutputStream.addEventListener('chunk', tty.appendChunk);
+ if (task = $('[data-task]').data('task')) {
+ Notifications.init(OutputStream, task);
+ }
+ OutputStream.init({
+ status: $code.closest('[data-status]').data('status'),
+ url: $code.data('next-chunks-url'),
+ text: initialOutput
+ });
+});
diff --git a/app/assets/javascripts/task.js.coffee b/app/assets/javascripts/task.js.coffee
deleted file mode 100644
index 1874635c4..000000000
--- a/app/assets/javascripts/task.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-#= require string_includes
-#= require mousetrap
-#= require mousetrap-global-bind
-#= require lodash
-#= require clusterize
-#= require_tree ./task
-#= require_self
-
-@OutputStream = new Stream
-
-jQuery ->
- $code = $('code')
- initialOutput = $code.attr('data-output')
- $code.removeAttr('data-output')
-
- search = new SearchBar($('.search-bar'))
-
- OutputStream.addEventListener 'status', (status, response) ->
- $('[data-status]').attr('data-status', status)
-
- if status == 'aborted' && response.rollback_url
- window.location = response.rollback_url
-
- tty = new TTY($('body'))
- search.addEventListener('query', tty.filterOutput)
- search.immediateBroadcastQueryChange()
- OutputStream.addEventListener('chunk', tty.appendChunk)
-
- if task = $('[data-task]').data('task')
- Notifications.init(OutputStream, task)
-
- OutputStream.init
- status: $code.closest('[data-status]').data('status')
- url: $code.data('next-chunks-url')
- text: initialOutput
diff --git a/app/assets/javascripts/task/ansi_colors.js b/app/assets/javascripts/task/ansi_colors.js
new file mode 100644
index 000000000..8dc62a6a1
--- /dev/null
+++ b/app/assets/javascripts/task/ansi_colors.js
@@ -0,0 +1,10 @@
+//= require ansi_stream
+//= require ./tty
+
+var stream;
+
+stream = new AnsiStream();
+
+TTY.appendFormatter(function(chunk) {
+ return stream.process(chunk);
+});
diff --git a/app/assets/javascripts/task/ansi_colors.js.coffee b/app/assets/javascripts/task/ansi_colors.js.coffee
deleted file mode 100644
index 0b54c8214..000000000
--- a/app/assets/javascripts/task/ansi_colors.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-#= require ansi_stream
-#= require ./tty
-
-stream = new AnsiStream()
-TTY.appendFormatter (chunk) ->
- stream.process(chunk)
diff --git a/app/assets/javascripts/task/notifications.js.coffee.erb b/app/assets/javascripts/task/notifications.js.coffee.erb
deleted file mode 100644
index 05dbddd84..000000000
--- a/app/assets/javascripts/task/notifications.js.coffee.erb
+++ /dev/null
@@ -1,27 +0,0 @@
-class @Notifications
- IMAGES =
- success: '<%= image_path "deploy_success.jpg" %>'
- failed: '<%= image_path "deploy_failed.jpg" %>'
- error: '<%= image_path "deploy_error.jpg" %>'
-
- @init: (outputStream, task) ->
- outputStream.addEventListener('status', new this(task).statusUpdated)
-
- constructor: ({@repo, @description}) ->
-
- statusUpdated: (status) =>
- return unless status of IMAGES
- return unless $.notifyCheck() == $.NOTIFY_ALLOWED
- $.notify(IMAGES[status], @repo, @message(status))
-
- message: (status) ->
- deployShortSha = $('.short-sha').text()
- switch status
- when 'success'
- "Your #{@description} was successful!"
- when 'failed'
- "Your #{@description} failed."
- when 'error'
- "Error during #{@description}."
- else
- "Your #{@description} ended with status: #{status}"
diff --git a/app/assets/javascripts/task/notifications.js.erb b/app/assets/javascripts/task/notifications.js.erb
new file mode 100644
index 000000000..5d826278d
--- /dev/null
+++ b/app/assets/javascripts/task/notifications.js.erb
@@ -0,0 +1,48 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Notifications = (function() {
+ var IMAGES;
+
+ IMAGES = {
+ success: '<%= image_path "deploy_success.jpg" %>',
+ failed: '<%= image_path "deploy_failed.jpg" %>',
+ error: '<%= image_path "deploy_error.jpg" %>'
+ };
+
+ Notifications.init = function(outputStream, task) {
+ outputStream.addEventListener('status', new this(task).statusUpdated);
+ };
+
+ function Notifications(arg) {
+ this.repo = arg.repo, this.description = arg.description;
+ this.statusUpdated = bind(this.statusUpdated, this);
+ }
+
+ Notifications.prototype.statusUpdated = function(status) {
+ if (!(status in IMAGES)) {
+ return;
+ }
+ if ($.notifyCheck() !== $.NOTIFY_ALLOWED) {
+ return;
+ }
+ $.notify(IMAGES[status], this.repo, this.message(status));
+ };
+
+ Notifications.prototype.message = function(status) {
+ var deployShortSha;
+ deployShortSha = $('.short-sha').text();
+ switch (status) {
+ case 'success':
+ return "Your " + this.description + " was successful!";
+ case 'failed':
+ return "Your " + this.description + " failed.";
+ case 'error':
+ return "Error during " + this.description + ".";
+ default:
+ return "Your " + this.description + " ended with status: " + status;
+ }
+ };
+
+ return Notifications;
+
+})();
diff --git a/app/assets/javascripts/task/plugins.js b/app/assets/javascripts/task/plugins.js
new file mode 100644
index 000000000..666d1f266
--- /dev/null
+++ b/app/assets/javascripts/task/plugins.js
@@ -0,0 +1,13 @@
+this.Shipit || (this.Shipit = {});
+
+this.Shipit.Plugins = {
+ config: function(name) {
+ var config;
+ config = $("meta[name=\"" + name + "-config\"]").attr('content');
+ try {
+ return JSON.parse(config);
+ } catch (error) {
+ return null;
+ }
+ }
+};
diff --git a/app/assets/javascripts/task/plugins.js.coffee b/app/assets/javascripts/task/plugins.js.coffee
deleted file mode 100644
index 3bd573969..000000000
--- a/app/assets/javascripts/task/plugins.js.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-@Shipit ||= {}
-@Shipit.Plugins =
- config: (name) ->
- config = $("""meta[name="#{name}-config"]""").attr('content')
- try
- JSON.parse(config)
- catch
- null
diff --git a/app/assets/javascripts/task/search_bar.js b/app/assets/javascripts/task/search_bar.js
new file mode 100644
index 000000000..05aa3c161
--- /dev/null
+++ b/app/assets/javascripts/task/search_bar.js
@@ -0,0 +1,88 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.SearchBar = (function() {
+ var DEBOUNCE;
+
+ DEBOUNCE = 300;
+
+ function SearchBar($bar) {
+ this.$bar = $bar;
+ this.closeIfEmpty = bind(this.closeIfEmpty, this);
+ this.open = bind(this.open, this);
+ this.immediateBroadcastQueryChange = bind(this.immediateBroadcastQueryChange, this);
+ this.updateQuery = bind(this.updateQuery, this);
+ this.eventListeners = {};
+ this.query = window.location.hash.replace(/^#/, '');
+ this.$input = this.$bar.find('.search-input');
+ this.$input.on('blur', this.closeIfEmpty);
+ this.$input.on('input', this.updateQuery);
+ this.broadcastQueryChange = _.debounce(this.immediateBroadcastQueryChange, DEBOUNCE);
+ Mousetrap.bindGlobal(['command+f', 'ctrl+f'], this.open);
+ if (this.query) {
+ this.open();
+ this.setQuery(this.query);
+ }
+ }
+
+ SearchBar.prototype.addEventListener = function(type, handler) {
+ this.listeners(type).push(handler);
+ };
+
+ SearchBar.prototype.listeners = function(type) {
+ var base;
+ return (base = this.eventListeners)[type] || (base[type] = []);
+ };
+
+ SearchBar.prototype.setQuery = function(query) {
+ this.$input.val(query);
+ this.updateQuery();
+ };
+
+ SearchBar.prototype.updateQuery = function() {
+ var oldQuery;
+ oldQuery = this.query;
+ this.query = this.$input.val();
+ if (this.query !== oldQuery) {
+ this.broadcastQueryChange();
+ }
+ };
+
+ SearchBar.prototype.immediateBroadcastQueryChange = function() {
+ var handler, i, len, ref;
+ this.updateHash();
+ ref = this.listeners('query');
+ for (i = 0, len = ref.length; i < len; i++) {
+ handler = ref[i];
+ handler(this.query);
+ }
+ };
+
+ SearchBar.prototype.updateHash = function() {
+ window.location.hash = "#" + this.query;
+ };
+
+ SearchBar.prototype.open = function(event) {
+ if (event != null) {
+ event.preventDefault();
+ }
+ this.$bar.removeClass('hidden');
+ this.focus();
+ };
+
+ SearchBar.prototype.focus = function() {
+ this.$input.focus()[0].select();
+ };
+
+ SearchBar.prototype.closeIfEmpty = function(event) {
+ if (!this.query.length) {
+ this.close();
+ }
+ };
+
+ SearchBar.prototype.close = function() {
+ this.$bar.addClass('hidden');
+ };
+
+ return SearchBar;
+
+})();
diff --git a/app/assets/javascripts/task/search_bar.js.coffee b/app/assets/javascripts/task/search_bar.js.coffee
deleted file mode 100644
index e74b12239..000000000
--- a/app/assets/javascripts/task/search_bar.js.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-class @SearchBar
- DEBOUNCE = 300
-
- constructor: (@$bar) ->
- @eventListeners = {}
- @query = window.location.hash.replace(/^#/, '')
- @$input = @$bar.find('.search-input')
- @$input.on('blur', @closeIfEmpty)
- @$input.on('input', @updateQuery)
- @broadcastQueryChange = _.debounce(@immediateBroadcastQueryChange, DEBOUNCE)
- Mousetrap.bindGlobal(['command+f', 'ctrl+f'], @open)
-
- if @query
- @open()
- @setQuery(@query)
-
- addEventListener: (type, handler) ->
- @listeners(type).push(handler)
-
- listeners: (type) ->
- @eventListeners[type] ||= []
-
- setQuery: (query) ->
- @$input.val(query)
- @updateQuery()
-
- updateQuery: =>
- oldQuery = @query
- @query = @$input.val()
- @broadcastQueryChange() unless @query == oldQuery
-
- immediateBroadcastQueryChange: =>
- @updateHash()
- for handler in @listeners('query')
- handler(@query)
-
- updateHash: ->
- window.location.hash = "##{@query}"
-
- open: (event) =>
- event?.preventDefault()
- @$bar.removeClass('hidden')
- @focus()
-
- focus: ->
- @$input.focus()[0].select()
-
- closeIfEmpty: (event) =>
- @close() unless @query.length
-
- close: ->
- @$bar.addClass('hidden')
diff --git a/app/assets/javascripts/task/sidebar.js b/app/assets/javascripts/task/sidebar.js
new file mode 100644
index 000000000..653e14e4f
--- /dev/null
+++ b/app/assets/javascripts/task/sidebar.js
@@ -0,0 +1,26 @@
+this.Sidebar = (function() {
+ var INSTANCE;
+
+ INSTANCE = null;
+
+ Sidebar.instance = function() {
+ return INSTANCE || (INSTANCE = new this($('.sidebar'), $('.sidebar-plugins')));
+ };
+
+ Sidebar.newWidgetContainer = function() {
+ return Sidebar.instance().newWidgetContainer();
+ };
+
+ function Sidebar($sidebar, $container) {
+ this.$sidebar = $sidebar;
+ this.$container = $container;
+ }
+
+ Sidebar.prototype.newWidgetContainer = function() {
+ this.$sidebar.addClass('enabled');
+ return $(document.createElement('div')).addClass('sidebar-plugin').prependTo(this.$container);
+ };
+
+ return Sidebar;
+
+})();
diff --git a/app/assets/javascripts/task/sidebar.js.coffee b/app/assets/javascripts/task/sidebar.js.coffee
deleted file mode 100644
index 7c2a3383a..000000000
--- a/app/assets/javascripts/task/sidebar.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-class @Sidebar
- INSTANCE = null
-
- @instance: ->
- INSTANCE ||= new this($('.sidebar'), $('.sidebar-plugins'))
-
- @newWidgetContainer: ->
- Sidebar.instance().newWidgetContainer()
-
- constructor: (@$sidebar, @$container) ->
-
- newWidgetContainer: ->
- @$sidebar.addClass('enabled')
- $(document.createElement('div')).addClass('sidebar-plugin').prependTo(@$container)
diff --git a/app/assets/javascripts/task/stream.js b/app/assets/javascripts/task/stream.js
new file mode 100644
index 000000000..ae3c2e215
--- /dev/null
+++ b/app/assets/javascripts/task/stream.js
@@ -0,0 +1,146 @@
+var Chunk,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ slice = [].slice;
+
+Chunk = (function() {
+ function Chunk(raw) {
+ this.raw = raw;
+ }
+
+ Chunk.prototype.rawText = function() {
+ return this.raw;
+ };
+
+ Chunk.prototype.text = function() {
+ return this._text || (this._text = AnsiStream.strip(this.raw));
+ };
+
+ Chunk.prototype.rawLines = function() {
+ return this._rawLines || (this._rawLines = this.splitLines(this.raw));
+ };
+
+ Chunk.prototype.lines = function() {
+ return this._lines || (this._lines = this.splitLines(this.text()));
+ };
+
+ Chunk.prototype.splitLines = function(text) {
+ var lines;
+ lines = text.split(/\r?\n/);
+ if (!lines[lines.length - 1]) {
+ lines.pop();
+ }
+ return lines;
+ };
+
+ return Chunk;
+
+})();
+
+this.Stream = (function() {
+ var INTERVAL, MAX_RETRIES;
+
+ INTERVAL = 1000;
+
+ MAX_RETRIES = 15;
+
+ function Stream() {
+ this.error = bind(this.error, this);
+ this.success = bind(this.success, this);
+ this.poll = bind(this.poll, this);
+ this.url = null;
+ this.eventListeners = {};
+ this.retries = 0;
+ this.status = 'running';
+ }
+
+ Stream.prototype.init = function(arg) {
+ var status, text, url;
+ url = arg.url, text = arg.text, status = arg.status;
+ this.status = status;
+ this.broadcastOutput(text);
+ this.start(url);
+ };
+
+ Stream.prototype.poll = function() {
+ jQuery.ajax(this.url, {
+ success: this.success,
+ error: this.error
+ });
+ };
+
+ Stream.prototype.success = function(response) {
+ this.retries = 0;
+ this.broadcastOutput(response.output, response);
+ this.broadcastStatus(response.status, response);
+ this.start(response.url || false);
+ };
+
+ Stream.prototype.broadcastStatus = function() {
+ var args, error, handler, i, len, ref, status;
+ status = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+ if (status !== this.status) {
+ this.status = status;
+ ref = this.listeners('status');
+ for (i = 0, len = ref.length; i < len; i++) {
+ handler = ref[i];
+ try {
+ handler.apply(null, [status].concat(slice.call(args)));
+ } catch (error1) {
+ error = error1;
+ if (typeof console !== "undefined" && console !== null) {
+ console.log("Plugin error: " + error);
+ }
+ }
+ }
+ }
+ };
+
+ Stream.prototype.broadcastOutput = function() {
+ var args, chunk, error, handler, i, len, raw, ref;
+ raw = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+ if (!raw) {
+ return;
+ }
+ chunk = new Chunk(raw);
+ ref = this.listeners('chunk');
+ for (i = 0, len = ref.length; i < len; i++) {
+ handler = ref[i];
+ try {
+ handler.apply(null, [chunk].concat(slice.call(args)));
+ } catch (error1) {
+ error = error1;
+ if (typeof console !== "undefined" && console !== null) {
+ console.log("Plugin error: " + error);
+ }
+ }
+ }
+ };
+
+ Stream.prototype.error = function(response) {
+ var ref;
+ if ((600 > (ref = response.status) && ref >= 500) && (this.retries += 1) < MAX_RETRIES) {
+ this.start();
+ }
+ };
+
+ Stream.prototype.start = function(url) {
+ if (url == null) {
+ url = this.url;
+ }
+ if (this.url = url) {
+ setTimeout(this.poll, INTERVAL);
+ }
+ };
+
+ Stream.prototype.addEventListener = function(type, handler) {
+ this.listeners(type).push(handler);
+ };
+
+ Stream.prototype.listeners = function(type) {
+ var base;
+ return (base = this.eventListeners)[type] || (base[type] = []);
+ };
+
+ return Stream;
+
+})();
diff --git a/app/assets/javascripts/task/stream.js.coffee b/app/assets/javascripts/task/stream.js.coffee
deleted file mode 100644
index 7b48d1109..000000000
--- a/app/assets/javascripts/task/stream.js.coffee
+++ /dev/null
@@ -1,77 +0,0 @@
-class Chunk
- constructor: (@raw) ->
-
- rawText: ->
- @raw
-
- text: ->
- @_text ||= AnsiStream.strip(@raw)
-
- rawLines: ->
- @_rawLines ||= @splitLines(@raw)
-
- lines: ->
- @_lines ||= @splitLines(@text())
-
- splitLines: (text) ->
- lines = text.split(/\r?\n/)
- lines.pop() unless lines[lines.length - 1]
- lines
-
-class @Stream
- INTERVAL = 1000
- MAX_RETRIES = 15
-
- constructor: ->
- @url = null
- @eventListeners = {}
- @retries = 0
- @status = 'running'
-
- init: ({url, text, status}) ->
- @status = status
- @broadcastOutput(text)
- @start(url)
-
- poll: =>
- jQuery.ajax @url,
- success: @success
- error: @error
-
- success: (response) =>
- @retries = 0
- @broadcastOutput(response.output, response)
- @broadcastStatus(response.status, response)
- @start(response.url || false)
-
- broadcastStatus: (status, args...) ->
- if status != @status
- @status = status
- for handler in @listeners('status')
- try
- handler(status, args...)
- catch error
- console?.log("Plugin error: #{error}")
-
- broadcastOutput: (raw, args...) ->
- return unless raw
-
- chunk = new Chunk(raw)
- for handler in @listeners('chunk')
- try
- handler(chunk, args...)
- catch error
- console?.log("Plugin error: #{error}")
-
- error: (response) =>
- @start() if 600 > response.status >= 500 && (@retries += 1) < MAX_RETRIES
-
- start: (url = @url) ->
- if @url = url
- setTimeout(@poll, INTERVAL)
-
- addEventListener: (type, handler) ->
- @listeners(type).push(handler)
-
- listeners: (type) ->
- @eventListeners[type] ||= []
diff --git a/app/assets/javascripts/task/tty.js b/app/assets/javascripts/task/tty.js
new file mode 100644
index 000000000..52871bf59
--- /dev/null
+++ b/app/assets/javascripts/task/tty.js
@@ -0,0 +1,212 @@
+var OutputLines,
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+OutputLines = (function() {
+ function OutputLines(screen, render) {
+ this.screen = screen;
+ this.render = render;
+ this.renderingCache = {};
+ }
+
+ OutputLines.prototype.append = function(lines) {
+ this.screen.append(this.renderLines(this.filter(lines)));
+ };
+
+ OutputLines.prototype.setFilter = function() {
+ return true;
+ };
+
+ OutputLines.prototype.filter = function(lines) {
+ return lines;
+ };
+
+ OutputLines.prototype.highlight = function(line) {
+ return line;
+ };
+
+ OutputLines.prototype.renderLines = function(lines) {
+ var base, i, len, line, results;
+ results = [];
+ for (i = 0, len = lines.length; i < len; i++) {
+ line = lines[i];
+ results.push(this.highlight((base = this.renderingCache)[line] || (base[line] = this.render(line))));
+ }
+ return results;
+ };
+
+ return OutputLines;
+
+})();
+
+this.ClusterizeOutputLines = (function(superClass) {
+ extend(ClusterizeOutputLines, superClass);
+
+ function ClusterizeOutputLines(screen, render) {
+ this.screen = screen;
+ this.render = render;
+ ClusterizeOutputLines.__super__.constructor.apply(this, arguments);
+ this.raw = [];
+ this.query = '';
+ this.highlightRegexp = null;
+ this.stripCache = {};
+ }
+
+ ClusterizeOutputLines.prototype.append = function(lines) {
+ this.raw = this.raw.concat(lines);
+ ClusterizeOutputLines.__super__.append.apply(this, arguments);
+ };
+
+ ClusterizeOutputLines.prototype.setFilter = function(query) {
+ if (this.query = query) {
+ this.screen.options.no_data_text = 'No matches';
+ } else {
+ this.screen.options.no_data_text = 'Loading...';
+ }
+ this.highlightRegexp = this.buildHighlightRegexp(this.query);
+ this.reset();
+ };
+
+ ClusterizeOutputLines.prototype.reset = function() {
+ this.screen.update(this.renderLines(this.filter(this.raw)));
+ };
+
+ ClusterizeOutputLines.prototype.strip = function(line) {
+ var base;
+ return (base = this.stripCache)[line] || (base[line] = AnsiStream.strip(line));
+ };
+
+ ClusterizeOutputLines.prototype.filter = function(lines) {
+ var i, len, line, results;
+ if (!this.query) {
+ return lines;
+ }
+ results = [];
+ for (i = 0, len = lines.length; i < len; i++) {
+ line = lines[i];
+ if (this.strip(line).includes(this.query)) {
+ results.push(line);
+ }
+ }
+ return results;
+ };
+
+ ClusterizeOutputLines.prototype.buildHighlightRegexp = function(query) {
+ var pattern;
+ pattern = query.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/(\s+)/g, '(<[^>]+>)*$1(<[^>]+>)*');
+ return new RegExp("(" + pattern + ")", 'g');
+ };
+
+ ClusterizeOutputLines.prototype.highlight = function(renderedLine) {
+ if (!this.query) {
+ return renderedLine;
+ }
+ return renderedLine.replace(this.highlightRegexp, '$1').replace(/([^<>]*)((<[^>]+>)+)([^<>]*<\/mark>)/, '$1$2$4');
+ };
+
+ return ClusterizeOutputLines;
+
+})(OutputLines);
+
+this.TTY = (function() {
+ var FORMATTERS, STICKY_SCROLL_TOLERENCE;
+
+ FORMATTERS = [];
+
+ STICKY_SCROLL_TOLERENCE = 200;
+
+ TTY.appendFormatter = function(formatter) {
+ FORMATTERS.push(formatter);
+ };
+
+ TTY.prependFormatter = function(formatter) {
+ FORMATTERS.unshift(formatter);
+ };
+
+ function TTY($body) {
+ this.appendChunk = bind(this.appendChunk, this);
+ this.filterOutput = bind(this.filterOutput, this);
+ var scroller;
+ this.outputLines = [];
+ this.$code = $body.find('code');
+ this.$container = this.$code.closest('.task-output-container');
+ if (this.$container.hasClass('clusterize-scroll')) {
+ scroller = new Clusterize({
+ no_data_text: 'Loading...',
+ tag: 'div',
+ contentElem: this.$code[0],
+ scrollElem: this.$container[0]
+ });
+ this.output = new ClusterizeOutputLines(scroller, (function(_this) {
+ return function(line) {
+ return _this.createLine(_this.formatChunks(line));
+ };
+ })(this));
+ } else {
+ this.output = new OutputLines(this.$code, (function(_this) {
+ return function(line) {
+ return _this.createLine(_this.formatChunks(line));
+ };
+ })(this));
+ }
+ }
+
+ TTY.prototype.filterOutput = function(query) {
+ this.output.setFilter(query);
+ };
+
+ TTY.prototype.formatChunks = function(chunk) {
+ var formatter, i, len;
+ for (i = 0, len = FORMATTERS.length; i < len; i++) {
+ formatter = FORMATTERS[i];
+ chunk = formatter(chunk) || chunk;
+ }
+ return chunk;
+ };
+
+ TTY.prototype.appendChunk = function(chunk) {
+ var lines;
+ lines = chunk.rawLines();
+ if (!lines.length) {
+ return;
+ }
+ this.preserveScroll((function(_this) {
+ return function() {
+ _this.output.append(lines);
+ };
+ })(this));
+ };
+
+ TTY.prototype.createLine = function(fragment) {
+ var div;
+ div = document.createElement('div');
+ div.appendChild(fragment);
+ div.className = 'output-line';
+ return div.outerHTML;
+ };
+
+ TTY.prototype.isScrolledToBottom = function() {
+ return (this.getMaxScroll() - this.$container.scrollTop()) < 1;
+ };
+
+ TTY.prototype.scrollToBottom = function() {
+ this.$container.scrollTop(this.getMaxScroll());
+ };
+
+ TTY.prototype.getMaxScroll = function() {
+ return this.$code.parent().outerHeight(true) - this.$container.outerHeight(true);
+ };
+
+ TTY.prototype.preserveScroll = function(callback) {
+ var wasScrolledToBottom;
+ wasScrolledToBottom = this.isScrolledToBottom();
+ callback();
+ if (wasScrolledToBottom) {
+ this.scrollToBottom();
+ }
+ };
+
+ return TTY;
+
+})();
diff --git a/app/assets/javascripts/task/tty.js.coffee b/app/assets/javascripts/task/tty.js.coffee
deleted file mode 100644
index c8fb55e3f..000000000
--- a/app/assets/javascripts/task/tty.js.coffee
+++ /dev/null
@@ -1,119 +0,0 @@
-class OutputLines
- constructor: (@screen, @render) ->
- @renderingCache = {}
-
- append: (lines) ->
- @screen.append(@renderLines(@filter(lines)))
-
- setFilter: ->
- true
-
- filter: (lines) ->
- lines
-
- highlight: (line) ->
- line
-
- renderLines: (lines) ->
- for line in lines
- @highlight(@renderingCache[line] ||= @render(line))
-
-class @ClusterizeOutputLines extends OutputLines
- constructor: (@screen, @render) ->
- super
- @raw = []
- @query = ''
- @highlightRegexp = null
- @stripCache = {}
-
- append: (lines) ->
- @raw = @raw.concat(lines)
- super
-
- setFilter: (query) ->
- if @query = query
- @screen.options.no_data_text = 'No matches'
- else
- @screen.options.no_data_text = 'Loading...'
- @highlightRegexp = @buildHighlightRegexp(@query)
- @reset()
-
- reset: ->
- @screen.update(@renderLines(@filter(@raw)))
-
- strip: (line) ->
- @stripCache[line] ||= AnsiStream.strip(line)
-
- filter: (lines) ->
- return lines unless @query
- line for line in lines when @strip(line).includes(@query)
-
- buildHighlightRegexp: (query) ->
- pattern = query.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/(\s+)/g, '(<[^>]+>)*$1(<[^>]+>)*')
- new RegExp("(#{pattern})", 'g')
-
- highlight: (renderedLine) ->
- return renderedLine unless @query
-
- renderedLine.replace(@highlightRegexp, '$1').replace(/([^<>]*)((<[^>]+>)+)([^<>]*<\/mark>)/, '$1$2$4');
-
-class @TTY
- FORMATTERS = []
- STICKY_SCROLL_TOLERENCE = 200
-
- @appendFormatter: (formatter) ->
- FORMATTERS.push(formatter)
-
- @prependFormatter: (formatter) ->
- FORMATTERS.unshift(formatter)
-
- constructor: ($body) ->
- @outputLines = []
- @$code = $body.find('code')
- @$container = @$code.closest('.task-output-container')
- if @$container.hasClass('clusterize-scroll')
- scroller = new Clusterize(
- no_data_text: 'Loading...'
- tag: 'div'
- contentElem: @$code[0]
- scrollElem: @$container[0]
- )
- @output = new ClusterizeOutputLines(scroller, (line) => @createLine(@formatChunks(line)))
- else
- @output = new OutputLines(@$code, (line) => @createLine(@formatChunks(line)))
-
- filterOutput: (query) =>
- @output.setFilter(query)
-
- formatChunks: (chunk) ->
- for formatter in FORMATTERS
- chunk = formatter(chunk) || chunk
- chunk
-
- appendChunk: (chunk) =>
- lines = chunk.rawLines()
- return unless lines.length
-
- @preserveScroll =>
- @output.append(lines)
-
- createLine: (fragment) ->
- div = document.createElement('div')
- div.appendChild(fragment)
- div.className = 'output-line'
- div.outerHTML
-
- isScrolledToBottom: ->
- (@getMaxScroll() - @$container.scrollTop()) < 1
-
- scrollToBottom: ->
- @$container.scrollTop(@getMaxScroll())
-
- getMaxScroll: ->
- @$code.parent().outerHeight(true) - @$container.outerHeight(true)
-
- preserveScroll: (callback) ->
- wasScrolledToBottom = @isScrolledToBottom()
- callback()
- @scrollToBottom() if wasScrolledToBottom
-
diff --git a/lib/shipit.rb b/lib/shipit.rb
index 1b6f738f2..04fe3660f 100644
--- a/lib/shipit.rb
+++ b/lib/shipit.rb
@@ -8,7 +8,6 @@
require 'explicit-parameters'
require 'paquito'
-require 'coffee-rails'
require 'jquery-rails'
require 'rails-timeago'
require 'lodash-rails'
diff --git a/shipit-engine.gemspec b/shipit-engine.gemspec
index edee8ec2a..a955c5b13 100644
--- a/shipit-engine.gemspec
+++ b/shipit-engine.gemspec
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
s.add_dependency('active_model_serializers', '~> 0.9.3')
s.add_dependency('ansi_stream', '~> 0.0.6')
s.add_dependency('autoprefixer-rails', '~> 6.4.1')
- s.add_dependency('coffee-rails', '~> 5.0')
s.add_dependency('explicit-parameters', '~> 0.4.0')
s.add_dependency('faraday', '~> 1.3')
s.add_dependency('faraday-http-cache', '~> 2.2')
diff --git a/vendor/assets/javascripts/ansi_stream.js b/vendor/assets/javascripts/ansi_stream.js
new file mode 100644
index 000000000..47381f1b9
--- /dev/null
+++ b/vendor/assets/javascripts/ansi_stream.js
@@ -0,0 +1,161 @@
+// Compiled from ansi_stream gem (v0.0.6) ansi_stream.coffee using CoffeeScript 1.12.7
+//
+// Copyright (c) 2014 Guillaume Malette
+//
+// MIT License
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+var AnsiSpan, AnsiStyle;
+
+this.AnsiStream = (function() {
+ var ANSI_CODE;
+
+ ANSI_CODE = /((\u001b\[)|\u009b)(\d{0,3}(;\d{0,3})*[A-M|f-m])|\u001b[A-M]/g;
+
+ AnsiStream.strip = function(text) {
+ return text.replace(ANSI_CODE, '');
+ };
+
+ function AnsiStream() {
+ this.style = new AnsiStyle();
+ this.span = new AnsiSpan();
+ }
+
+ AnsiStream.prototype.process = function(text) {
+ var first_part, i, len, part, partText, parts, ref, span, spans, styles;
+ parts = text.split(/\033\[/);
+ spans = document.createDocumentFragment();
+ first_part = parts.shift();
+ if (first_part) {
+ spans.appendChild(this.span.create(first_part, this.style));
+ }
+ for (i = 0, len = parts.length; i < len; i++) {
+ part = parts[i];
+ ref = this._extractTextAndStyles(part), partText = ref[0], styles = ref[1];
+ this.style.apply(styles);
+ span = this.span.create(partText, this.style);
+ spans.appendChild(span);
+ }
+ return spans;
+ };
+
+ AnsiStream.prototype._extractTextAndStyles = function(originalText) {
+ var matches, numbers, ref, text;
+ matches = originalText.match(/^([\d;]*)m([^]*)$/);
+ if (!matches) {
+ return [originalText, null];
+ }
+ ref = matches, matches = ref[0], numbers = ref[1], text = ref[2];
+ return [text, numbers.split(";")];
+ };
+
+ return AnsiStream;
+
+})();
+
+AnsiStyle = (function() {
+ var COLORS;
+
+ COLORS = {
+ 0: 'black',
+ 1: 'red',
+ 2: 'green',
+ 3: 'yellow',
+ 4: 'blue',
+ 5: 'magenta',
+ 6: 'cyan',
+ 7: 'white',
+ 8: null,
+ 9: 'default'
+ };
+
+ function AnsiStyle() {
+ this.reset();
+ }
+
+ AnsiStyle.prototype.apply = function(newStyles) {
+ var i, len, style;
+ if (!newStyles) {
+ return;
+ }
+ for (i = 0, len = newStyles.length; i < len; i++) {
+ style = newStyles[i];
+ style = parseInt(style);
+ if (style === 0) {
+ this.reset();
+ } else if (style === 1) {
+ this.bright = true;
+ } else if ((30 <= style && style <= 39) && style !== 38) {
+ this._applyStyle('foreground', style);
+ } else if ((40 <= style && style <= 49) && style !== 48) {
+ this._applyStyle('background', style);
+ } else if (style === 4) {
+ this.underline = true;
+ } else if (style === 24) {
+ this.underline = false;
+ }
+ }
+ };
+
+ AnsiStyle.prototype.reset = function() {
+ this.background = this.foreground = 'default';
+ this.underline = this.bright = false;
+ };
+
+ AnsiStyle.prototype.toClass = function() {
+ var classes;
+ classes = [];
+ if (this.background) {
+ classes.push("ansi-background-" + this.background);
+ }
+ if (this.foreground) {
+ classes.push("ansi-foreground-" + this.foreground);
+ }
+ if (this.bright) {
+ classes.push("ansi-bright");
+ }
+ if (this.underline) {
+ classes.push("ansi-underline");
+ }
+ return classes.join(" ");
+ };
+
+ AnsiStyle.prototype._applyStyle = function(layer, number) {
+ this[layer] = COLORS[number % 10];
+ };
+
+ return AnsiStyle;
+
+})();
+
+AnsiSpan = (function() {
+ function AnsiSpan() {}
+
+ AnsiSpan.prototype.create = function(text, style) {
+ var span;
+ span = document.createElement('span');
+ span.textContent = text;
+ span.className = style.toClass();
+ return span;
+ };
+
+ return AnsiSpan;
+
+})();