summaryrefslogtreecommitdiff
path: root/assets/js/src
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/src')
-rw-r--r--assets/js/src/analytic.js209
-rw-r--r--assets/js/src/base.js284
-rw-r--r--assets/js/src/board.js411
-rw-r--r--assets/js/src/budget.js38
-rw-r--r--assets/js/src/calendar.js152
-rw-r--r--assets/js/src/dashboard.js52
-rw-r--r--assets/js/src/screenshot.js102
-rw-r--r--assets/js/src/swimlane.js90
8 files changed, 1338 insertions, 0 deletions
diff --git a/assets/js/src/analytic.js b/assets/js/src/analytic.js
new file mode 100644
index 00000000..0912bbcb
--- /dev/null
+++ b/assets/js/src/analytic.js
@@ -0,0 +1,209 @@
+
+Kanboard.Analytic = (function() {
+
+ jQuery(document).ready(function() {
+
+ if (Kanboard.Exists("analytic-task-repartition")) {
+ Kanboard.Analytic.TaskRepartition.Init();
+ }
+ else if (Kanboard.Exists("analytic-user-repartition")) {
+ Kanboard.Analytic.UserRepartition.Init();
+ }
+ else if (Kanboard.Exists("analytic-cfd")) {
+ Kanboard.Analytic.CFD.Init();
+ }
+ else if (Kanboard.Exists("analytic-burndown")) {
+ Kanboard.Analytic.Burndown.Init();
+ }
+ });
+
+ return {};
+
+})();
+
+Kanboard.Analytic.Burndown = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 380);
+ var chart = new dimple.chart(svg, series);
+
+ var x = chart.addCategoryAxis("x", labels['day']);
+ x.addOrderRule("Date");
+
+ chart.addMeasureAxis("y", labels['score']);
+ chart.addSeries(null, dimple.plot.line);
+
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var row = {};
+ var score = parseInt(metrics[i]['score']);
+ row[labels['day']] = metrics[i]['day'];
+ row[labels['score']] = score;
+ series.push(row);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
+Kanboard.Analytic.CFD = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels, data.columns);
+ });
+ }
+
+ function drawGraph(metrics, labels, columns)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 380);
+ var chart = new dimple.chart(svg, series);
+
+ var x = chart.addCategoryAxis("x", labels['day']);
+ x.addOrderRule("Date");
+
+ chart.addMeasureAxis("y", labels['total']);
+
+ var s = chart.addSeries(labels['column'], dimple.plot.area);
+ s.addOrderRule(columns.reverse());
+
+ chart.addLegend(10, 10, 500, 30, "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var row = {};
+ row[labels['column']] = metrics[i]['column_title'];
+ row[labels['day']] = metrics[i]['day'];
+ row[labels['total']] = metrics[i]['total'];
+ series.push(row);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
+Kanboard.Analytic.TaskRepartition = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 350);
+
+ var chart = new dimple.chart(svg, series);
+ chart.addMeasureAxis("p", labels["nb_tasks"]);
+ var ring = chart.addSeries(labels["column_title"], dimple.plot.pie);
+ ring.innerRadius = "50%";
+ chart.addLegend(0, 0, 100, "100%", "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var serie = {};
+ serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
+ serie[labels["column_title"]] = metrics[i]["column_title"];
+
+ series.push(serie);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
+Kanboard.Analytic.UserRepartition = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 350);
+
+ var chart = new dimple.chart(svg, series);
+ chart.addMeasureAxis("p", labels["nb_tasks"]);
+ var ring = chart.addSeries(labels["user"], dimple.plot.pie);
+ ring.innerRadius = "50%";
+ chart.addLegend(0, 0, 100, "100%", "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var serie = {};
+ serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
+ serie[labels["user"]] = metrics[i]["user"];
+
+ series.push(serie);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
diff --git a/assets/js/src/base.js b/assets/js/src/base.js
new file mode 100644
index 00000000..0d46b503
--- /dev/null
+++ b/assets/js/src/base.js
@@ -0,0 +1,284 @@
+// Common functions
+var Kanboard = (function() {
+
+ jQuery(document).ready(function() {
+ Kanboard.Init();
+ });
+
+ return {
+
+ // Return true if the element#id exists
+ Exists: function(id) {
+ if (document.getElementById(id)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ // Open a popup on a link click
+ Popover: function(e, callback) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ var link = e.target.getAttribute("href");
+
+ if (! link) {
+ link = e.target.getAttribute("data-href");
+ }
+
+ if (link) {
+ Kanboard.OpenPopover(link, callback);
+ }
+ },
+
+ // Display a popup
+ OpenPopover: function(link, callback) {
+
+ $.get(link, function(content) {
+
+ $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
+
+ $("#popover-container").click(function() {
+ $(this).remove();
+ });
+
+ $("#popover-content").click(function(e) {
+ e.stopPropagation();
+ });
+
+ $(".close-popover").click(function(e) {
+ e.preventDefault();
+ $('#popover-container').remove();
+ });
+
+ Mousetrap.bind("esc", function() {
+ $('#popover-container').remove();
+ });
+
+ if (callback) {
+ callback();
+ }
+ });
+ },
+
+ // Return true if the page is visible
+ 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;
+ },
+
+ // Save preferences in local storage
+ SetStorageItem: function(key, value) {
+ if (typeof(Storage) !== "undefined") {
+ localStorage.setItem(key, value);
+ }
+ },
+
+ GetStorageItem: function(key) {
+
+ if (typeof(Storage) !== "undefined") {
+ return localStorage.getItem(key);
+ }
+
+ return '';
+ },
+
+ // Generate Markdown preview
+ MarkdownPreview: function(e) {
+
+ e.preventDefault();
+
+ var link = $(this);
+ var nav = $(this).closest("ul");
+ var write = $(".write-area");
+ var preview = $(".preview-area");
+ var textarea = $("textarea");
+
+ var request = $.ajax({
+ url: "?controller=app&action=preview",
+ contentType: "application/json",
+ type: "POST",
+ processData: false,
+ dataType: "html",
+ data: JSON.stringify({
+ "text": textarea.val()
+ })
+ });
+
+ request.done(function(data) {
+
+ nav.find("li").removeClass("form-tab-selected");
+ link.parent().addClass("form-tab-selected");
+
+ preview.find(".markdown").html(data)
+ preview.css("height", textarea.css("height"));
+ preview.css("width", textarea.css("width"));
+
+ write.hide();
+ preview.show();
+ });
+ },
+
+ // Show the Markdown textarea
+ MarkdownWriter: function(e) {
+
+ e.preventDefault();
+
+ $(this).closest("ul").find("li").removeClass("form-tab-selected")
+ $(this).parent().addClass("form-tab-selected");
+
+ $(".write-area").show();
+ $(".preview-area").hide();
+ },
+
+ // Check session and redirect to the login page if not logged
+ CheckSession: function() {
+
+ if (! $(".form-login").length) {
+ $.ajax({
+ cache: false,
+ url: $("body").data("status-url"),
+ statusCode: {
+ 401: function() {
+ window.location = $("body").data("login-url");
+ }
+ }
+ });
+ }
+ },
+
+ Init: function() {
+
+ // Chosen select
+ $(".chosen-select").chosen({
+ width: "200px",
+ no_results_text: $(".chosen-select").data("notfound"),
+ disable_search_threshold: 10
+ });
+
+ // Project select box
+ $("#board-selector").chosen({
+ width: 180,
+ no_results_text: $("#board-selector").data("notfound")
+ });
+
+ $("#board-selector").change(function() {
+ window.location = $(this).attr("data-board-url").replace(/PROJECT_ID/g, $(this).val());
+ });
+
+ // Check the session every 60s
+ window.setInterval(Kanboard.CheckSession, 60000);
+
+ // Keyboard shortcuts
+ Mousetrap.bindGlobal("mod+enter", function() {
+ $("form").submit();
+ });
+
+ Mousetrap.bind("b", function(e) {
+ e.preventDefault();
+ $('#board-selector').trigger('chosen:open');
+ });
+
+ $.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);
+
+ Kanboard.InitAfterAjax();
+ },
+
+ InitAfterAjax: function() {
+
+ // Popover
+ $(document).on("click", ".popover", Kanboard.Popover);
+
+ // Autofocus fields (html5 autofocus works only with page onload)
+ $("[autofocus]").each(function(index, element) {
+ $(this).focus();
+ })
+
+ // Datepicker
+ $(".form-date").datepicker({
+ showOtherMonths: true,
+ selectOtherMonths: true,
+ dateFormat: 'yy-mm-dd',
+ constrainInput: false
+ });
+
+ // Markdown Preview for textareas
+ $("#markdown-preview").click(Kanboard.MarkdownPreview);
+ $("#markdown-write").click(Kanboard.MarkdownWriter);
+
+ // Auto-select input fields
+ $(".auto-select").focus(function() {
+ $(this).select();
+ });
+
+ // Dropdown
+ $(".dropit-submenu").hide();
+ $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" });
+
+ // Task auto-completion
+ if ($(".task-autocomplete").length) {
+
+ if ($('.opposite_task_id').val() == '') {
+ $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled');
+ }
+
+ $(".task-autocomplete").autocomplete({
+ source: $(".task-autocomplete").data("search-url"),
+ minLength: 1,
+ select: function(event, ui) {
+ var field = $(".task-autocomplete").data("dst-field");
+ $("input[name=" + field + "]").val(ui.item.id);
+
+ $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled');
+ }
+ });
+ }
+
+ // Tooltip for column description
+ $(".column-tooltip").tooltip({
+ content: function() {
+ return '<div class="markdown">' + $(this).attr("title") + '</div>';
+ },
+ 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 == 0 ? "align-left" : "align-right")
+ .appendTo(this);
+ }
+ }
+ });
+
+ // Screenshot
+ if (Kanboard.Exists("screenshot-zone")) {
+ Kanboard.Screenshot.Init();
+ }
+ }
+ };
+
+})();
diff --git a/assets/js/src/board.js b/assets/js/src/board.js
new file mode 100644
index 00000000..9f93a869
--- /dev/null
+++ b/assets/js/src/board.js
@@ -0,0 +1,411 @@
+Kanboard.Board = (function() {
+
+ var checkInterval = null;
+
+ function on_popover(e)
+ {
+ e.preventDefault();
+ e.stopPropagation();
+
+ Kanboard.Popover(e, Kanboard.InitAfterAjax);
+ }
+
+ function keyboard_shortcuts()
+ {
+ Mousetrap.bind("n", function() {
+
+ Kanboard.OpenPopover(
+ $("#board").data("task-creation-url"),
+ Kanboard.InitAfterAjax
+ );
+ });
+
+ Mousetrap.bind("s", function() {
+ stack_toggle();
+ });
+
+ Mousetrap.bind("c", function() {
+ compactview_toggle();
+ });
+ }
+
+ // Collapse/Expand tasks
+ function stack_load_events()
+ {
+ $(".filter-expand-link").click(function(e) {
+ e.preventDefault();
+ stack_expand();
+ Kanboard.SetStorageItem(stack_key(), "expanded");
+ });
+
+ $(".filter-collapse-link").click(function(e) {
+ e.preventDefault();
+ stack_collapse();
+ Kanboard.SetStorageItem(stack_key(), "collapsed");
+ });
+
+ stack_show();
+ }
+
+ function stack_key()
+ {
+ var projectId = $('#board').data('project-id');
+ return "board_stacking_" + projectId;
+ }
+
+ function stack_collapse()
+ {
+ $(".filter-collapse").hide();
+ $(".task-board-collapsed").show();
+
+ $(".filter-expand").show();
+ $(".task-board-expanded").hide();
+ }
+
+ function stack_expand()
+ {
+ $(".filter-collapse").show();
+ $(".task-board-collapsed").hide();
+
+ $(".filter-expand").hide();
+ $(".task-board-expanded").show();
+ }
+
+ function stack_toggle()
+ {
+ var state = Kanboard.GetStorageItem(stack_key()) || "expanded";
+
+ if (state === "expanded") {
+ stack_collapse();
+ Kanboard.SetStorageItem(stack_key(), "collapsed");
+ }
+ else {
+ stack_expand();
+ Kanboard.SetStorageItem(stack_key(), "expanded");
+ }
+ }
+
+ function stack_show()
+ {
+ var state = Kanboard.GetStorageItem(stack_key()) || "expanded";
+
+ if (state === "expanded") {
+ stack_expand();
+ }
+ else {
+ stack_collapse();
+ }
+ }
+
+ // Setup the board
+ function board_load_events()
+ {
+ // Drag and drop
+ $(".column").sortable({
+ delay: 300,
+ distance: 5,
+ connectWith: ".column",
+ placeholder: "draggable-placeholder",
+ stop: function(event, ui) {
+ board_save(
+ ui.item.attr('data-task-id'),
+ ui.item.parent().attr("data-column-id"),
+ ui.item.index() + 1,
+ ui.item.parent().attr('data-swimlane-id')
+ );
+ }
+ });
+
+ // Task popover
+ $("#board").on("click", ".task-board-popover", on_popover);
+
+ // Redirect to the task details page
+ $("#board").on("click", ".task-board", function() {
+ window.location = $(this).data("task-url");
+ });
+
+ // Tooltips for tasks
+ $(".task-board-tooltip").tooltip({
+ track: 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 == 0 ? "align-left" : "align-right")
+ .appendTo(this);
+ }
+ },
+ content: function(e) {
+ var href = $(this).attr('data-href');
+
+ if (! href) {
+ return;
+ }
+
+ var _this = this;
+ $.get(href, function setTooltipContent(data) {
+
+ $('.ui-tooltip-content:visible').html(data);
+
+ var tooltip = $('.ui-tooltip:visible');
+
+ // 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);
+
+ // Toggle subtasks status
+ $('#tooltip-subtasks a').not(".popover").click(function(e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if ($(this).hasClass("popover-subtask-restriction")) {
+ Kanboard.OpenPopover($(this).attr('href'));
+ $(_this).tooltip('close');
+ }
+ else {
+ $.get($(this).attr('href'), setTooltipContent);
+ }
+ });
+ });
+
+ return '<i class="fa fa-refresh fa-spin fa-2x"></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);
+ });
+
+ // Automatic refresh
+ var interval = parseInt($("#board").attr("data-check-interval"));
+
+ if (interval > 0) {
+ checkInterval = window.setInterval(board_check, interval * 1000);
+ }
+ }
+
+ // Stop events
+ function board_unload_events()
+ {
+ clearInterval(checkInterval);
+ }
+
+ // Save and refresh the board
+ function board_save(taskId, columnId, position, swimlaneId)
+ {
+ board_unload_events();
+
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("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) {
+ $("#board-container").remove();
+ $("#main").append(data);
+ Kanboard.InitAfterAjax();
+ board_load_events();
+ filter_apply();
+ stack_show();
+ compactview_reload();
+ }
+ });
+ }
+
+ // Check if the board have been changed by someone else
+ function board_check()
+ {
+ if (Kanboard.IsVisible()) {
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("data-check-url"),
+ statusCode: {
+ 200: function(data) {
+ $("#board-container").remove();
+ $("#main").append(data);
+ Kanboard.InitAfterAjax();
+ board_unload_events();
+ board_load_events();
+ filter_apply();
+ stack_show();
+ compactview_reload();
+ }
+ }
+ });
+ }
+ }
+
+ // Apply user or date filter (change tasks opacity)
+ function filter_apply()
+ {
+ var selectedUserId = $("#form-user_id").val();
+ var selectedCategoryId = $("#form-category_id").val();
+ var filterDueDate = $("#more-filters option[value=filter-due-date]").is(":selected")
+ var filterRecent = $("#more-filters option[value=filter-recent]").is(":selected")
+ var projectId = $('#board').data('project-id');
+
+ $("[data-task-id]").each(function(index, item) {
+
+ var ownerId = item.getAttribute("data-owner-id");
+ var dueDate = item.getAttribute("data-due-date");
+ var categoryId = item.getAttribute("data-category-id");
+ var recent = $(item).hasClass("task-board-recent");
+
+ if (ownerId != selectedUserId && selectedUserId != -1) {
+ item.style.display = "none";
+ }
+ else {
+ item.style.display = "block";
+ }
+
+ if (filterDueDate && (dueDate == "" || dueDate == "0")) {
+ item.style.display = "none";
+ }
+
+ if (categoryId != selectedCategoryId && selectedCategoryId != -1) {
+ item.style.display = "none";
+ }
+
+ if (filterRecent && ! recent) {
+ item.style.display = "none";
+ }
+ });
+
+ // Save filter settings
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_form-user_id", selectedUserId);
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_form-category_id", selectedCategoryId);
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_filter-due-date", ~~(filterDueDate));
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_filter-recent", ~~(filterRecent));
+ }
+
+ // Load filter events
+ function filter_load_events()
+ {
+ var projectId = $('#board').data('project-id');
+
+ $("#form-user_id").chosen({
+ width: "180px",
+ no_results_text: $("#form-user_id").data("notfound")
+ });
+
+ $("#form-category_id").chosen({
+ width: "200px",
+ no_results_text: $("#form-category_id").data("notfound")
+ });
+
+ $("#more-filters").chosen({
+ width: "30%",
+ no_results_text: $("#more-filters").data("notfound")
+ });
+
+ $(".apply-filters").change(function(e) {
+ filter_apply();
+ });
+
+ // Get and set filters from localStorage
+ $("#form-user_id").val(Kanboard.GetStorageItem("board_filter_" + projectId + "_form-user_id") || -1);
+ $("#form-user_id").trigger("chosen:updated");
+
+ $("#form-category_id").val(Kanboard.GetStorageItem("board_filter_" + projectId + "_form-category_id") || -1);
+ $("#form-category_id").trigger("chosen:updated");
+
+ if (+Kanboard.GetStorageItem("board_filter_" + projectId + "_filter-due-date")) {
+ $("#more-filters option[value=filter-due-date]").attr("selected", true);
+ }
+
+ if (+Kanboard.GetStorageItem("board_filter_" + projectId + "_filter-recent")) {
+ $("#more-filters option[value=filter-recent]").attr("selected", true);
+ }
+
+ $("#more-filters").trigger("chosen:updated");
+
+ filter_apply();
+ }
+
+ function compactview_load_events()
+ {
+ jQuery(document).on('click', ".filter-toggle-scrolling", function(e) {
+ e.preventDefault();
+ compactview_toggle();
+ });
+
+ compactview_reload();
+ }
+
+ function compactview_toggle()
+ {
+ var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1;
+ Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
+ compactview_reload();
+ }
+
+ function compactview_reload()
+ {
+ if (Kanboard.GetStorageItem("horizontal_scroll") == 0) {
+
+ $(".filter-wide").show();
+ $(".filter-compact").hide();
+
+ $("#board-container").addClass("board-container-compact");
+ $("#board th").addClass("board-column-compact");
+ }
+ else {
+
+ $(".filter-wide").hide();
+ $(".filter-compact").show();
+
+ $("#board-container").removeClass("board-container-compact");
+ $("#board th").removeClass("board-column-compact");
+ }
+ }
+
+ jQuery(document).ready(function() {
+
+ if (Kanboard.Exists("board")) {
+ board_load_events();
+ filter_load_events();
+ stack_load_events();
+ compactview_load_events();
+ keyboard_shortcuts();
+ }
+ });
+
+})();
diff --git a/assets/js/src/budget.js b/assets/js/src/budget.js
new file mode 100644
index 00000000..ee39c68c
--- /dev/null
+++ b/assets/js/src/budget.js
@@ -0,0 +1,38 @@
+Kanboard.Budget = (function() {
+
+ jQuery(document).ready(function() {
+
+ if (Kanboard.Exists("budget-chart")) {
+
+ var labels =$("#chart").data("labels");
+ var serie = $("#chart").data("serie");
+ var types = ["in", "out", "left"];
+ var data = [];
+
+ for (var i = 0; i < serie.length; i++) {
+
+ for (var j = 0; j < types.length; j++) {
+ var row = {};
+ row[labels["date"]] = serie[i]["date"];
+ row[labels["value"]] = serie[i][types[j]];
+ row[labels["type"]] = labels[types[j]];
+
+ data.push(row);
+ }
+ }
+
+ var svg = dimple.newSvg("#chart", 750, 600);
+ var myChart = new dimple.chart(svg, data);
+
+ var x = myChart.addCategoryAxis("x", [labels["date"], labels["type"]]);
+ x.addOrderRule(labels["date"]);
+
+ myChart.addMeasureAxis("y", labels["value"]);
+
+ myChart.addSeries(labels["type"], dimple.plot.bar);
+ myChart.addLegend(65, 10, 510, 20, "right");
+ myChart.draw();
+ }
+ });
+
+})(); \ No newline at end of file
diff --git a/assets/js/src/calendar.js b/assets/js/src/calendar.js
new file mode 100644
index 00000000..9ee5100d
--- /dev/null
+++ b/assets/js/src/calendar.js
@@ -0,0 +1,152 @@
+Kanboard.Calendar = (function() {
+
+ var filter_storage_key = "";
+
+ // Save the new due date for a moved task
+ function move_calendar_event(calendar_event)
+ {
+ var url = $("#calendar").data("save-url") || $("#user-calendar").data("save-url");
+
+ $.ajax({
+ cache: false,
+ url: url,
+ contentType: "application/json",
+ type: "POST",
+ processData: false,
+ data: JSON.stringify({
+ "task_id": calendar_event.id,
+ "date_due": calendar_event.start.format()
+ })
+ });
+ }
+
+ // Show the user calendar
+ function show_user_calendar()
+ {
+ var calendar = $("#user-calendar");
+
+ calendar.fullCalendar({
+ lang: $("body").data("js-lang"),
+ editable: true,
+ eventLimit: true,
+ height: Kanboard.Exists("dashboard-calendar") ? 500 : "auto",
+ defaultView: "agendaWeek",
+ header: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'month,agendaWeek,agendaDay'
+ },
+ viewRender: refresh_user_calendar,
+ eventDrop: move_calendar_event
+ });
+ }
+
+ // Refresh the calendar events
+ function refresh_user_calendar()
+ {
+ var calendar = $("#user-calendar");
+ var url = calendar.data("check-url");
+ var params = {
+ "start": calendar.fullCalendar('getView').start.format(),
+ "end": calendar.fullCalendar('getView').end.format(),
+ "user_id": calendar.data("user-id")
+ };
+
+ for (var key in params) {
+ url += "&" + key + "=" + params[key];
+ }
+
+ $.getJSON(url, function(events) {
+ calendar.fullCalendar('removeEvents');
+ calendar.fullCalendar('addEventSource', events);
+ calendar.fullCalendar('rerenderEvents');
+ });
+ }
+
+ // Show the project calendar
+ function show_project_calendar()
+ {
+ var calendar = $("#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'
+ },
+ viewRender: load_project_filters,
+ eventDrop: move_calendar_event
+ });
+ }
+
+ // Refresh the calendar events
+ function refresh_project_calendar(filters)
+ {
+ var calendar = $("#calendar");
+ var url = calendar.data("check-url");
+ var params = {
+ "start": calendar.fullCalendar('getView').start.format(),
+ "end": calendar.fullCalendar('getView').end.format()
+ };
+
+ jQuery.extend(params, filters);
+
+ for (var key in params) {
+ url += "&" + key + "=" + params[key];
+ }
+
+ $.getJSON(url, function(events) {
+ calendar.fullCalendar('removeEvents');
+ calendar.fullCalendar('addEventSource', events);
+ calendar.fullCalendar('rerenderEvents');
+ });
+ }
+
+ // Restore saved filters
+ function load_project_filters()
+ {
+ var filters = Kanboard.GetStorageItem(filter_storage_key);
+
+ if (filters !== "") {
+ filters = JSON.parse(filters);
+
+ for (var filter in filters) {
+ $("select[name=" + filter + "]").val(filters[filter]);
+ }
+ }
+
+ refresh_project_calendar(filters || {});
+
+ $('.calendar-filter').change(apply_project_filters);
+ }
+
+ // Apply filters on change
+ function apply_project_filters()
+ {
+ var filters = {};
+
+ $('.calendar-filter').each(function() {
+ filters[$(this).attr("name")] = $(this).val();
+ });
+
+ Kanboard.SetStorageItem(filter_storage_key, JSON.stringify(filters));
+ refresh_project_calendar(filters);
+ }
+
+ jQuery(document).ready(function() {
+
+ if (Kanboard.Exists("calendar")) {
+ filter_storage_key = "calendar_filters_" + $("#calendar").data("project-id");
+ show_project_calendar();
+ load_project_filters();
+ }
+ else if (Kanboard.Exists("user-calendar")) {
+ show_user_calendar();
+ }
+ });
+
+})();
diff --git a/assets/js/src/dashboard.js b/assets/js/src/dashboard.js
new file mode 100644
index 00000000..d0867e54
--- /dev/null
+++ b/assets/js/src/dashboard.js
@@ -0,0 +1,52 @@
+Kanboard.Dashboard = (function() {
+
+ jQuery(document).ready(function() {
+
+ var state = Kanboard.GetStorageItem("dashboard_view");
+
+ if (state) {
+
+ var sections = JSON.parse(state);
+
+ for (var section in sections) {
+ $("#dashboard-" + section).toggle(sections[section]);
+ }
+
+ hideColumns();
+ }
+ });
+
+ jQuery(document).on('click', ".dashboard-toggle", function(e) {
+ e.preventDefault();
+
+ $("#dashboard-" + $(this).data("toggle")).toggle();
+ hideColumns();
+
+ var sections = ["projects", "tasks", "subtasks", "activities", "calendar"];
+ var state = {};
+
+ for (var i = 0; i < sections.length; i++) {
+ state[sections[i]] = $("#dashboard-" + sections[i]).is(":visible");
+ }
+
+ Kanboard.SetStorageItem("dashboard_view", JSON.stringify(state));
+ });
+
+ function hideColumns()
+ {
+ if ($(".dashboard-right-column > div:visible").size() > 0) {
+ $(".dashboard-left-column").removeClass("dashboard-single-column");
+ }
+ else {
+ $(".dashboard-left-column").addClass("dashboard-single-column");
+ }
+
+ if ($(".dashboard-left-column > div:visible").size() > 0) {
+ $(".dashboard-right-column").removeClass("dashboard-single-column");
+ }
+ else {
+ $(".dashboard-right-column").addClass("dashboard-single-column");
+ }
+ }
+
+})(); \ No newline at end of file
diff --git a/assets/js/src/screenshot.js b/assets/js/src/screenshot.js
new file mode 100644
index 00000000..fef37356
--- /dev/null
+++ b/assets/js/src/screenshot.js
@@ -0,0 +1,102 @@
+Kanboard.Screenshot = (function() {
+
+ var pasteCatcher = null;
+
+ // Setup event listener and workarounds
+ function init()
+ {
+ if (! window.Clipboard) {
+
+ // Create a contenteditable element
+ pasteCatcher = document.createElement("div");
+ pasteCatcher.setAttribute("contenteditable", "");
+ pasteCatcher.style.opacity = 0;
+ document.body.appendChild(pasteCatcher);
+
+ // Make sure it is always in focus
+ pasteCatcher.focus();
+ document.addEventListener("click", function() { pasteCatcher.focus(); });
+ }
+
+ window.addEventListener("paste", pasteHandler);
+ }
+
+ // Paste event callback
+ function pasteHandler(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();
+ reader.onload = function(event) {
+ createImage(event.target.result);
+ };
+
+ reader.readAsDataURL(blob);
+ }
+ }
+ }
+ }
+ else {
+
+ // Handle Firefox
+ setTimeout(checkInput, 100);
+ }
+ }
+
+ // Parse the input in the paste catcher element
+ function checkInput()
+ {
+ var child = pasteCatcher.childNodes[0];
+ pasteCatcher.innerHTML = "";
+
+ if (child) {
+ // If the user pastes an image, the src attribute
+ // will represent the image as a base64 encoded string.
+ if (child.tagName === "IMG") {
+ createImage(child.src);
+ }
+ }
+ }
+
+ // Creates a new image from a given source
+ function createImage(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);
+ };
+
+ document.getElementById("screenshot-inner").style.display = "none";
+ document.getElementById("screenshot-zone").className = "screenshot-pasted";
+ document.getElementById("screenshot-zone").appendChild(pastedImage);
+ }
+
+ jQuery(document).ready(function() {
+
+ if (Kanboard.Exists("screenshot-zone")) {
+ init();
+ }
+ });
+
+ return {
+ Init: init
+ };
+})(); \ No newline at end of file
diff --git a/assets/js/src/swimlane.js b/assets/js/src/swimlane.js
new file mode 100644
index 00000000..77f45907
--- /dev/null
+++ b/assets/js/src/swimlane.js
@@ -0,0 +1,90 @@
+Kanboard.Swimlane = (function() {
+
+ // Expand a Swimlane via display attributes
+ function expand(swimlaneId)
+ {
+ $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ }
+
+ // Collapse a Swimlane via display attributes
+ function collapse(swimlaneId)
+ {
+ $('.swimlane-row-' + swimlaneId).css('display', 'none');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
+ }
+
+ // Add swimlane Id to the hidden list and stores the list to localStorage
+ function hide(id)
+ {
+ var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
+ var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
+
+ hiddenSwimlaneIds.push(id);
+
+ Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
+ }
+
+ // Remove swimlane Id from the hidden list and stores the list to
+ // localStorage
+ function unhide(id)
+ {
+ var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
+ var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
+ var index = hiddenSwimlaneIds.indexOf(id);
+
+ if (index > -1) {
+ hiddenSwimlaneIds.splice(index, 1);
+ }
+
+ Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
+ }
+
+ // Check if swimlane Id is hidden. Anything > -1 means hidden.
+ function isHidden(id)
+ {
+ return getAllHidden().indexOf(id) > -1;
+ }
+
+ // Gets all swimlane Ids that are hidden
+ function getAllHidden()
+ {
+ var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
+ return JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
+ }
+
+ // Reload the swimlane states (shown/hidden) after an ajax call
+ jQuery(document).ajaxComplete(function() {
+
+ getAllHidden().map(function(swimlaneId) {
+ collapse(swimlaneId);
+ });
+ });
+
+ // Reload the swimlane states (shown/hidden) after page refresh
+ jQuery(document).ready(function() {
+
+ getAllHidden().map(function(swimlaneId) {
+ collapse(swimlaneId);
+ });
+ });
+
+ // Clicking on Show/Hide icon fires this.
+ jQuery(document).on('click', ".board-swimlane-toggle", function(e) {
+ e.preventDefault();
+
+ var swimlaneId = $(this).data('swimlane-id');
+
+ if (isHidden(swimlaneId)) {
+ unhide(swimlaneId);
+ expand(swimlaneId);
+ }
+ else {
+ hide(swimlaneId);
+ collapse(swimlaneId);
+ }
+ });
+
+})();