Refactoring/rewrite of modal boxes handling

This commit is contained in:
Frederic Guillot
2017-01-02 17:01:27 -05:00
parent d49ce63e51
commit 3833c12ccc
173 changed files with 1526 additions and 1654 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +1,27 @@
KB.component('submit-cancel', function (containerElement, options) {
KB.component('confirm-buttons', function (containerElement, options) {
var isLoading = false;
function onSubmit() {
isLoading = true;
KB.find('#modal-submit-button').replace(buildButton());
KB.trigger('modal.submit');
KB.find('#modal-confirm-button').replace(buildButton());
window.location.href = options.url;
}
function onCancel() {
KB.trigger('modal.cancel');
_KB.get('Popover').close();
KB.trigger('modal.close');
}
function onStop() {
isLoading = false;
KB.find('#modal-submit-button').replace(buildButton());
KB.find('#modal-confirm-button').replace(buildButton());
}
function buildButton() {
var button = KB.dom('button')
.click(onSubmit)
.attr('id', 'modal-submit-button')
.attr('type', 'submit')
.attr('class', 'btn btn-blue');
.attr('id', 'modal-confirm-button')
.attr('type', 'button')
.attr('class', 'btn btn-red');
if (isLoading) {
button
@@ -32,6 +31,10 @@ KB.component('submit-cancel', function (containerElement, options) {
;
}
if (options.tabindex) {
button.attr('tabindex', options.tabindex);
}
return button
.text(options.submitLabel)
.build();

View File

@@ -0,0 +1,192 @@
KB.component('file-upload', function (containerElement, options) {
var inputFileElement = null;
var dropzoneElement = null;
var files = [];
var currentFileIndex = null;
function onProgress(e) {
if (e.lengthComputable) {
var progress = e.loaded / e.total;
var percentage = Math.floor(progress * 100);
KB.find('#file-progress-' + currentFileIndex).attr('value', progress);
KB.find('#file-percentage-' + currentFileIndex).replaceText('(' + percentage + '%)');
}
}
function onError() {
var errorElement = KB.dom('div').addClass('file-error').text(options.labelUploadError).build();
KB.find('#file-item-' + currentFileIndex).add(errorElement);
}
function onComplete() {
currentFileIndex++;
if (currentFileIndex < files.length) {
KB.http.uploadFile(options.url, files[currentFileIndex], onProgress, onComplete, onError);
} else {
KB.trigger('modal.stop');
KB.trigger('modal.hide');
var alertElement = KB.dom('div')
.addClass('alert')
.addClass('alert-success')
.text(options.labelSuccess)
.build();
var buttonElement = KB.dom('button')
.attr('type', 'button')
.addClass('btn')
.addClass('btn-blue')
.click(onCloseWindow)
.text(options.labelCloseSuccess)
.build();
KB.dom(dropzoneElement).replace(KB.dom('div').add(alertElement).add(buttonElement).build());
}
}
function onCloseWindow() {
window.location.reload();
}
function onSubmit() {
currentFileIndex = 0;
uploadFiles();
}
function onFileChange() {
files = inputFileElement.files;
showFiles();
}
function onClickFileBrowser() {
files = [];
currentFileIndex = 0;
inputFileElement.click();
}
function onDragOver(e) {
e.stopPropagation();
e.preventDefault();
}
function onDrop(e) {
e.stopPropagation();
e.preventDefault();
files = e.dataTransfer.files;
showFiles();
}
function uploadFiles() {
if (files.length > 0) {
KB.http.uploadFile(options.url, files[currentFileIndex], onProgress, onComplete, onError);
}
}
function showFiles() {
if (files.length > 0) {
KB.trigger('modal.enable');
KB.dom(dropzoneElement)
.empty()
.add(buildFileListElement());
} else {
KB.trigger('modal.disable');
KB.dom(dropzoneElement)
.empty()
.add(buildInnerDropzoneElement());
}
}
function buildFileInputElement() {
return KB.dom('input')
.attr('id', 'file-input-element')
.attr('type', 'file')
.attr('name', 'files[]')
.attr('multiple', true)
.on('change', onFileChange)
.hide()
.build();
}
function buildInnerDropzoneElement() {
var dropzoneLinkElement = KB.dom('a')
.attr('href', '#')
.text(options.labelChooseFiles)
.click(onClickFileBrowser)
.build();
return KB.dom('div')
.attr('id', 'file-dropzone-inner')
.text(options.labelDropzone + ' ' + options.labelOr + ' ')
.add(dropzoneLinkElement)
.build();
}
function buildDropzoneElement() {
var dropzoneElement = KB.dom('div')
.attr('id', 'file-dropzone')
.add(buildInnerDropzoneElement())
.build();
dropzoneElement.ondragover = onDragOver;
dropzoneElement.ondrop = onDrop;
dropzoneElement.ondragover = onDragOver;
return dropzoneElement;
}
function buildFileListItem(index) {
var isOversize = false;
var progressElement = KB.dom('progress')
.attr('id', 'file-progress-' + index)
.attr('value', 0)
.build();
var percentageElement = KB.dom('span')
.attr('id', 'file-percentage-' + index)
.text('(0%)')
.build();
var itemElement = KB.dom('li')
.attr('id', 'file-item-' + index)
.add(progressElement)
.text(' ' + files[index].name + ' ')
.add(percentageElement);
if (files[index].size > options.maxSize) {
itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());
isOversize = true;
}
if (isOversize) {
KB.trigger('modal.disable');
}
return itemElement.build();
}
function buildFileListElement() {
var fileListElement = KB.dom('ul')
.attr('id', 'file-list')
.build();
for (var i = 0; i < files.length; i++) {
fileListElement.appendChild(buildFileListItem(i));
}
return fileListElement;
}
this.render = function () {
KB.on('modal.submit', onSubmit);
inputFileElement = buildFileInputElement();
dropzoneElement = buildDropzoneElement();
containerElement.appendChild(inputFileElement);
containerElement.appendChild(dropzoneElement);
};
});

View File

@@ -15,19 +15,19 @@ KB.keyboardShortcuts = function () {
} else if (forms.length > 1) {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
$(document.activeElement).parents("form").submit();
} else if (_KB.get("Popover").isOpen()) {
$("#popover-container form").submit();
} else if (KB.modal.isOpen()) {
KB.modal.getForm().submit();
}
}
}
KB.onKey('?', function () {
_KB.get("Popover").open($("body").data("keyboard-shortcut-url"));
KB.modal.open(KB.find('body').data('keyboardShortcutUrl'));
});
KB.onKey('Escape', function () {
if (! KB.exists('#suggest-menu')) {
_KB.get("Popover").close();
KB.trigger('modal.close');
_KB.get("Dropdown").close();
}
});
@@ -49,25 +49,25 @@ KB.keyboardShortcuts = function () {
});
KB.onKey('n', function () {
_KB.get("Popover").open($("#board").data("task-creation-url"));
KB.modal.open(KB.find('#board').data('taskCreationUrl'), 'large', false);
});
}
if (KB.exists('#task-view')) {
KB.onKey('e', function () {
_KB.get("Popover").open(KB.find('#task-view').data('editUrl'));
KB.modal.open(KB.find('#task-view').data('editUrl'), 'large');
});
KB.onKey('c', function () {
_KB.get("Popover").open(KB.find('#task-view').data('commentUrl'));
KB.modal.open(KB.find('#task-view').data('commentUrl'));
});
KB.onKey('s', function () {
_KB.get("Popover").open(KB.find('#task-view').data('subtaskUrl'));
KB.modal.open(KB.find('#task-view').data('subtaskUrl'));
});
KB.onKey('l', function () {
_KB.get("Popover").open(KB.find('#task-view').data('internalLinkUrl'));
KB.modal.open(KB.find('#task-view').data('internalLinkUrl'));
});
}

View File

@@ -0,0 +1,29 @@
(function () {
function getLink(e) {
if (e.target.tagName === 'I') {
return e.target.parentNode.getAttribute('href');
}
return e.target.getAttribute('href')
}
KB.onClick('.js-modal-large', function (e) {
KB.modal.open(getLink(e), 'large', false);
});
KB.onClick('.js-modal-medium', function (e) {
KB.modal.open(getLink(e), 'medium', false);
});
KB.onClick('.js-modal-small', function (e) {
KB.modal.open(getLink(e), 'small', false);
});
KB.onClick('.js-modal-confirm', function (e) {
KB.modal.open(getLink(e), 'small');
});
KB.onClick('.js-modal-close', function () {
KB.modal.close();
});
}());

View File

@@ -0,0 +1,120 @@
KB.component('screenshot', function (containerElement) {
var pasteCatcher = null;
var inputElement = null;
function onPaste(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);
}
}
function initialize() {
destroy();
if (! window.Clipboard) {
// Insert the content editable at the top to avoid scrolling down in the board view
pasteCatcher = document.createElement('div');
pasteCatcher.id = 'screenshot-pastezone';
pasteCatcher.contentEditable = true;
pasteCatcher.style.opacity = 0;
pasteCatcher.style.position = 'fixed';
pasteCatcher.style.top = 0;
pasteCatcher.style.right = 0;
pasteCatcher.style.width = 0;
document.body.insertBefore(pasteCatcher, document.body.firstChild);
pasteCatcher.focus();
// Set the focus when clicked anywhere in the document
document.addEventListener('click', setFocus);
// Set the focus when clicked in screenshot dropzone
document.getElementById('screenshot-zone').addEventListener('click', setFocus);
}
window.addEventListener('paste', onPaste, false);
}
function destroy() {
if (pasteCatcher !== null) {
document.body.removeChild(pasteCatcher);
} else if (document.getElementById('screenshot-pastezone')) {
document.body.removeChild(document.getElementById('screenshot-pastezone'));
}
document.removeEventListener('click', setFocus);
pasteCatcher = null;
}
function setFocus() {
if (pasteCatcher !== null) {
pasteCatcher.focus();
}
}
function checkInput() {
var child = 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') {
createImage(child.src);
}
}
pasteCatcher.innerHTML = '';
}
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,');
inputElement.value = sourceSplit[1];
};
var zone = document.getElementById('screenshot-zone');
zone.innerHTML = '';
zone.className = 'screenshot-pasted';
zone.appendChild(pastedImage);
destroy();
initialize();
}
this.render = function () {
inputElement = KB.dom('input')
.attr('type', 'hidden')
.attr('name', 'screenshot')
.build();
containerElement.appendChild(inputElement);
initialize();
};
});

View File

@@ -1,5 +1,24 @@
KB.component('select-dropdown-autocomplete', function(containerElement, options) {
var componentElement, inputElement, inputHiddenElement;
var componentElement, inputElement, inputHiddenElement, chevronIconElement, loadingIconElement;
function onLoadingStart() {
KB.dom(loadingIconElement).show();
KB.dom(chevronIconElement).hide();
}
function onLoadingStop() {
KB.dom(loadingIconElement).hide();
KB.dom(chevronIconElement).show();
}
function onScroll() {
var menuElement = KB.find('#select-dropdown-menu');
if (menuElement) {
var componentPosition = componentElement.getBoundingClientRect();
menuElement.style('top', (document.body.scrollTop + componentPosition.bottom) + 'px');
}
}
function onKeyDown(e) {
switch (KB.utils.getKey(e)) {
@@ -66,8 +85,10 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
destroyDropdownMenu();
if (options.redirect) {
var regex = new RegExp(options.redirect.regex, 'g');
window.location = options.redirect.url.replace(regex, value);
window.location = options.redirect.url.replace(new RegExp(options.redirect.regex, 'g'), value);
} else if (options.replace) {
onLoadingStart();
KB.modal.replace(options.replace.url.replace(new RegExp(options.replace.regex, 'g'), value));
}
}
@@ -158,7 +179,7 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
return KB.dom('ul')
.attr('id', 'select-dropdown-menu')
.style('top', componentPosition.bottom + 'px')
.style('top', (document.body.scrollTop + componentPosition.bottom) + 'px')
.style('left', componentPosition.left + 'px')
.style('width', componentPosition.width + 'px')
.style('maxHeight', (window.innerHeight - componentPosition.bottom - 20) + 'px')
@@ -203,11 +224,20 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
}
this.render = function () {
var dropdownIconElement = KB.dom('i')
KB.on('select.dropdown.loading.start', onLoadingStart);
KB.on('select.dropdown.loading.stop', onLoadingStop);
chevronIconElement = KB.dom('i')
.attr('class', 'fa fa-chevron-down select-dropdown-chevron')
.click(toggleDropdownMenu)
.build();
loadingIconElement = KB.dom('span')
.hide()
.addClass('select-loading-icon')
.add(KB.dom('i').attr('class', 'fa fa-spinner fa-pulse').build())
.build();
inputHiddenElement = KB.dom('input')
.attr('type', 'hidden')
.attr('name', options.name)
@@ -227,7 +257,8 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
.addClass('select-dropdown-input-container')
.add(inputHiddenElement)
.add(inputElement)
.add(dropdownIconElement)
.add(chevronIconElement)
.add(loadingIconElement)
.build();
containerElement.appendChild(componentElement);
@@ -237,5 +268,7 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
KB.on(eventName, function() { inputElement.focus(); });
});
}
window.addEventListener('scroll', onScroll, false);
};
});

View File

@@ -0,0 +1,94 @@
KB.component('submit-buttons', function (containerElement, options) {
var isLoading = false;
var isDisabled = options.disabled || false;
var submitLabel = options.submitLabel;
var formActionElement = null;
function onSubmit() {
isLoading = true;
KB.find('#modal-submit-button').replace(buildButton());
KB.trigger('modal.submit');
}
function onCancel() {
KB.trigger('modal.close');
}
function onStop() {
isLoading = false;
KB.find('#modal-submit-button').replace(buildButton());
}
function onDisable() {
isLoading = false;
isDisabled = true;
KB.find('#modal-submit-button').replace(buildButton());
}
function onEnable() {
isLoading = false;
isDisabled = false;
KB.find('#modal-submit-button').replace(buildButton());
}
function onHide() {
KB.dom(formActionElement).hide();
}
function onUpdateSubmitLabel(eventData) {
submitLabel = eventData.submitLabel;
KB.find('#modal-submit-button').replace(buildButton());
}
function buildButton() {
var button = KB.dom('button')
.attr('id', 'modal-submit-button')
.attr('type', 'submit')
.attr('class', 'btn btn-' + (options.color || 'blue'));
if (KB.modal.isOpen()) {
button.click(onSubmit);
}
if (options.tabindex) {
button.attr('tabindex', options.tabindex);
}
if (isLoading) {
button
.disable()
.add(KB.dom('i').attr('class', 'fa fa-spinner fa-pulse').build())
.text(' ')
;
}
if (isDisabled) {
button.disable();
}
return button
.text(submitLabel)
.build();
}
this.render = function () {
KB.on('modal.stop', onStop);
KB.on('modal.disable', onDisable);
KB.on('modal.enable', onEnable);
KB.on('modal.hide', onHide);
KB.on('modal.submit.label', onUpdateSubmitLabel);
var formActionElementBuilder = KB.dom('div')
.attr('class', 'form-actions')
.add(buildButton());
if (KB.modal.isOpen()) {
formActionElementBuilder
.text(' ' + options.orLabel + ' ')
.add(KB.dom('a').attr('href', '#').click(onCancel).text(options.cancelLabel).build())
}
formActionElement = formActionElementBuilder.build();
containerElement.appendChild(formActionElement);
};
});

View File

@@ -62,7 +62,7 @@ KB.component('suggest-menu', function(containerElement, options) {
}
function getParentElement() {
var selectors = ['.popover-form', '#popover-content', 'body'];
var selectors = ['#modal-content form', '#modal-content', 'body'];
for (var i = 0; i < selectors.length; i++) {
var element = document.querySelector(selectors[i]);

View File

@@ -74,8 +74,6 @@ KB.component('task-move-position', function (containerElement, options) {
"column_id": getColumnId(),
"swimlane_id": getSwimlaneId(),
"position": position
}).success(function () {
window.location.reload(true);
}).error(function (response) {
if (response) {
onError(response.message);
@@ -150,7 +148,6 @@ KB.component('task-move-position', function (containerElement, options) {
KB.on('modal.submit', onSubmit);
var form = KB.dom('div')
.on('submit', onSubmit)
.add(KB.dom('div').attr('id', 'message-container').build())
.add(KB.html.label(options.swimlaneLabel, 'form-swimlanes'))
.add(buildSwimlaneSelect())

View File

@@ -22,9 +22,7 @@ KB.on = function (eventType, callback) {
KB.trigger = function (eventType, eventData) {
if (this.listeners.internals.hasOwnProperty(eventType)) {
for (var i = 0; i < this.listeners.internals[eventType].length; i++) {
if (! this.listeners.internals[eventType][i](eventData)) {
break;
}
this.listeners.internals[eventType][i](eventData);
}
}
};

View File

@@ -88,6 +88,11 @@ KB.dom = function (tag) {
return this;
};
this.replaceText = function (text) {
element.textContent = text;
return this;
};
this.addClass = function (className) {
element.classList.add(className);
return this;
@@ -122,6 +127,13 @@ KB.dom = function (tag) {
return this;
};
this.empty = function () {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
return this;
};
this.parent = function (selector) {
for (; element && element !== document; element = element.parentNode) {
if (element.matches(selector)) {

View File

@@ -3,11 +3,21 @@ KB.http.request = function (method, url, headers, body) {
var errorCallback = function() {};
function parseResponse(request) {
try {
return JSON.parse(request.responseText);
} catch (e) {
return request.responseText;
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 if (request.getResponseHeader('Content-Type') === 'application/json') {
try {
return JSON.parse(request.responseText);
} catch (e) {}
}
return request.responseText;
}
this.execute = function () {
@@ -64,3 +74,20 @@ KB.http.postJson = function (url, body) {
return (new KB.http.request('POST', url, headers, JSON.stringify(body))).execute();
};
KB.http.postForm = function (url, formElement) {
var formData = new FormData(formElement);
return (new KB.http.request('POST', url, {}, formData)).execute();
};
KB.http.uploadFile = function (url, file, onProgress, onComplete, onError) {
var fd = new FormData();
fd.append('files[]', file);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', onProgress);
xhr.upload.addEventListener('load', onComplete);
xhr.upload.addEventListener('error', onError);
xhr.open('POST', url, true);
xhr.send(fd);
};

172
assets/js/core/modal.js Normal file
View File

@@ -0,0 +1,172 @@
(function () {
var isOpen = false;
function onOverlayClick(e) {
if (e.target.matches('#modal-overlay')) {
e.stopPropagation();
e.preventDefault();
destroy();
}
}
function onCloseButtonClick() {
destroy();
}
function onFormSubmit() {
KB.trigger('modal.loading');
submitForm();
}
function getForm() {
return document.querySelector('#modal-content form');
}
function submitForm() {
var form = getForm();
if (form) {
var url = form.getAttribute('action');
if (url) {
KB.http.postForm(url, form).success(function (response) {
KB.trigger('modal.stop');
if (response) {
replace(response);
} else {
destroy();
}
});
}
}
}
function afterRendering() {
var formElement = KB.find('#modal-content form');
if (formElement) {
formElement.on('submit', onFormSubmit, false);
}
var autoFocusElement = document.querySelector('#modal-content input[autofocus]');
if (autoFocusElement) {
autoFocusElement.focus();
}
KB.render();
_KB.datePicker();
_KB.autoComplete();
_KB.tagAutoComplete();
_KB.get('Task').onPopoverOpened();
}
function replace(html) {
var contentElement = KB.find('#modal-content');
if (contentElement) {
contentElement.replace(KB.dom('div')
.attr('id', 'modal-content')
.html(html)
.build()
);
afterRendering();
}
}
function create(html, width, overlayClickDestroy) {
var closeButtonElement = KB.dom('a')
.attr('href', '#')
.attr('id', 'modal-close-button')
.html('<i class="fa fa-times"></i>')
.click(onCloseButtonClick)
.build();
var headerElement = KB.dom('div')
.attr('id', 'modal-header')
.add(closeButtonElement)
.build();
var contentElement = KB.dom('div')
.attr('id', 'modal-content')
.html(html)
.build();
var boxElement = KB.dom('div')
.attr('id', 'modal-box')
.style('width', width)
.add(headerElement)
.add(contentElement)
.build();
var overlayElement = KB.dom('div')
.attr('id', 'modal-overlay')
.add(boxElement)
.build();
if (overlayClickDestroy) {
overlayElement.addEventListener('click', onOverlayClick, false);
}
document.body.appendChild(overlayElement);
afterRendering();
}
function destroy() {
var overlayElement = KB.find('#modal-overlay');
if (overlayElement) {
overlayElement.remove();
}
}
function getWidth(size) {
var viewport = KB.utils.getViewportSize();
switch (size) {
case 'large':
return viewport.width < 1300 ? '95%' : '1300px';
case 'medium':
return viewport.width < 1024 ? '70%' : '1024px';
}
return viewport.width < 800 ? '75%' : '800px';
}
KB.on('modal.close', function () {
destroy();
});
KB.on('modal.submit', function () {
submitForm();
});
KB.modal = {
open: function (url, size, overlayClickDestroy) {
_KB.get('Dropdown').close();
destroy();
if (typeof overlayClickDestroy === 'undefined') {
overlayClickDestroy = true;
}
KB.http.get(url).success(function (response) {
isOpen = true;
create(response, getWidth(size), overlayClickDestroy);
});
},
close: function () {
isOpen = false;
destroy();
},
isOpen: function () {
return isOpen;
},
replace: function (url) {
KB.http.get(url).success(function (response) {
replace(response);
});
},
getForm: getForm
};
}());

View File

@@ -87,3 +87,10 @@ KB.utils.getKey = function (e) {
return e.key;
};
KB.utils.getViewportSize = function () {
return {
width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
};
};

View File

@@ -2,6 +2,7 @@ Kanboard.BoardTask = function(app) {
this.app = app;
};
// TODO: rewrite this code
Kanboard.BoardTask.prototype.listen = function() {
var self = this;

View File

@@ -2,6 +2,7 @@ Kanboard.Dropdown = function(app) {
this.app = app;
};
// TODO: rewrite this code
Kanboard.Dropdown.prototype.listen = function() {
var self = this;
@@ -9,10 +10,6 @@ Kanboard.Dropdown.prototype.listen = function() {
self.close();
});
$(document).on('click', '#popover-content', function() {
self.close();
});
$(document).on('click', '.dropdown-menu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
@@ -56,7 +53,3 @@ Kanboard.Dropdown.prototype.listen = function() {
Kanboard.Dropdown.prototype.close = function() {
$("#dropdown").remove();
};
Kanboard.Dropdown.prototype.onPopoverOpened = function() {
this.close();
};

View File

@@ -1,125 +0,0 @@
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();
}
};

View File

@@ -1,161 +0,0 @@
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-close-button", 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">' +
'<div id="popover-content-header"><a href="#" id="popover-close-button"><i class="fa fa-times"></i></a></div>' +
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)
$("#popover-content input[autofocus]").each(function() {
$(this).focus();
});
this.app.datePicker();
this.app.autoComplete();
this.app.tagAutoComplete();
KB.render();
};

View File

@@ -1,134 +0,0 @@
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();
};

View File

@@ -15,6 +15,7 @@ Kanboard.Search.prototype.focus = function() {
});
};
// TODO: rewrite this code
Kanboard.Search.prototype.listen = function() {
$(document).on("click", ".filter-helper", function (e) {
e.preventDefault();

View File

@@ -2,9 +2,9 @@ Kanboard.Task = function(app) {
this.app = app;
};
// TODO: rewrite this code
Kanboard.Task.prototype.onPopoverOpened = function() {
var self = this;
var reloadingProjectId = 0;
self.renderColorPicker();
@@ -19,29 +19,6 @@ Kanboard.Task.prototype.onPopoverOpened = function() {
$(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.Task.prototype.renderColorPicker = function() {

View File

@@ -65,11 +65,10 @@ td.board-column-task-collapsed
.board-add-icon
float: left
padding: 0 5px
a
i
text-decoration: none
color: link-color('primary')
font-size: size('xlarge')
line-height: 70%
font-size: size('large')
&:focus, &:hover
text-decoration: none
color: red

View File

@@ -26,6 +26,6 @@
padding-bottom: 8px
border-bottom: 1px dotted #ddd
width: 95%
&.file-error
.file-error
font-weight: bold
color: color('error')

View File

@@ -1,13 +1,19 @@
@import variables
@import mixins
form
margin-bottom: 20px
fieldset
border: 1px solid #ccc
margin-top: 20px
legend
font-weight: 500
font-size: size('medium')
label
cursor: pointer
display: block
margin-top: 10px
font-weight: 400
input
&[type="number"], &[type="date"], &[type="email"], &[type="password"], &[type="text"]:not(.input-addon-field)
@@ -19,7 +25,6 @@ input
height: 25px
padding-bottom: 0
font-family: sans-serif
margin-top: 10px
+appearance
@include placeholder
color: color('lighter')

View File

@@ -10,3 +10,6 @@ a
&:hover
color: link-color('hover')
text-decoration: none
.fa
text-decoration: none
color: color('primary')

View File

@@ -1,6 +1,7 @@
@import variables
.text-editor
margin-top: 10px
a
font-size: size('normal')
color: color('light')
@@ -10,7 +11,12 @@
color: link-color('primary')
.text-editor-preview-area
border: 1px solid color('lighter')
width: 400px
height: 200px
width: 700px
max-width: 99%
height: 250px
overflow: auto
padding: 2px
textarea
width: 700px
max-width: 98%
height: 250px

View File

@@ -1,7 +1,7 @@
@import variables
@import mixins
#popover-container
#modal-overlay
position: fixed
top: 0
left: 0
@@ -11,25 +11,24 @@
overflow: auto
z-index: 100
#popover-content
#modal-box
position: fixed
width: 950px
max-width: 95%
max-height: calc(100% - 50px)
top: 5%
top: 2%
left: 50%
transform: translateX(-50%)
padding: 0 15px 15px
background: #fff
overflow: auto
border-radius: 5px
#popover-content-header
#modal-content
padding: 0 5px 5px
#modal-header
text-align: right
padding-right: 5px
#popover-close-button
#modal-close-button
color: color('primary')
&:hover
color: color('error')
.popover-form
margin-bottom: 0

View File

@@ -45,3 +45,8 @@
top: 4px
right: 5px
cursor: pointer
.select-loading-icon
color: color('medium')
position: absolute
top: 4px
right: 5px

View File

@@ -0,0 +1,32 @@
@import variables
.task-form-container
@include grid(100)
.task-form-main-column
@include grid_width(60/100)
@include custom-device(1000px)
@include grid_width(1)
input[type="text"]
width: 700px
max-width: 99%
.task-form-secondary-column
max-width: 250px
min-width: 200px
max-height: 600px
padding-left: 10px
overflow: auto
@include grid_width(20/100)
@include custom-device(1000px)
@include grid_width(1)
max-width: 99%
max-height: none
label:first-child
margin-top: 0
@include custom-device(1000px)
margin-top: 10px
.task-form-bottom
@include grid_width(1)

View File

@@ -15,7 +15,7 @@
@import select_dropdown
@import suggest_menu
@import dialog_box
@import popover
@import modal
@import pagination
@import header
@import logo
@@ -41,6 +41,7 @@
@import task_board_date
@import task_tags
@import task_summary
@import task_form
@import task_listing
@import comment
@import subtasks