Kanboard-Prod/assets/js/app.min.js

2682 lines
75 KiB
JavaScript

'use strict';
var Kanboard = {};
Kanboard.Accordion = function(app) {
this.app = app;
};
Kanboard.Accordion.prototype.listen = function() {
$(document).on("click", ".accordion-toggle", function(e) {
var section = $(this).parents(".accordion-section");
e.preventDefault();
if (section.hasClass("accordion-collapsed")) {
section.find(".accordion-content").show();
section.removeClass("accordion-collapsed");
} else {
section.find(".accordion-content").hide();
section.addClass("accordion-collapsed");
}
});
};
Kanboard.App = function() {
this.controllers = {};
};
Kanboard.App.prototype.get = function(controller) {
return this.controllers[controller];
};
Kanboard.App.prototype.execute = function() {
for (var className in Kanboard) {
if (className !== "App") {
var controller = new Kanboard[className](this);
this.controllers[className] = controller;
if (typeof controller.execute === "function") {
controller.execute();
}
if (typeof controller.listen === "function") {
controller.listen();
}
if (typeof controller.focus === "function") {
controller.focus();
}
if (typeof controller.keyboardShortcuts === "function") {
controller.keyboardShortcuts();
}
}
}
this.focus();
this.chosen();
this.keyboardShortcuts();
this.datePicker();
this.autoComplete();
};
Kanboard.App.prototype.keyboardShortcuts = function() {
var self = this;
// Submit form
Mousetrap.bindGlobal("mod+enter", function() {
var forms = $("form");
if (forms.length == 1) {
forms.submit();
} else if (forms.length > 1) {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
$(document.activeElement).parents("form").submit();
} else if (self.get("Popover").isOpen()) {
$("#popover-container form").submit();
}
}
});
// Open board selector
Mousetrap.bind("b", function(e) {
e.preventDefault();
$('#board-selector').trigger('chosen:open');
});
// Close popover and dropdown
Mousetrap.bindGlobal("esc", function() {
self.get("Popover").close();
self.get("Dropdown").close();
});
// Show keyboard shortcut
Mousetrap.bind("?", function() {
self.get("Popover").open($("body").data("keyboard-shortcut-url"));
});
};
Kanboard.App.prototype.focus = function() {
// Auto-select input fields
$(document).on('focus', '.auto-select', function() {
$(this).select();
});
// Workaround for chrome
$(document).on('mouseup', '.auto-select', function(e) {
e.preventDefault();
});
};
Kanboard.App.prototype.chosen = function() {
$(".chosen-select").each(function() {
var searchThreshold = $(this).data("search-threshold");
if (searchThreshold === undefined) {
searchThreshold = 10;
}
$(this).chosen({
width: "180px",
no_results_text: $(this).data("notfound"),
disable_search_threshold: searchThreshold
});
});
$(".select-auto-redirect").change(function() {
var regex = new RegExp($(this).data('redirect-regex'), 'g');
window.location = $(this).data('redirect-url').replace(regex, $(this).val());
});
};
Kanboard.App.prototype.datePicker = function() {
// Datepicker translation
$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);
// Datepicker
$(".form-date").datepicker({
showOtherMonths: true,
selectOtherMonths: true,
dateFormat: 'yy-mm-dd',
constrainInput: false
});
// Datetime picker
$(".form-datetime").datetimepicker({
controlType: 'select',
oneLine: true,
dateFormat: 'yy-mm-dd',
// timeFormat: 'h:mm tt',
constrainInput: false
});
};
Kanboard.App.prototype.autoComplete = function() {
$(".autocomplete").each(function() {
var input = $(this);
var field = input.data("dst-field");
var extraField = input.data("dst-extra-field");
if ($('#form-' + field).val() == '') {
input.parent().find("button[type=submit]").attr('disabled','disabled');
}
input.autocomplete({
source: input.data("search-url"),
minLength: 1,
select: function(event, ui) {
$("input[name=" + field + "]").val(ui.item.id);
if (extraField) {
$("input[name=" + extraField + "]").val(ui.item[extraField]);
}
input.parent().find("button[type=submit]").removeAttr('disabled');
}
});
});
};
Kanboard.App.prototype.hasId = function(id) {
return !!document.getElementById(id);
};
Kanboard.App.prototype.showLoadingIcon = function() {
$("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>');
};
Kanboard.App.prototype.hideLoadingIcon = function() {
$("#app-loading-icon").remove();
};
Kanboard.App.prototype.formatDuration = function(d) {
if (d >= 86400) {
return Math.round(d/86400) + "d";
}
else if (d >= 3600) {
return Math.round(d/3600) + "h";
}
else if (d >= 60) {
return Math.round(d/60) + "m";
}
return d + "s";
};
Kanboard.App.prototype.isVisible = function() {
var property = "";
if (typeof document.hidden !== "undefined") {
property = "visibilityState";
} else if (typeof document.mozHidden !== "undefined") {
property = "mozVisibilityState";
} else if (typeof document.msHidden !== "undefined") {
property = "msVisibilityState";
} else if (typeof document.webkitHidden !== "undefined") {
property = "webkitVisibilityState";
}
if (property != "") {
return document[property] == "visible";
}
return true;
};
Kanboard.AvgTimeColumnChart = function(app) {
this.app = app;
};
Kanboard.AvgTimeColumnChart.prototype.execute = function() {
if (this.app.hasId("analytic-avg-time-column")) {
this.show();
}
};
Kanboard.AvgTimeColumnChart.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var plots = [chart.data("label")];
var categories = [];
for (var column_id in metrics) {
plots.push(metrics[column_id].average);
categories.push(metrics[column_id].title);
}
c3.generate({
data: {
columns: [plots],
type: 'bar'
},
bar: {
width: {
ratio: 0.5
}
},
axis: {
x: {
type: 'category',
categories: categories
},
y: {
tick: {
format: this.app.formatDuration
}
}
},
legend: {
show: false
}
});
};
Kanboard.BoardCollapsedMode = function(app) {
this.app = app;
};
Kanboard.BoardCollapsedMode.prototype.keyboardShortcuts = function() {
var self = this;
if (self.app.hasId("board")) {
Mousetrap.bind("s", function() {
self.toggle();
});
}
};
Kanboard.BoardCollapsedMode.prototype.toggle = function() {
var self = this;
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'),
success: function(data) {
$('.filter-display-mode').toggle();
self.app.get("BoardDragAndDrop").refresh(data);
}
});
};
Kanboard.BoardColumnScrolling = function(app) {
this.app = app;
};
Kanboard.BoardColumnScrolling.prototype.execute = function() {
if (this.app.hasId("board")) {
this.render();
$(window).on("load", this.render);
$(window).resize(this.render);
}
};
Kanboard.BoardColumnScrolling.prototype.listen = function() {
var self = this;
$(document).on('click', ".filter-toggle-height", function(e) {
e.preventDefault();
self.toggle();
});
};
Kanboard.BoardColumnScrolling.prototype.onBoardRendered = function() {
this.render();
};
Kanboard.BoardColumnScrolling.prototype.toggle = function() {
var scrolling = localStorage.getItem("column_scroll");
if (scrolling == undefined) {
scrolling = 1;
}
localStorage.setItem("column_scroll", scrolling == 0 ? 1 : 0);
this.render();
};
Kanboard.BoardColumnScrolling.prototype.render = function() {
var taskList = $(".board-task-list");
var rotationWrapper = $(".board-rotation-wrapper");
var filterMax = $(".filter-max-height");
var filterMin = $(".filter-min-height");
if (localStorage.getItem("column_scroll") == 0) {
var height = 80;
filterMax.show();
filterMin.hide();
rotationWrapper.css("min-height", '');
taskList.each(function() {
var columnHeight = $(this).height();
if (columnHeight > height) {
height = columnHeight;
}
});
taskList.css("min-height", height);
taskList.css("height", '');
}
else {
filterMax.hide();
filterMin.show();
if ($(".board-swimlane").length > 1) {
taskList.each(function() {
if ($(this).height() > 500) {
$(this).css("height", 500);
}
else {
$(this).css("min-height", 320); // Height of the dropdown menu
rotationWrapper.css("min-height", 320);
}
});
}
else {
var height = $(window).height() - 170;
taskList.css("height", height);
rotationWrapper.css("min-height", height);
}
}
};
Kanboard.BoardColumnView = function(app) {
this.app = app;
};
Kanboard.BoardColumnView.prototype.execute = function() {
if (this.app.hasId("board")) {
this.render();
}
};
Kanboard.BoardColumnView.prototype.listen = function() {
var self = this;
$(document).on("click", ".board-toggle-column-view", function() {
self.toggle($(this).data("column-id"));
});
};
Kanboard.BoardColumnView.prototype.onBoardRendered = function() {
this.render();
};
Kanboard.BoardColumnView.prototype.render = function() {
var self = this;
$(".board-column-header").each(function() {
var columnId = $(this).data('column-id');
if (localStorage.getItem("hidden_column_" + columnId)) {
self.hideColumn(columnId);
}
});
};
Kanboard.BoardColumnView.prototype.toggle = function(columnId) {
if (localStorage.getItem("hidden_column_" + columnId)) {
this.showColumn(columnId);
}
else {
this.hideColumn(columnId);
}
};
Kanboard.BoardColumnView.prototype.hideColumn = function(columnId) {
$(".board-column-" + columnId + " .board-column-expanded").hide();
$(".board-column-" + columnId + " .board-column-collapsed").show();
$(".board-column-header-" + columnId + " .board-column-expanded").hide();
$(".board-column-header-" + columnId + " .board-column-collapsed").show();
$(".board-column-header-" + columnId).each(function() {
$(this).removeClass("board-column-compact");
$(this).addClass("board-column-header-collapsed");
});
$(".board-column-" + columnId).each(function() {
$(this).addClass("board-column-task-collapsed");
});
$(".board-column-" + columnId + " .board-rotation").each(function() {
$(this).css("width", $(".board-column-" + columnId + "").height());
});
localStorage.setItem("hidden_column_" + columnId, 1);
};
Kanboard.BoardColumnView.prototype.showColumn = function(columnId) {
$(".board-column-" + columnId + " .board-column-expanded").show();
$(".board-column-" + columnId + " .board-column-collapsed").hide();
$(".board-column-header-" + columnId + " .board-column-expanded").show();
$(".board-column-header-" + columnId + " .board-column-collapsed").hide();
$(".board-column-header-" + columnId).removeClass("board-column-header-collapsed");
$(".board-column-" + columnId).removeClass("board-column-task-collapsed");
if (localStorage.getItem("horizontal_scroll") == 0) {
$(".board-column-header-" + columnId).addClass("board-column-compact");
}
localStorage.removeItem("hidden_column_" + columnId);
};
Kanboard.BoardDragAndDrop = function(app) {
this.app = app;
this.savingInProgress = false;
};
Kanboard.BoardDragAndDrop.prototype.execute = function() {
if (this.app.hasId("board")) {
this.dragAndDrop();
this.executeListeners();
}
};
Kanboard.BoardDragAndDrop.prototype.dragAndDrop = function() {
var self = this;
var params = {
forcePlaceholderSize: true,
tolerance: "pointer",
connectWith: ".board-task-list",
placeholder: "draggable-placeholder",
items: ".draggable-item",
stop: function(event, ui) {
var task = ui.item;
var taskId = task.attr('data-task-id');
var taskPosition = task.attr('data-position');
var taskColumnId = task.attr('data-column-id');
var taskSwimlaneId = task.attr('data-swimlane-id');
var newColumnId = task.parent().attr("data-column-id");
var newSwimlaneId = task.parent().attr('data-swimlane-id');
var newPosition = task.index() + 1;
task.removeClass("draggable-item-selected");
if (newColumnId != taskColumnId || newSwimlaneId != taskSwimlaneId || newPosition != taskPosition) {
self.changeTaskState(taskId);
self.save(taskId, newColumnId, newPosition, newSwimlaneId);
}
},
start: function(event, ui) {
ui.item.addClass("draggable-item-selected");
ui.placeholder.height(ui.item.height());
}
};
if ($.support.touch) {
$(".task-board-sort-handle").css("display", "inline");
params["handle"] = ".task-board-sort-handle";
}
$(".board-task-list").sortable(params);
};
Kanboard.BoardDragAndDrop.prototype.changeTaskState = function(taskId) {
var task = $("div[data-task-id=" + taskId + "]");
task.addClass('task-board-saving-state');
task.find('.task-board-saving-icon').show();
};
Kanboard.BoardDragAndDrop.prototype.save = function(taskId, columnId, position, swimlaneId) {
var self = this;
self.app.showLoadingIcon();
self.savingInProgress = true;
$.ajax({
cache: false,
url: $("#board").data("save-url"),
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"task_id": taskId,
"column_id": columnId,
"swimlane_id": swimlaneId,
"position": position
}),
success: function(data) {
self.refresh(data);
self.savingInProgress = false;
},
error: function() {
self.app.hideLoadingIcon();
self.savingInProgress = false;
}
});
};
Kanboard.BoardDragAndDrop.prototype.refresh = function(data) {
$("#board-container").replaceWith(data);
this.app.hideLoadingIcon();
this.dragAndDrop();
this.executeListeners();
};
Kanboard.BoardDragAndDrop.prototype.executeListeners = function() {
for (var className in this.app.controllers) {
var controller = this.app.get(className);
if (typeof controller.onBoardRendered === "function") {
controller.onBoardRendered();
}
}
};
Kanboard.BoardHorizontalScrolling = function(app) {
this.app = app;
};
Kanboard.BoardHorizontalScrolling.prototype.execute = function() {
if (this.app.hasId("board")) {
this.render();
}
};
Kanboard.BoardHorizontalScrolling.prototype.listen = function() {
var self = this;
$(document).on('click', ".filter-toggle-scrolling", function(e) {
e.preventDefault();
self.toggle();
});
};
Kanboard.BoardHorizontalScrolling.prototype.keyboardShortcuts = function() {
var self = this;
if (self.app.hasId("board")) {
Mousetrap.bind("c", function () {
self.toggle();
});
}
};
Kanboard.BoardHorizontalScrolling.prototype.onBoardRendered = function() {
this.render();
};
Kanboard.BoardHorizontalScrolling.prototype.toggle = function() {
var scrolling = localStorage.getItem("horizontal_scroll") || 1;
localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
this.render();
};
Kanboard.BoardHorizontalScrolling.prototype.render = function() {
if (localStorage.getItem("horizontal_scroll") == 0) {
$(".filter-wide").show();
$(".filter-compact").hide();
$("#board-container").addClass("board-container-compact");
$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact");
}
else {
$(".filter-wide").hide();
$(".filter-compact").show();
$("#board-container").removeClass("board-container-compact");
$("#board th").removeClass("board-column-compact");
}
};
Kanboard.BoardPolling = function(app) {
this.app = app;
};
Kanboard.BoardPolling.prototype.execute = function() {
if (this.app.hasId("board")) {
var interval = parseInt($("#board").attr("data-check-interval"));
if (interval > 0) {
window.setInterval(this.check.bind(this), interval * 1000);
}
}
};
Kanboard.BoardPolling.prototype.check = function() {
if (this.app.isVisible() && !this.app.get("BoardDragAndDrop").savingInProgress) {
var self = this;
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: $("#board").data("check-url"),
statusCode: {
200: function(data) {
self.app.get("BoardDragAndDrop").refresh(data);
},
304: function () {
self.app.hideLoadingIcon();
}
}
});
}
};
Kanboard.BoardTask = function(app) {
this.app = app;
};
Kanboard.BoardTask.prototype.listen = function() {
var self = this;
$(document).on("click", ".task-board-change-assignee", function(e) {
e.preventDefault();
e.stopPropagation();
self.app.get("Popover").open($(this).data('url'));
});
$(document).on("click", ".task-board", function(e) {
if (e.target.tagName != "A" && e.target.tagName != "IMG") {
window.location = $(this).data("task-url");
}
});
};
Kanboard.BoardTask.prototype.keyboardShortcuts = function() {
var self = this;
if (self.app.hasId("board")) {
Mousetrap.bind("n", function () {
self.app.get("Popover").open($("#board").data("task-creation-url"));
});
}
};
Kanboard.BurndownChart = function(app) {
this.app = app;
};
Kanboard.BurndownChart.prototype.execute = function() {
if (this.app.hasId("analytic-burndown")) {
this.show();
}
};
Kanboard.BurndownChart.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var columns = [[chart.data("label-total")]];
var categories = [];
var inputFormat = d3.time.format("%Y-%m-%d");
var outputFormat = d3.time.format(chart.data("date-format"));
for (var i = 0; i < metrics.length; i++) {
for (var j = 0; j < metrics[i].length; j++) {
if (i == 0) {
columns.push([metrics[i][j]]);
}
else {
columns[j + 1].push(metrics[i][j]);
if (j > 0) {
if (columns[0][i] == undefined) {
columns[0].push(0);
}
columns[0][i] += metrics[i][j];
}
if (j == 0) {
categories.push(outputFormat(inputFormat.parse(metrics[i][j])));
}
}
}
}
c3.generate({
data: {
columns: columns
},
axis: {
x: {
type: 'category',
categories: categories
}
}
});
};
Kanboard.Calendar = function(app) {
this.app = app;
};
Kanboard.Calendar.prototype.execute = function() {
var calendar = $('#calendar');
if (calendar.length == 1) {
this.show(calendar);
}
};
Kanboard.Calendar.prototype.show = function(calendar) {
calendar.fullCalendar({
lang: $("body").data("js-lang"),
editable: true,
eventLimit: true,
defaultView: "month",
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
eventDrop: function(event) {
$.ajax({
cache: false,
url: calendar.data("save-url"),
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"task_id": event.id,
"date_due": event.start.format()
})
});
},
viewRender: function() {
var url = calendar.data("check-url");
var params = {
"start": calendar.fullCalendar('getView').start.format(),
"end": calendar.fullCalendar('getView').end.format()
};
for (var key in params) {
url += "&" + key + "=" + params[key];
}
$.getJSON(url, function(events) {
calendar.fullCalendar('removeEvents');
calendar.fullCalendar('addEventSource', events);
calendar.fullCalendar('rerenderEvents');
});
}
});
};
Kanboard.Column = function(app) {
this.app = app;
};
Kanboard.Column.prototype.listen = function() {
this.dragAndDrop();
};
Kanboard.Column.prototype.dragAndDrop = function() {
var self = this;
$(".draggable-row-handle").mouseenter(function() {
$(this).parent().parent().addClass("draggable-item-hover");
}).mouseleave(function() {
$(this).parent().parent().removeClass("draggable-item-hover");
});
$(".columns-table tbody").sortable({
forcePlaceholderSize: true,
handle: "td:first i",
helper: function(e, ui) {
ui.children().each(function() {
$(this).width($(this).width());
});
return ui;
},
stop: function(event, ui) {
var column = ui.item;
column.removeClass("draggable-item-selected");
self.savePosition(column.data("column-id"), column.index() + 1);
},
start: function(event, ui) {
ui.item.addClass("draggable-item-selected");
}
}).disableSelection();
};
Kanboard.Column.prototype.savePosition = function(columnId, position) {
var url = $(".columns-table").data("save-position-url");
var self = this;
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: url,
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"column_id": columnId,
"position": position
}),
complete: function() {
self.app.hideLoadingIcon();
}
});
};
Kanboard.CompareHoursColumnChart = function(app) {
this.app = app;
};
Kanboard.CompareHoursColumnChart.prototype.execute = function() {
if (this.app.hasId("analytic-compare-hours")) {
this.show();
}
};
Kanboard.CompareHoursColumnChart.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var labelOpen = chart.data("label-open");
var labelClosed = chart.data("label-closed");
var spent = [chart.data("label-spent")];
var estimated = [chart.data("label-estimated")];
var categories = [];
for (var status in metrics) {
spent.push(parseFloat(metrics[status].time_spent));
estimated.push(parseFloat(metrics[status].time_estimated));
categories.push(status == 'open' ? labelOpen : labelClosed);
}
c3.generate({
data: {
columns: [spent, estimated],
type: 'bar'
},
bar: {
width: {
ratio: 0.2
}
},
axis: {
x: {
type: 'category',
categories: categories
}
},
legend: {
show: true
}
});
};
Kanboard.CumulativeFlowDiagram = function(app) {
this.app = app;
};
Kanboard.CumulativeFlowDiagram.prototype.execute = function() {
if (this.app.hasId("analytic-cfd")) {
this.show();
}
};
Kanboard.CumulativeFlowDiagram.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var columns = [];
var groups = [];
var categories = [];
var inputFormat = d3.time.format("%Y-%m-%d");
var outputFormat = d3.time.format(chart.data("date-format"));
for (var i = 0; i < metrics.length; i++) {
for (var j = 0; j < metrics[i].length; j++) {
if (i == 0) {
columns.push([metrics[i][j]]);
if (j > 0) {
groups.push(metrics[i][j]);
}
}
else {
columns[j].push(metrics[i][j]);
if (j == 0) {
categories.push(outputFormat(inputFormat.parse(metrics[i][j])));
}
}
}
}
c3.generate({
data: {
columns: columns,
type: 'area-spline',
groups: [groups]
},
axis: {
x: {
type: 'category',
categories: categories
}
}
});
};
Kanboard.Dropdown = function(app) {
this.app = app;
};
Kanboard.Dropdown.prototype.listen = function() {
var self = this;
$(document).on('click', function() {
self.close();
});
$(document).on('click', '.dropdown-menu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
self.close();
var submenu = $(this).next('ul');
var offset = $(this).offset();
// Clone the submenu outside of the column to avoid clipping issue with overflow
$("body").append(jQuery("<div>", {"id": "dropdown"}));
submenu.clone().appendTo("#dropdown");
var clone = $("#dropdown ul");
clone.addClass('dropdown-submenu-open');
var submenuHeight = clone.outerHeight();
var submenuWidth = clone.outerWidth();
if (offset.top + submenuHeight - $(window).scrollTop() < $(window).height() || $(window).scrollTop() + offset.top < submenuHeight) {
clone.css('top', offset.top + $(this).height());
}
else {
clone.css('top', offset.top - submenuHeight - 5);
}
if (offset.left + submenuWidth > $(window).width()) {
clone.css('left', offset.left - submenuWidth + $(this).outerWidth());
}
else {
clone.css('left', offset.left);
}
});
$(document).on('click', '.dropdown-submenu-open li', function(e) {
if ($(e.target).is('li')) {
$(this).find('a:visible')[0].click(); // Calling native click() not the jQuery one
}
});
};
Kanboard.Dropdown.prototype.close = function() {
$("#dropdown").remove();
};
Kanboard.Dropdown.prototype.onPopoverOpened = function() {
this.close();
};
Kanboard.FileUpload = function(app) {
this.app = app;
this.files = [];
this.currentFile = 0;
};
Kanboard.FileUpload.prototype.onPopoverOpened = function() {
var dropzone = document.getElementById("file-dropzone");
var self = this;
if (dropzone) {
dropzone.ondragover = dropzone.ondragenter = function(e) {
e.stopPropagation();
e.preventDefault();
};
dropzone.ondrop = function(e) {
e.stopPropagation();
e.preventDefault();
self.files = e.dataTransfer.files;
self.show();
$("#file-error-max-size").hide();
};
$(document).on("click", "#file-browser", function(e) {
e.preventDefault();
$("#file-form-element").get(0).click();
});
$(document).on("click", "#file-upload-button", function(e) {
e.preventDefault();
self.currentFile = 0;
self.checkFiles();
});
$("#file-form-element").change(function() {
self.files = document.getElementById("file-form-element").files;
self.show();
$("#file-error-max-size").hide();
});
}
};
Kanboard.FileUpload.prototype.show = function() {
$("#file-list").remove();
if (this.files.length > 0) {
$("#file-upload-button").prop("disabled", false);
$("#file-dropzone-inner").hide();
var ul = jQuery("<ul>", {"id": "file-list"});
for (var i = 0; i < this.files.length; i++) {
var percentage = jQuery("<span>", {"id": "file-percentage-" + i}).append("(0%)");
var progress = jQuery("<progress>", {"id": "file-progress-" + i, "value": 0});
var li = jQuery("<li>", {"id": "file-label-" + i})
.append(progress)
.append("&nbsp;")
.append(this.files[i].name)
.append("&nbsp;")
.append(percentage);
ul.append(li);
}
$("#file-dropzone").append(ul);
} else {
$("#file-dropzone-inner").show();
}
};
Kanboard.FileUpload.prototype.checkFiles = function() {
var max = parseInt($("#file-dropzone").data("max-size"));
for (var i = 0; i < this.files.length; i++) {
if (this.files[i].size > max) {
$("#file-error-max-size").show();
$("#file-label-" + i).addClass("file-error");
$("#file-upload-button").prop("disabled", true);
return;
}
}
this.uploadFiles();
};
Kanboard.FileUpload.prototype.uploadFiles = function() {
if (this.files.length > 0) {
this.uploadFile(this.files[this.currentFile]);
}
};
Kanboard.FileUpload.prototype.uploadFile = function(file) {
var dropzone = document.getElementById("file-dropzone");
var url = dropzone.dataset.url;
var xhr = new XMLHttpRequest();
var fd = new FormData();
xhr.upload.addEventListener("progress", this.updateProgress.bind(this));
xhr.upload.addEventListener("load", this.transferComplete.bind(this));
xhr.open("POST", url, true);
fd.append('files[]', file);
xhr.send(fd);
};
Kanboard.FileUpload.prototype.updateProgress = function(e) {
if (e.lengthComputable) {
$("#file-progress-" + this.currentFile).val(e.loaded / e.total);
$("#file-percentage-" + this.currentFile).text('(' + Math.floor((e.loaded / e.total) * 100) + '%)');
}
};
Kanboard.FileUpload.prototype.transferComplete = function() {
this.currentFile++;
if (this.currentFile < this.files.length) {
this.uploadFile(this.files[this.currentFile]);
} else {
var uploadButton = $("#file-upload-button");
uploadButton.prop("disabled", true);
uploadButton.parent().hide();
$("#file-done").show();
}
};
// Based on jQuery.ganttView v.0.8.8 Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com - MIT License
Kanboard.Gantt = function(app) {
this.app = app;
this.data = [];
this.options = {
container: "#gantt-chart",
showWeekends: true,
allowMoves: true,
allowResizes: true,
cellWidth: 21,
cellHeight: 31,
slideWidth: 1000,
vHeaderWidth: 200
};
};
Kanboard.Gantt.prototype.execute = function() {
if (this.app.hasId("gantt-chart")) {
this.show();
}
};
// Save record after a resize or move
Kanboard.Gantt.prototype.saveRecord = function(record) {
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: $(this.options.container).data("save-url"),
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify(record),
complete: this.app.hideLoadingIcon.bind(this)
});
};
// Build the Gantt chart
Kanboard.Gantt.prototype.show = function() {
this.data = this.prepareData($(this.options.container).data('records'));
var minDays = Math.floor((this.options.slideWidth / this.options.cellWidth) + 5);
var range = this.getDateRange(minDays);
var startDate = range[0];
var endDate = range[1];
var container = $(this.options.container);
var chart = jQuery("<div>", { "class": "ganttview" });
chart.append(this.renderVerticalHeader());
chart.append(this.renderSlider(startDate, endDate));
container.append(chart);
jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", container).addClass("last");
jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", container).addClass("last");
jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", container).addClass("last");
if (! $(this.options.container).data('readonly')) {
this.listenForBlockResize(startDate);
this.listenForBlockMove(startDate);
}
else {
this.options.allowResizes = false;
this.options.allowMoves = false;
}
};
// Render record list on the left
Kanboard.Gantt.prototype.renderVerticalHeader = function() {
var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" });
var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" });
var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" });
for (var i = 0; i < this.data.length; i++) {
var content = jQuery("<span>")
.append(jQuery("<i>", {"class": "fa fa-info-circle tooltip", "title": this.getVerticalHeaderTooltip(this.data[i])}))
.append("&nbsp;");
if (this.data[i].type == "task") {
content.append(jQuery("<a>", {"href": this.data[i].link, "title": this.data[i].title}).append(this.data[i].title));
}
else {
content
.append(jQuery("<a>", {"href": this.data[i].board_link, "target": "_blank", "title": $(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>'))
.append("&nbsp;")
.append(jQuery("<a>", {"href": this.data[i].gantt_link, "target": "_blank", "title": $(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>'))
.append("&nbsp;")
.append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank"}).append(this.data[i].title));
}
seriesDiv.append(jQuery("<div>", {"class": "ganttview-vtheader-series-name"}).append(content));
}
itemDiv.append(seriesDiv);
headerDiv.append(itemDiv);
return headerDiv;
};
// Render right part of the chart (top header + grid + bars)
Kanboard.Gantt.prototype.renderSlider = function(startDate, endDate) {
var slideDiv = jQuery("<div>", {"class": "ganttview-slide-container"});
var dates = this.getDates(startDate, endDate);
slideDiv.append(this.renderHorizontalHeader(dates));
slideDiv.append(this.renderGrid(dates));
slideDiv.append(this.addBlockContainers());
this.addBlocks(slideDiv, startDate);
return slideDiv;
};
// Render top header (days)
Kanboard.Gantt.prototype.renderHorizontalHeader = function(dates) {
var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" });
var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" });
var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" });
var totalW = 0;
for (var y in dates) {
for (var m in dates[y]) {
var w = dates[y][m].length * this.options.cellWidth;
totalW = totalW + w;
monthsDiv.append(jQuery("<div>", {
"class": "ganttview-hzheader-month",
"css": { "width": (w - 1) + "px" }
}).append($.datepicker.regional[$("body").data('js-lang')].monthNames[m] + " " + y));
for (var d in dates[y][m]) {
daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" }).append(dates[y][m][d].getDate()));
}
}
}
monthsDiv.css("width", totalW + "px");
daysDiv.css("width", totalW + "px");
headerDiv.append(monthsDiv).append(daysDiv);
return headerDiv;
};
// Render grid
Kanboard.Gantt.prototype.renderGrid = function(dates) {
var gridDiv = jQuery("<div>", { "class": "ganttview-grid" });
var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" });
for (var y in dates) {
for (var m in dates[y]) {
for (var d in dates[y][m]) {
var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" });
if (this.options.showWeekends && this.isWeekend(dates[y][m][d])) {
cellDiv.addClass("ganttview-weekend");
}
rowDiv.append(cellDiv);
}
}
}
var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * this.options.cellWidth;
rowDiv.css("width", w + "px");
gridDiv.css("width", w + "px");
for (var i = 0; i < this.data.length; i++) {
gridDiv.append(rowDiv.clone());
}
return gridDiv;
};
// Render bar containers
Kanboard.Gantt.prototype.addBlockContainers = function() {
var blocksDiv = jQuery("<div>", { "class": "ganttview-blocks" });
for (var i = 0; i < this.data.length; i++) {
blocksDiv.append(jQuery("<div>", { "class": "ganttview-block-container" }));
}
return blocksDiv;
};
// Render bars
Kanboard.Gantt.prototype.addBlocks = function(slider, start) {
var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", slider);
var rowIdx = 0;
for (var i = 0; i < this.data.length; i++) {
var series = this.data[i];
var size = this.daysBetween(series.start, series.end) + 1;
var offset = this.daysBetween(start, series.start);
var text = jQuery("<div>", {"class": "ganttview-block-text"});
var block = jQuery("<div>", {
"class": "ganttview-block tooltip" + (this.options.allowMoves ? " ganttview-block-movable" : ""),
"title": this.getBarTooltip(series),
"css": {
"width": ((size * this.options.cellWidth) - 9) + "px",
"margin-left": (offset * this.options.cellWidth) + "px"
}
}).append(text);
if (size >= 2) {
text.append(series.progress);
}
block.data("record", series);
this.setBarColor(block, series);
if (series.progress != "0%") {
block.append(jQuery("<div>", {
"css": {
"z-index": 0,
"position": "absolute",
"top": 0,
"bottom": 0,
"background-color": series.color.border,
"width": series.progress,
"opacity": 0.4
}
}));
}
jQuery(rows[rowIdx]).append(block);
rowIdx = rowIdx + 1;
}
};
// Get tooltip for vertical header
Kanboard.Gantt.prototype.getVerticalHeaderTooltip = function(record) {
var tooltip = "";
if (record.type == "task") {
tooltip = "<strong>" + record.column_title + "</strong> (" + record.progress + ")<br/>" + record.title;
}
else {
var types = ["project-manager", "project-member"];
for (var index in types) {
var type = types[index];
if (! jQuery.isEmptyObject(record.users[type])) {
var list = jQuery("<ul>");
for (var user_id in record.users[type]) {
if (user_id) {
list.append(jQuery("<li>").append(record.users[type][user_id]));
}
}
tooltip += "<p><strong>" + $(this.options.container).data("label-" + type) + "</strong></p>" + list[0].outerHTML;
}
}
}
return tooltip;
};
// Get tooltip for bars
Kanboard.Gantt.prototype.getBarTooltip = function(record) {
var tooltip = "";
if (record.not_defined) {
tooltip = $(this.options.container).data("label-not-defined");
}
else {
if (record.type == "task") {
tooltip = "<strong>" + record.progress + "</strong><br/>" +
$(this.options.container).data("label-assignee") + " " + (record.assignee ? record.assignee : '') + "<br/>";
}
tooltip += $(this.options.container).data("label-start-date") + " " + $.datepicker.formatDate('yy-mm-dd', record.start) + "<br/>";
tooltip += $(this.options.container).data("label-end-date") + " " + $.datepicker.formatDate('yy-mm-dd', record.end);
}
return tooltip;
};
// Set bar color
Kanboard.Gantt.prototype.setBarColor = function(block, record) {
if (record.not_defined) {
block.addClass("ganttview-block-not-defined");
}
else {
block.css("background-color", record.color.background);
block.css("border-color", record.color.border);
}
};
// Setup jquery-ui resizable
Kanboard.Gantt.prototype.listenForBlockResize = function(startDate) {
var self = this;
jQuery("div.ganttview-block", this.options.container).resizable({
grid: this.options.cellWidth,
handles: "e,w",
delay: 300,
stop: function() {
var block = jQuery(this);
self.updateDataAndPosition(block, startDate);
self.saveRecord(block.data("record"));
}
});
};
// Setup jquery-ui drag and drop
Kanboard.Gantt.prototype.listenForBlockMove = function(startDate) {
var self = this;
jQuery("div.ganttview-block", this.options.container).draggable({
axis: "x",
delay: 300,
grid: [this.options.cellWidth, this.options.cellWidth],
stop: function() {
var block = jQuery(this);
self.updateDataAndPosition(block, startDate);
self.saveRecord(block.data("record"));
}
});
};
// Update the record data and the position on the chart
Kanboard.Gantt.prototype.updateDataAndPosition = function(block, startDate) {
var container = jQuery("div.ganttview-slide-container", this.options.container);
var scroll = container.scrollLeft();
var offset = block.offset().left - container.offset().left - 1 + scroll;
var record = block.data("record");
// Restore color for defined block
record.not_defined = false;
this.setBarColor(block, record);
// Set new start date
var daysFromStart = Math.round(offset / this.options.cellWidth);
var newStart = this.addDays(this.cloneDate(startDate), daysFromStart);
record.start = newStart;
// Set new end date
var width = block.outerWidth();
var numberOfDays = Math.round(width / this.options.cellWidth) - 1;
record.end = this.addDays(this.cloneDate(newStart), numberOfDays);
if (record.type === "task" && numberOfDays > 0) {
jQuery("div.ganttview-block-text", block).text(record.progress);
}
// Update tooltip
block.attr("title", this.getBarTooltip(record));
block.data("record", record);
// Remove top and left properties to avoid incorrect block positioning,
// set position to relative to keep blocks relative to scrollbar when scrolling
block
.css("top", "")
.css("left", "")
.css("position", "relative")
.css("margin-left", offset + "px");
};
// Creates a 3 dimensional array [year][month][day] of every day
// between the given start and end dates
Kanboard.Gantt.prototype.getDates = function(start, end) {
var dates = [];
dates[start.getFullYear()] = [];
dates[start.getFullYear()][start.getMonth()] = [start];
var last = start;
while (this.compareDate(last, end) == -1) {
var next = this.addDays(this.cloneDate(last), 1);
if (! dates[next.getFullYear()]) {
dates[next.getFullYear()] = [];
}
if (! dates[next.getFullYear()][next.getMonth()]) {
dates[next.getFullYear()][next.getMonth()] = [];
}
dates[next.getFullYear()][next.getMonth()].push(next);
last = next;
}
return dates;
};
// Convert data to Date object
Kanboard.Gantt.prototype.prepareData = function(data) {
for (var i = 0; i < data.length; i++) {
var start = new Date(data[i].start[0], data[i].start[1] - 1, data[i].start[2], 0, 0, 0, 0);
data[i].start = start;
var end = new Date(data[i].end[0], data[i].end[1] - 1, data[i].end[2], 0, 0, 0, 0);
data[i].end = end;
}
return data;
};
// Get the start and end date from the data provided
Kanboard.Gantt.prototype.getDateRange = function(minDays) {
var minStart = new Date();
var maxEnd = new Date();
for (var i = 0; i < this.data.length; i++) {
var start = new Date();
start.setTime(Date.parse(this.data[i].start));
var end = new Date();
end.setTime(Date.parse(this.data[i].end));
if (i == 0) {
minStart = start;
maxEnd = end;
}
if (this.compareDate(minStart, start) == 1) {
minStart = start;
}
if (this.compareDate(maxEnd, end) == -1) {
maxEnd = end;
}
}
// Insure that the width of the chart is at least the slide width to avoid empty
// whitespace to the right of the grid
if (this.daysBetween(minStart, maxEnd) < minDays) {
maxEnd = this.addDays(this.cloneDate(minStart), minDays);
}
// Always start one day before the minStart
minStart.setDate(minStart.getDate() - 1);
return [minStart, maxEnd];
};
// Returns the number of day between 2 dates
Kanboard.Gantt.prototype.daysBetween = function(start, end) {
if (! start || ! end) {
return 0;
}
var count = 0, date = this.cloneDate(start);
while (this.compareDate(date, end) == -1) {
count = count + 1;
this.addDays(date, 1);
}
return count;
};
// Return true if it's the weekend
Kanboard.Gantt.prototype.isWeekend = function(date) {
return date.getDay() % 6 == 0;
};
// Clone Date object
Kanboard.Gantt.prototype.cloneDate = function(date) {
return new Date(date.getTime());
};
// Add days to a Date object
Kanboard.Gantt.prototype.addDays = function(date, value) {
date.setDate(date.getDate() + value * 1);
return date;
};
/**
* Compares the first date to the second date and returns an number indication of their relative values.
*
* -1 = date1 is lessthan date2
* 0 = values are equal
* 1 = date1 is greaterthan date2.
*/
Kanboard.Gantt.prototype.compareDate = function(date1, date2) {
if (isNaN(date1) || isNaN(date2)) {
throw new Error(date1 + " - " + date2);
} else if (date1 instanceof Date && date2 instanceof Date) {
return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
} else {
throw new TypeError(date1 + " - " + date2);
}
};
Kanboard.LeadCycleTimeChart = function(app) {
this.app = app;
};
Kanboard.LeadCycleTimeChart.prototype.execute = function() {
if (this.app.hasId("analytic-lead-cycle-time")) {
this.show();
}
};
Kanboard.LeadCycleTimeChart.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var cycle = [chart.data("label-cycle")];
var lead = [chart.data("label-lead")];
var categories = [];
var types = {};
types[chart.data("label-cycle")] = 'area';
types[chart.data("label-lead")] = 'area-spline';
var colors = {};
colors[chart.data("label-lead")] = '#afb42b';
colors[chart.data("label-cycle")] = '#4e342e';
for (var i = 0; i < metrics.length; i++) {
cycle.push(parseInt(metrics[i].avg_cycle_time));
lead.push(parseInt(metrics[i].avg_lead_time));
categories.push(metrics[i].day);
}
c3.generate({
data: {
columns: [
lead,
cycle
],
types: types,
colors: colors
},
axis: {
x: {
type: 'category',
categories: categories
},
y: {
tick: {
format: this.app.formatDuration
}
}
}
});
};
Kanboard.Markdown = function(app) {
this.app = app;
this.editor = null;
};
Kanboard.Markdown.prototype.onPopoverOpened = function() {
this.listen();
};
Kanboard.Markdown.prototype.onPopoverClosed = function() {
this.listen();
};
Kanboard.Markdown.prototype.listen = function() {
var editors = $(".markdown-editor");
if (this.editor) {
this.destroy();
}
if (editors.length > 0) {
this.show(editors[0]);
}
};
Kanboard.Markdown.prototype.destroy = function() {
var cm = this.editor.codemirror;
var wrapper = cm.getWrapperElement();
for (var item in ["toolbar", "statusbar", "sideBySide"]) {
if (this.editor.gui[item]) {
wrapper.parentNode.removeChild(this.editor.gui[item]);
}
}
cm.toTextArea();
this.editor = null;
};
Kanboard.Markdown.prototype.show = function(textarea) {
var toolbar = ["bold", "italic", "strikethrough", "heading", "|", "unordered-list", "ordered-list", "link", "|", "code", "table"];
this.editor = new SimpleMDE({
element: textarea,
status: false,
toolbarTips: false,
autoDownloadFontAwesome: false,
spellChecker: false,
autosave: {
enabled: false
},
forceSync: true,
blockStyles: {
italic: "_"
},
toolbar: textarea.hasAttribute("data-markdown-editor-disable-toolbar") ? false : toolbar,
placeholder: textarea.getAttribute("placeholder")
});
};
Kanboard.Notification = function(app) {
this.app = app;
};
Kanboard.Notification.prototype.execute = function() {
$(".alert-fade-out").delay(4000).fadeOut(800, function() {
$(this).remove();
});
};
Kanboard.Popover = function(app) {
this.app = app;
};
Kanboard.Popover.prototype.listen = function() {
var self = this;
$(document).on("click", ".popover", function(e) {
self.onClick(e);
});
$(document).on("click", ".close-popover", function(e) {
self.close(e);
});
$(document).on("click", "#popover-container", function(e) {
self.close(e);
});
$(document).on("click", "#popover-content", function(e) {
e.stopPropagation();
});
};
Kanboard.Popover.prototype.onClick = function(e) {
e.preventDefault();
e.stopPropagation();
var target = e.currentTarget || e.target;
var link = target.getAttribute("href");
if (! link) {
link = target.getAttribute("data-href");
}
if (link) {
this.open(link);
}
};
Kanboard.Popover.prototype.isOpen = function() {
return $('#popover-container').size() > 0;
};
Kanboard.Popover.prototype.open = function(link) {
var self = this;
if (!self.isOpen()) {
$.get(link, function(content) {
$("body").prepend('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
self.executeOnOpenedListeners();
});
}
};
Kanboard.Popover.prototype.close = function(e) {
if (this.isOpen()) {
if (e) {
e.preventDefault();
}
$("#popover-container").remove();
this.executeOnClosedListeners();
}
};
Kanboard.Popover.prototype.ajaxReload = function(data, request, self) {
var redirect = request.getResponseHeader("X-Ajax-Redirect");
if (redirect === 'self') {
window.location.reload();
} else if (redirect && redirect.indexOf('#') > -1) {
window.location = redirect.split('#')[0];
} else if (redirect) {
window.location = redirect;
} else {
$("#popover-content").html(data);
$("#popover-content input[autofocus]").focus();
self.executeOnOpenedListeners();
}
};
Kanboard.Popover.prototype.executeOnOpenedListeners = function() {
for (var className in this.app.controllers) {
var controller = this.app.get(className);
if (typeof controller.onPopoverOpened === "function") {
controller.onPopoverOpened();
}
}
this.afterOpen();
};
Kanboard.Popover.prototype.executeOnClosedListeners = function() {
for (var className in this.app.controllers) {
var controller = this.app.get(className);
if (typeof controller.onPopoverClosed === "function") {
controller.onPopoverClosed();
}
}
};
Kanboard.Popover.prototype.afterOpen = function() {
var self = this;
var popoverForm = $("#popover-content .popover-form");
// Submit forms with Ajax request
if (popoverForm) {
popoverForm.on("submit", function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: popoverForm.attr("action"),
data: popoverForm.serialize(),
success: function(data, textStatus, request) {
self.ajaxReload(data, request, self);
},
beforeSend: function() {
var button = $('.popover-form button[type="submit"]');
button.html('<i class="fa fa-spinner fa-pulse"></i> ' + button.html());
button.attr("disabled", true);
}
});
});
}
// Submit link with Ajax request
$(document).on("click", ".popover-link", function(e) {
e.preventDefault();
$.ajax({
type: "GET",
url: $(this).attr("href"),
success: function(data, textStatus, request) {
self.ajaxReload(data, request, self);
}
});
});
// Autofocus fields (html5 autofocus works only with page onload)
$("[autofocus]").each(function() {
$(this).focus();
});
this.app.datePicker();
this.app.autoComplete();
};
Kanboard.ProjectCreation = function(app) {
this.app = app;
};
Kanboard.ProjectCreation.prototype.onPopoverOpened = function() {
$('#project-creation-form #form-src_project_id').on('change', function() {
var srcProjectId = $(this).val();
if (srcProjectId == 0) {
$(".project-creation-options").hide();
} else {
$(".project-creation-options").show();
}
});
};
Kanboard.ProjectPermission = function(app) {
this.app = app;
};
Kanboard.ProjectPermission.prototype.listen = function() {
$('.project-change-role').on('change', function () {
$.ajax({
cache: false,
url: $(this).data('url'),
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"id": $(this).data('id'),
"role": $(this).val()
})
});
});
};
Kanboard.Screenshot = function(app) {
this.app = app;
this.pasteCatcher = null;
};
Kanboard.Screenshot.prototype.onPopoverOpened = function() {
if (this.app.hasId("screenshot-zone")) {
this.initialize();
}
};
// Setup event listener and workarounds
Kanboard.Screenshot.prototype.initialize = function() {
this.destroy();
if (! window.Clipboard) {
// Create a contenteditable element
this.pasteCatcher = document.createElement("div");
this.pasteCatcher.id = "screenshot-pastezone";
this.pasteCatcher.contentEditable = "true";
// Insert the content editable at the top to avoid scrolling down in the board view
this.pasteCatcher.style.opacity = 0;
this.pasteCatcher.style.position = "fixed";
this.pasteCatcher.style.top = 0;
this.pasteCatcher.style.right = 0;
this.pasteCatcher.style.width = 0;
document.body.insertBefore(this.pasteCatcher, document.body.firstChild);
// Set focus on the contenteditable element
this.pasteCatcher.focus();
// Set the focus when clicked anywhere in the document
document.addEventListener("click", this.setFocus.bind(this));
// Set the focus when clicked in screenshot dropzone (popover)
document.getElementById("screenshot-zone").addEventListener("click", this.setFocus.bind(this));
}
window.addEventListener("paste", this.pasteHandler.bind(this));
};
// Destroy contentEditable element
Kanboard.Screenshot.prototype.destroy = function() {
if (this.pasteCatcher != null) {
document.body.removeChild(this.pasteCatcher);
}
else if (document.getElementById("screenshot-pastezone")) {
document.body.removeChild(document.getElementById("screenshot-pastezone"));
}
document.removeEventListener("click", this.setFocus.bind(this));
this.pasteCatcher = null;
};
// Set focus on contentEditable element
Kanboard.Screenshot.prototype.setFocus = function() {
if (this.pasteCatcher !== null) {
this.pasteCatcher.focus();
}
};
// Paste event callback
Kanboard.Screenshot.prototype.pasteHandler = function(e) {
// Firefox doesn't have the property e.clipboardData.items (only Chrome)
if (e.clipboardData && e.clipboardData.items) {
var items = e.clipboardData.items;
if (items) {
for (var i = 0; i < items.length; i++) {
// Find an image in pasted elements
if (items[i].type.indexOf("image") !== -1) {
var blob = items[i].getAsFile();
// Get the image as base64 data
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
self.createImage(event.target.result);
};
reader.readAsDataURL(blob);
}
}
}
}
else {
// Handle Firefox
setTimeout(this.checkInput.bind(this), 100);
}
};
// Parse the input in the paste catcher element
Kanboard.Screenshot.prototype.checkInput = function() {
var child = this.pasteCatcher.childNodes[0];
if (child) {
// If the user pastes an image, the src attribute
// will represent the image as a base64 encoded string.
if (child.tagName === "IMG") {
this.createImage(child.src);
}
}
this.pasteCatcher.innerHTML = "";
};
// Creates a new image from a given source
Kanboard.Screenshot.prototype.createImage = function(blob) {
var pastedImage = new Image();
pastedImage.src = blob;
// Send the image content to the form variable
pastedImage.onload = function() {
var sourceSplit = blob.split("base64,");
var sourceString = sourceSplit[1];
$("input[name=screenshot]").val(sourceString);
};
var zone = document.getElementById("screenshot-zone");
zone.innerHTML = "";
zone.className = "screenshot-pasted";
zone.appendChild(pastedImage);
this.destroy();
this.initialize();
};
Kanboard.Search = function(app) {
this.app = app;
};
Kanboard.Search.prototype.focus = function() {
// Place cursor at the end when focusing on the search box
$(document).on("focus", "#form-search", function() {
var input = $("#form-search");
if (input[0].setSelectionRange) {
var len = input.val().length * 2;
input[0].setSelectionRange(len, len);
}
});
};
Kanboard.Search.prototype.listen = function() {
$(document).on("click", ".filter-helper", function (e) {
e.preventDefault();
var filter = $(this).data("filter");
var appendFilter = $(this).data("append-filter");
var uniqueFilter = $(this).data("unique-filter");
var input = $("#form-search");
if (uniqueFilter) {
var attribute = uniqueFilter.substr(0, uniqueFilter.indexOf(':'));
filter = input.val().replace(new RegExp('(' + attribute + ':[#a-z0-9]+)', 'g'), '');
filter = filter.replace(new RegExp('(' + attribute + ':"(.+)")', 'g'), '');
filter = filter.trim();
filter += ' ' + uniqueFilter;
} else if (appendFilter) {
filter = input.val() + " " + appendFilter;
}
input.val(filter);
$("form.search").submit();
});
};
Kanboard.Search.prototype.goToView = function(label) {
var link = $(label);
if (link.length) {
window.location = link.attr('href');
}
};
Kanboard.Search.prototype.keyboardShortcuts = function() {
var self = this;
// Switch view mode for projects: go to the overview page
Mousetrap.bind("v o", function() {
self.goToView(".view-overview");
});
// Switch view mode for projects: go to the board
Mousetrap.bind("v b", function() {
self.goToView(".view-board");
});
// Switch view mode for projects: go to the calendar
Mousetrap.bind("v c", function() {
self.goToView(".view-calendar");
});
// Switch view mode for projects: go to the listing
Mousetrap.bind("v l", function() {
self.goToView(".view-listing");
});
// Switch view mode for projects: go to the gantt chart
Mousetrap.bind("v g", function() {
self.goToView(".view-gantt");
});
// Focus to the search field
Mousetrap.bind("f", function(e) {
e.preventDefault();
var input = document.getElementById("form-search");
if (input) {
input.focus();
}
});
// Reset to the search field
Mousetrap.bind("r", function(e) {
e.preventDefault();
var reset = $(".filter-reset").data("filter");
var input = $("#form-search");
input.val(reset);
$("form.search").submit();
});
};
Kanboard.Session = function(app) {
this.app = app;
};
Kanboard.Session.prototype.execute = function() {
window.setInterval(this.checkSession, 60000);
};
Kanboard.Session.prototype.checkSession = function() {
if (! $(".form-login").length) {
$.ajax({
cache: false,
url: $("body").data("status-url"),
statusCode: {
401: function() {
window.location = $("body").data("login-url");
}
}
});
}
};
Kanboard.Subtask = function(app) {
this.app = app;
};
Kanboard.Subtask.prototype.listen = function() {
var self = this;
this.dragAndDrop();
$(document).on("click", ".subtask-toggle-status", function(e) {
var el = $(this);
e.preventDefault();
$.ajax({
cache: false,
url: el.attr("href"),
success: function(data) {
if (el.hasClass("subtask-refresh-table")) {
$(".subtasks-table").replaceWith(data);
} else {
el.replaceWith(data);
}
self.dragAndDrop();
}
});
});
$(document).on("click", ".subtask-toggle-timer", function(e) {
var el = $(this);
e.preventDefault();
$.ajax({
cache: false,
url: el.attr("href"),
success: function(data) {
$(".subtasks-table").replaceWith(data);
self.dragAndDrop();
}
});
});
};
Kanboard.Subtask.prototype.dragAndDrop = function() {
var self = this;
$(".draggable-row-handle").mouseenter(function() {
$(this).parent().parent().addClass("draggable-item-hover");
}).mouseleave(function() {
$(this).parent().parent().removeClass("draggable-item-hover");
});
$(".subtasks-table tbody").sortable({
forcePlaceholderSize: true,
handle: "td:first i",
helper: function(e, ui) {
ui.children().each(function() {
$(this).width($(this).width());
});
return ui;
},
stop: function(event, ui) {
var subtask = ui.item;
subtask.removeClass("draggable-item-selected");
self.savePosition(subtask.data("subtask-id"), subtask.index() + 1);
},
start: function(event, ui) {
ui.item.addClass("draggable-item-selected");
}
}).disableSelection();
};
Kanboard.Subtask.prototype.savePosition = function(subtaskId, position) {
var url = $(".subtasks-table").data("save-position-url");
var self = this;
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: url,
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"subtask_id": subtaskId,
"position": position
}),
complete: function() {
self.app.hideLoadingIcon();
}
});
};
Kanboard.Swimlane = function(app) {
this.app = app;
};
Kanboard.Swimlane.prototype.execute = function() {
if ($(".swimlanes-table").length) {
this.dragAndDrop();
}
};
Kanboard.Swimlane.prototype.listen = function() {
var self = this;
$(document).on('click', ".board-swimlane-toggle", function(e) {
e.preventDefault();
var swimlaneId = $(this).data('swimlane-id');
if (self.isCollapsed(swimlaneId)) {
self.expand(swimlaneId);
}
else {
self.collapse(swimlaneId);
}
});
};
Kanboard.Swimlane.prototype.onBoardRendered = function() {
var swimlaneIds = this.getAllCollapsed();
for (var i = 0; i < swimlaneIds.length; i++) {
this.collapse(swimlaneIds[i]);
}
};
Kanboard.Swimlane.prototype.getStorageKey = function() {
return "hidden_swimlanes_" + $("#board").data("project-id");
};
Kanboard.Swimlane.prototype.expand = function(swimlaneId) {
var swimlaneIds = this.getAllCollapsed();
var index = swimlaneIds.indexOf(swimlaneId);
if (index > -1) {
swimlaneIds.splice(index, 1);
}
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
$('.board-swimlane-columns-' + swimlaneId).css('display', 'table-row');
$('.board-swimlane-tasks-' + swimlaneId).css('display', 'table-row');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
};
Kanboard.Swimlane.prototype.collapse = function(swimlaneId) {
var swimlaneIds = this.getAllCollapsed();
if (swimlaneIds.indexOf(swimlaneId) < 0) {
swimlaneIds.push(swimlaneId);
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
}
$('.board-swimlane-columns-' + swimlaneId + ':not(:first-child)').css('display', 'none');
$('.board-swimlane-tasks-' + swimlaneId).css('display', 'none');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
};
Kanboard.Swimlane.prototype.isCollapsed = function(swimlaneId) {
return this.getAllCollapsed().indexOf(swimlaneId) > -1;
};
Kanboard.Swimlane.prototype.getAllCollapsed = function() {
return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
};
Kanboard.Swimlane.prototype.dragAndDrop = function() {
var self = this;
$(".draggable-row-handle").mouseenter(function() {
$(this).parent().parent().addClass("draggable-item-hover");
}).mouseleave(function() {
$(this).parent().parent().removeClass("draggable-item-hover");
});
$(".swimlanes-table tbody").sortable({
forcePlaceholderSize: true,
handle: "td:first i",
helper: function(e, ui) {
ui.children().each(function() {
$(this).width($(this).width());
});
return ui;
},
stop: function(event, ui) {
var swimlane = ui.item;
swimlane.removeClass("draggable-item-selected");
self.savePosition(swimlane.data("swimlane-id"), swimlane.index() + 1);
},
start: function(event, ui) {
ui.item.addClass("draggable-item-selected");
}
}).disableSelection();
};
Kanboard.Swimlane.prototype.savePosition = function(swimlaneId, position) {
var url = $(".swimlanes-table").data("save-position-url");
var self = this;
this.app.showLoadingIcon();
$.ajax({
cache: false,
url: url,
contentType: "application/json",
type: "POST",
processData: false,
data: JSON.stringify({
"swimlane_id": swimlaneId,
"position": position
}),
complete: function() {
self.app.hideLoadingIcon();
}
});
};
Kanboard.Task = function(app) {
this.app = app;
};
Kanboard.Task.prototype.keyboardShortcuts = function() {
var taskView = $("#task-view");
var self = this;
if (this.app.hasId("task-view")) {
Mousetrap.bind("e", function() {
self.app.get("Popover").open(taskView.data("edit-url"));
});
Mousetrap.bind("d", function() {
self.app.get("Popover").open(taskView.data("description-url"));
});
Mousetrap.bind("c", function() {
self.app.get("Popover").open(taskView.data("comment-url"));
});
Mousetrap.bind("s", function() {
self.app.get("Popover").open(taskView.data("subtask-url"));
});
Mousetrap.bind("l", function() {
self.app.get("Popover").open(taskView.data("internal-link-url"));
});
}
};
Kanboard.Task.prototype.onPopoverOpened = function() {
var self = this;
var reloadingProjectId = 0;
// Change color
$(document).on("click", ".color-square", function() {
$(".color-square-selected").removeClass("color-square-selected");
$(this).addClass("color-square-selected");
$("#form-color_id").val($(this).data("color-id"));
});
// Assign to me
$(document).on("click", ".assign-me", function(e) {
var currentId = $(this).data("current-id");
var dropdownId = "#" + $(this).data("target-id");
e.preventDefault();
if ($(dropdownId + ' option[value=' + currentId + ']').length) {
$(dropdownId).val(currentId);
}
});
// Reload page when a destination project is changed
$(document).on("change", "select.task-reload-project-destination", function() {
if (reloadingProjectId > 0) {
$(this).val(reloadingProjectId);
}
else {
reloadingProjectId = $(this).val();
var url = $(this).data("redirect").replace(/PROJECT_ID/g, reloadingProjectId);
$(".loading-icon").show();
$.ajax({
type: "GET",
url: url,
success: function(data, textStatus, request) {
reloadingProjectId = 0;
$(".loading-icon").hide();
self.app.get("Popover").ajaxReload(data, request, self.app.get("Popover"));
}
});
}
});
};
Kanboard.TaskRepartitionChart = function(app) {
this.app = app;
};
Kanboard.TaskRepartitionChart.prototype.execute = function() {
if (this.app.hasId("analytic-task-repartition")) {
this.show();
}
};
Kanboard.TaskRepartitionChart.prototype.show = function() {
var metrics = $("#chart").data("metrics");
var columns = [];
for (var i = 0; i < metrics.length; i++) {
columns.push([metrics[i].column_title, metrics[i].nb_tasks]);
}
c3.generate({
data: {
columns: columns,
type : 'donut'
}
});
};
Kanboard.TaskTimeColumnChart = function(app) {
this.app = app;
};
Kanboard.TaskTimeColumnChart.prototype.execute = function() {
if (this.app.hasId("analytic-task-time-column")) {
this.show();
}
};
Kanboard.TaskTimeColumnChart.prototype.show = function() {
var chart = $("#chart");
var metrics = chart.data("metrics");
var plots = [chart.data("label")];
var categories = [];
for (var i = 0; i < metrics.length; i++) {
plots.push(metrics[i].time_spent);
categories.push(metrics[i].title);
}
c3.generate({
data: {
columns: [plots],
type: 'bar'
},
bar: {
width: {
ratio: 0.5
}
},
axis: {
x: {
type: 'category',
categories: categories
},
y: {
tick: {
format: this.app.formatDuration
}
}
},
legend: {
show: false
}
});
};
Kanboard.Tooltip = function(app) {
this.app = app;
};
Kanboard.Tooltip.prototype.onBoardRendered = function() {
this.execute();
};
Kanboard.Tooltip.prototype.execute = function() {
$(".tooltip").tooltip({
track: false,
show: false,
hide: false,
position: {
my: 'left-20 top',
at: 'center bottom+9',
using: function(position, feedback) {
$(this).css(position);
var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
$("<div>")
.addClass("tooltip-arrow")
.addClass(feedback.vertical)
.addClass(arrow_pos < 1 ? "align-left" : "align-right")
.appendTo(this);
}
},
content: function() {
var _this = this;
var href = $(this).attr('data-href');
if (! href) {
return '<div class="markdown">' + $(this).attr("title") + '</div>';
}
$.get(href, function setTooltipContent(data) {
var tooltip = $('.ui-tooltip:visible');
$('.ui-tooltip-content:visible').html(data);
// Clear previous position, it interferes with the updated position computation
tooltip.css({ top: '', left: '' });
// Remove arrow, it will be added when repositionning
tooltip.children('.tooltip-arrow').remove();
// Reposition the tooltip
var position = $(_this).tooltip("option", "position");
position.of = $(_this);
tooltip.position(position);
});
return '<i class="fa fa-spinner fa-spin"></i>';
}
})
.on("mouseenter", function() {
var _this = this;
$(this).tooltip("open");
$(".ui-tooltip").on("mouseleave", function() {
$(_this).tooltip('close');
});
}).on("mouseleave focusout", function(e) {
e.stopImmediatePropagation();
var _this = this;
setTimeout(function() {
if (! $(".ui-tooltip:hover").length) {
$(_this).tooltip("close");
}
}, 100);
});
};
Kanboard.UserRepartitionChart = function(app) {
this.app = app;
};
Kanboard.UserRepartitionChart.prototype.execute = function() {
if (this.app.hasId("analytic-user-repartition")) {
this.show();
}
};
Kanboard.UserRepartitionChart.prototype.show = function() {
var metrics = $("#chart").data("metrics");
var columns = [];
for (var i = 0; i < metrics.length; i++) {
columns.push([metrics[i].user, metrics[i].nb_tasks]);
}
c3.generate({
data: {
columns: columns,
type : 'donut'
}
});
};
jQuery(document).ready(function() {
var app = new Kanboard.App();
app.execute();
});