Rewrite board drag and drop with jquery (touch devices, IE, auto-update)
This commit is contained in:
parent
09da5720e8
commit
5e4b40665f
|
|
@ -29,7 +29,7 @@ body {
|
|||
/* links */
|
||||
a {
|
||||
color: #3366CC;
|
||||
border: 1px solid rgba(255, 255, 255, 0);
|
||||
border: none;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
|
|
@ -123,10 +123,6 @@ label {
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
input[type="number"],
|
||||
input[type="date"],
|
||||
input[type="email"],
|
||||
|
|
@ -574,17 +570,15 @@ td div.over {
|
|||
}
|
||||
|
||||
.draggable-item {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
[draggable] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
[draggable=true]:hover {
|
||||
box-shadow: 0 0 3px #333;
|
||||
.draggable-placeholder {
|
||||
border: 2px dashed #000;
|
||||
background: #fafafa;
|
||||
height: 70px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
tr td.task a,
|
||||
|
|
@ -607,6 +601,8 @@ div.task-title a {
|
|||
|
||||
div.task {
|
||||
position: relative;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-score {
|
||||
|
|
|
|||
|
|
@ -1,259 +1,102 @@
|
|||
(function () {
|
||||
|
||||
function handleItemDragStart(e)
|
||||
var checkInterval = null;
|
||||
|
||||
// Setup the board
|
||||
function board_load_events()
|
||||
{
|
||||
this.style.opacity = '0.4';
|
||||
|
||||
dragSrcItem = this;
|
||||
dragSrcColumn = this.parentNode;
|
||||
|
||||
e.dataTransfer.effectAllowed = 'copy';
|
||||
e.dataTransfer.setData('text/plain', this.innerHTML);
|
||||
}
|
||||
|
||||
function handleItemDragEnd(e)
|
||||
{
|
||||
// Restore styles
|
||||
removeOver();
|
||||
this.style.opacity = '1.0';
|
||||
|
||||
dragSrcColumn = null;
|
||||
dragSrcItem = null;
|
||||
}
|
||||
|
||||
function handleItemDragOver(e)
|
||||
{
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleItemDragEnter(e)
|
||||
{
|
||||
if (dragSrcItem != this) {
|
||||
removeOver();
|
||||
this.classList.add('over');
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemDrop(e)
|
||||
{
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
if (e.stopPropagation) e.stopPropagation();
|
||||
|
||||
// Drop the element if the item is not the same
|
||||
if (dragSrcItem != this) {
|
||||
|
||||
var position = getItemPosition(this);
|
||||
var item = createItem(e.dataTransfer.getData('text/plain'));
|
||||
|
||||
if (countColumnItems(this.parentNode) == position) {
|
||||
this.parentNode.appendChild(item);
|
||||
}
|
||||
else {
|
||||
this.parentNode.insertBefore(item, this);
|
||||
$(".column").sortable({
|
||||
connectWith: ".column",
|
||||
placeholder: "draggable-placeholder",
|
||||
stop: function(event, ui) {
|
||||
board_save();
|
||||
}
|
||||
});
|
||||
|
||||
dragSrcItem.parentNode.removeChild(dragSrcItem);
|
||||
var interval = parseInt($("#board").attr("data-check-interval"));
|
||||
|
||||
saveBoard();
|
||||
}
|
||||
|
||||
dragSrcColumn = null;
|
||||
dragSrcItem = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function handleColumnDragOver(e)
|
||||
{
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleColumnDragEnter(e)
|
||||
{
|
||||
if (dragSrcColumn != this) {
|
||||
removeOver();
|
||||
this.classList.add('over');
|
||||
if (interval > 0) {
|
||||
checkInterval = window.setInterval(board_check, interval * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleColumnDrop(e)
|
||||
// Stop events
|
||||
function board_unload_events()
|
||||
{
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
if (e.stopPropagation) e.stopPropagation();
|
||||
|
||||
// Drop the element if the column is not the same
|
||||
if (dragSrcColumn != this) {
|
||||
|
||||
var item = createItem(e.dataTransfer.getData('text/plain'));
|
||||
this.appendChild(item);
|
||||
dragSrcColumn.removeChild(dragSrcItem);
|
||||
|
||||
saveBoard();
|
||||
}
|
||||
|
||||
return false;
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
|
||||
function saveBoard()
|
||||
// Save and refresh the board
|
||||
function board_save()
|
||||
{
|
||||
var data = [];
|
||||
var projectId = document.getElementById("board").getAttribute("data-project-id");
|
||||
var cols = document.querySelectorAll('.column');
|
||||
var projectId = $("#board").attr("data-project-id");
|
||||
|
||||
[].forEach.call(cols, function(col) {
|
||||
board_unload_events();
|
||||
|
||||
var task_limit = col.getAttribute("data-task-limit");
|
||||
|
||||
if (task_limit != "" && task_limit != "0") {
|
||||
|
||||
task_limit = parseInt(task_limit);
|
||||
|
||||
if (col.children.length > task_limit) {
|
||||
col.classList.add("task-limit-warning");
|
||||
}
|
||||
else {
|
||||
col.classList.remove("task-limit-warning");
|
||||
}
|
||||
|
||||
var counter = document.getElementById("task-number-column-" + col.getAttribute("data-column-id"));
|
||||
if (counter) counter.innerHTML = col.children.length;
|
||||
}
|
||||
|
||||
[].forEach.call(col.children, function(item) {
|
||||
$(".column").each(function() {
|
||||
var columnId = $(this).attr("data-column-id");
|
||||
|
||||
$("#column-" + columnId + " .task").each(function(index) {
|
||||
data.push({
|
||||
"task_id": item.firstElementChild.getAttribute("data-task-id"),
|
||||
"position": getItemPosition(item),
|
||||
"column_id": col.getAttribute("data-column-id")
|
||||
})
|
||||
"task_id": parseInt($(this).attr("data-task-id")),
|
||||
"position": index + 1,
|
||||
"column_id": parseInt(columnId)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "?controller=board&action=save&project_id=" + projectId, true);
|
||||
$.ajax({
|
||||
url: "?controller=board&action=save&project_id=" + projectId,
|
||||
data: {positions: data},
|
||||
type: "POST",
|
||||
success: function(data) {
|
||||
$("#board").remove();
|
||||
$("#main").append(data);
|
||||
board_load_events();
|
||||
applyFilter(getSelectedUserFilter(), hasDueDateFilter());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function(response) {
|
||||
// Check if a board have been changed by someone else
|
||||
function board_check()
|
||||
{
|
||||
var projectId = $("#board").attr("data-project-id");
|
||||
var timestamp = $("#board").attr("data-time");
|
||||
|
||||
if (this.readyState == this.DONE) {
|
||||
try {
|
||||
var response = JSON.parse(this.responseText);
|
||||
|
||||
if (response.result == false) {
|
||||
window.alert('Unable to update the board');
|
||||
}
|
||||
else if (response.refresh == true) {
|
||||
window.location = "?controller=board&action=show&project_id=" + projectId;
|
||||
if (projectId != undefined && timestamp != undefined) {
|
||||
$.ajax({
|
||||
url: "?controller=board&action=check&project_id=" + projectId + "×tamp=" + timestamp,
|
||||
statusCode: {
|
||||
200: function(data) {
|
||||
$("#board").remove();
|
||||
$("#main").append(data);
|
||||
board_unload_events();
|
||||
board_load_events();
|
||||
applyFilter(getSelectedUserFilter(), hasDueDateFilter());
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function getItemPosition(element)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
while ((element = element.previousSibling) != null) {
|
||||
|
||||
if (element.nodeName == "DIV" && element.className == "draggable-item") {
|
||||
i++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
function countColumnItems(element)
|
||||
{
|
||||
return element.children.length;
|
||||
}
|
||||
|
||||
function createItem(html)
|
||||
{
|
||||
var item = document.createElement("div");
|
||||
item.className = "draggable-item";
|
||||
item.draggable = true;
|
||||
item.innerHTML = html;
|
||||
item.ondragstart = handleItemDragStart;
|
||||
item.ondragend = handleItemDragEnd;
|
||||
item.ondragenter = handleItemDragEnter;
|
||||
item.ondragover = handleItemDragOver;
|
||||
item.ondrop = handleItemDrop;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function removeOver()
|
||||
{
|
||||
// Remove column over
|
||||
[].forEach.call(document.querySelectorAll('.column'), function (col) {
|
||||
col.classList.remove('over');
|
||||
});
|
||||
|
||||
// Remove item over
|
||||
[].forEach.call(document.querySelectorAll('.draggable-item'), function (item) {
|
||||
item.classList.remove('over');
|
||||
});
|
||||
}
|
||||
|
||||
// Drag and drop events
|
||||
|
||||
var dragSrcItem = null;
|
||||
var dragSrcColumn = null;
|
||||
|
||||
var items = document.querySelectorAll('.draggable-item');
|
||||
|
||||
[].forEach.call(items, function(item) {
|
||||
item.addEventListener('dragstart', handleItemDragStart, false);
|
||||
item.addEventListener('dragend', handleItemDragEnd, false);
|
||||
item.addEventListener('dragenter', handleItemDragEnter, false);
|
||||
item.addEventListener('dragover', handleItemDragOver, false);
|
||||
item.addEventListener('drop', handleItemDrop, false);
|
||||
});
|
||||
|
||||
var cols = document.querySelectorAll('.column');
|
||||
|
||||
[].forEach.call(cols, function(col) {
|
||||
col.addEventListener('dragenter', handleColumnDragEnter, false);
|
||||
col.addEventListener('dragover', handleColumnDragOver, false);
|
||||
col.addEventListener('drop', handleColumnDrop, false);
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('[data-task-id]'), function (item) {
|
||||
item.addEventListener('click', function() {
|
||||
window.location.href = '?controller=task&action=show&task_id=' + item.getAttribute('data-task-id');
|
||||
});
|
||||
});
|
||||
|
||||
// Filtering
|
||||
|
||||
// Get the selected user id
|
||||
function getSelectedUserFilter()
|
||||
{
|
||||
var select = document.getElementById("form-user_id");
|
||||
return select.options[select.selectedIndex].value;
|
||||
return $("#form-user_id").val();
|
||||
}
|
||||
|
||||
// Return true if the filter is activated
|
||||
function hasDueDateFilter()
|
||||
{
|
||||
var dateFilter = document.getElementById("filter-due-date");
|
||||
return dateFilter.classList.contains("filter-on");
|
||||
return $("#filter-due-date").hasClass("filter-on");
|
||||
}
|
||||
|
||||
// Apply user or date filter (change tasks opacity)
|
||||
function applyFilter(selectedUserId, filterDueDate)
|
||||
{
|
||||
[].forEach.call(document.querySelectorAll('[data-task-id]'), function (item) {
|
||||
$("[data-task-id]").each(function(index, item) {
|
||||
|
||||
var ownerId = item.getAttribute("data-owner-id");
|
||||
var dueDate = item.getAttribute("data-due-date");
|
||||
|
|
@ -271,22 +114,24 @@
|
|||
});
|
||||
}
|
||||
|
||||
var userFilter = document.getElementById("form-user_id");
|
||||
var dateFilter = document.getElementById("filter-due-date");
|
||||
|
||||
if (userFilter) {
|
||||
userFilter.onchange = function() {
|
||||
// Load filter events
|
||||
function filter_load_events()
|
||||
{
|
||||
$("#form-user_id").change(function() {
|
||||
applyFilter(getSelectedUserFilter(), hasDueDateFilter());
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (dateFilter) {
|
||||
|
||||
dateFilter.onclick = function(e) {
|
||||
dateFilter.classList.toggle("filter-on");
|
||||
$("#filter-due-date").click(function(e) {
|
||||
$(this).toggleClass("filter-on");
|
||||
applyFilter(getSelectedUserFilter(), hasDueDateFilter());
|
||||
e.preventDefault();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Initialization
|
||||
$(function() {
|
||||
board_load_events();
|
||||
filter_load_events();
|
||||
});
|
||||
|
||||
}());
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
||||
|
|
@ -6,7 +6,7 @@ require __DIR__.'/core/translator.php';
|
|||
|
||||
$registry = new Core\Registry;
|
||||
|
||||
$registry->db_version = 14;
|
||||
$registry->db_version = 15;
|
||||
|
||||
$registry->db = function() use ($registry) {
|
||||
require __DIR__.'/vendor/PicoDb/Database.php';
|
||||
|
|
@ -112,8 +112,11 @@ $registry->google = function() use ($registry) {
|
|||
|
||||
if (file_exists('config.php')) require 'config.php';
|
||||
|
||||
// Auto-refresh frequency in seconds for the public board view
|
||||
defined('AUTO_REFRESH_DURATION') or define('AUTO_REFRESH_DURATION', 60);
|
||||
// Board refresh frequency in seconds for the public board view
|
||||
defined('BOARD_PUBLIC_CHECK_INTERVAL') or define('BOARD_PUBLIC_CHECK_INTERVAL', 60);
|
||||
|
||||
// Board refresh frequency in seconds (the value 0 disable this feature)
|
||||
defined('BOARD_CHECK_INTERVAL') or define('BOARD_CHECK_INTERVAL', 10);
|
||||
|
||||
// Custom session save path
|
||||
defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', '');
|
||||
|
|
|
|||
|
|
@ -212,8 +212,9 @@ abstract class Base
|
|||
$this->response->redirect('?controller=user&action=forbidden');
|
||||
}
|
||||
|
||||
// Attach events for automatic actions
|
||||
// Attach events
|
||||
$this->action->attachEvents();
|
||||
$this->project->attachEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -174,20 +174,13 @@ class Board extends Base
|
|||
$this->notfound();
|
||||
}
|
||||
|
||||
$filters = array();
|
||||
$users = $this->project->getUsersList($project_id, true, true);
|
||||
|
||||
if ($user_id !== \Model\User::EVERYBODY_ID && in_array($user_id, array_keys($users))) {
|
||||
$filters[] = array('column' => 'owner_id', 'operator' => 'eq', 'value' => $user_id);
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('board_index', array(
|
||||
'users' => $users,
|
||||
'users' => $this->project->getUsersList($project_id, true, true),
|
||||
'filters' => array('user_id' => $user_id),
|
||||
'projects' => $projects,
|
||||
'current_project_id' => $project_id,
|
||||
'current_project_name' => $projects[$project_id],
|
||||
'columns' => $this->board->get($project_id, $filters),
|
||||
'board' => $this->board->get($project_id),
|
||||
'menu' => 'boards',
|
||||
'title' => $projects[$project_id]
|
||||
)));
|
||||
|
|
@ -351,14 +344,43 @@ class Board extends Base
|
|||
public function save()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->json(array('result' => false), 401);
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
$this->response->json(array(
|
||||
'result' => $this->board->saveTasksPosition($this->request->getValues()),
|
||||
'refresh' => $this->event->getLastListenerExecuted() !== ''
|
||||
));
|
||||
if (isset($values['positions'])) {
|
||||
$this->board->saveTasksPosition($values['positions']);
|
||||
}
|
||||
|
||||
$this->response->html(
|
||||
$this->template->load('board_show', array('current_project_id' => $project_id, 'board' => $this->board->get($project_id))),
|
||||
201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board have been changed
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$timestamp = $this->request->getIntegerParam('timestamp');
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
if ($this->project->isModifiedSince($project_id, $timestamp)) {
|
||||
$this->response->html(
|
||||
$this->template->load('board_show', array('current_project_id' => $project_id, 'board' => $this->board->get($project_id)))
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->response->status(304);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,8 @@ class Response
|
|||
*/
|
||||
public function status($status_code)
|
||||
{
|
||||
if (strpos(php_sapi_name(), 'apache') !== false) {
|
||||
header('HTTP/1.0 '.$status_code);
|
||||
}
|
||||
else {
|
||||
header('Status: '.$status_code);
|
||||
}
|
||||
header('Status: '.$status_code);
|
||||
header($_SERVER['SERVER_PROTOCOL'].' '.$status_code);
|
||||
}
|
||||
|
||||
public function redirect($url)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Event;
|
||||
|
||||
use \Core\Listener;
|
||||
use \Model\Project;
|
||||
|
||||
/**
|
||||
* Task modification listener
|
||||
*
|
||||
* @package events
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskModification implements Listener
|
||||
{
|
||||
/**
|
||||
* Project model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Project
|
||||
*/
|
||||
private $project;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Model\Project $project Project model instance
|
||||
*/
|
||||
public function __construct(Project $project)
|
||||
{
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function execute(array $data)
|
||||
{
|
||||
if (isset($data['project_id'])) {
|
||||
$this->project->updateModificationDate($data['project_id']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -32,8 +32,8 @@ class Acl extends Base
|
|||
*/
|
||||
private $user_actions = array(
|
||||
'app' => array('index'),
|
||||
'board' => array('index', 'show', 'assign', 'assigntask', 'save'),
|
||||
'project' => array('tasks', 'index', 'forbidden'),
|
||||
'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'),
|
||||
'project' => array('tasks', 'index', 'forbidden', 'search'),
|
||||
'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'description', 'duplicate'),
|
||||
'comment' => array('save', 'confirm', 'remove', 'update', 'edit'),
|
||||
'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle'),
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ require_once __DIR__.'/base.php';
|
|||
require_once __DIR__.'/acl.php';
|
||||
require_once __DIR__.'/board.php';
|
||||
require_once __DIR__.'/task.php';
|
||||
require_once __DIR__.'/../events/task_modification.php';
|
||||
|
||||
use \SimpleValidator\Validator;
|
||||
use \SimpleValidator\Validators;
|
||||
use \Event\TaskModification;
|
||||
|
||||
/**
|
||||
* Project model
|
||||
|
|
@ -389,6 +391,36 @@ class Project extends Base
|
|||
return (int) $project_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the project have been modified
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $timestamp Timestamp
|
||||
* @return bool
|
||||
*/
|
||||
public function isModifiedSince($project_id, $timestamp)
|
||||
{
|
||||
return (bool) $this->db->table(self::TABLE)
|
||||
->eq('id', $project_id)
|
||||
->gt('last_modified', $timestamp)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification date
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return bool
|
||||
*/
|
||||
public function updateModificationDate($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $project_id)->save(array(
|
||||
'last_modified' => time()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a project
|
||||
*
|
||||
|
|
@ -508,4 +540,25 @@ class Project extends Base
|
|||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach events
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachEvents()
|
||||
{
|
||||
$events = array(
|
||||
Task::EVENT_UPDATE,
|
||||
Task::EVENT_CREATE,
|
||||
Task::EVENT_CLOSE,
|
||||
Task::EVENT_OPEN,
|
||||
);
|
||||
|
||||
$listener = new TaskModification($this);
|
||||
|
||||
foreach ($events as $event_name) {
|
||||
$this->event->attach($event_name, $listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
namespace Schema;
|
||||
|
||||
function version_15($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INT DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_14($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN name VARCHAR(255)");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
namespace Schema;
|
||||
|
||||
function version_15($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_14($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN name TEXT");
|
||||
|
|
|
|||
|
|
@ -27,88 +27,10 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (empty($columns)): ?>
|
||||
<?php if (empty($board)): ?>
|
||||
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
|
||||
<?php else: ?>
|
||||
|
||||
<table id="board" data-project-id="<?= $current_project_id ?>">
|
||||
<tr>
|
||||
<?php $column_with = round(100 / count($columns), 2); ?>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<th width="<?= $column_with ?>%">
|
||||
<a href="?controller=task&action=create&project_id=<?= $column['project_id'] ?>&column_id=<?= $column['id'] ?>" title="<?= t('Add a new task') ?>">+</a>
|
||||
<?= Helper\escape($column['title']) ?>
|
||||
<?php if ($column['task_limit']): ?>
|
||||
<span title="<?= t('Task limit') ?>" class="task-limit">
|
||||
(
|
||||
<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>
|
||||
/
|
||||
<?= Helper\escape($column['task_limit']) ?>
|
||||
)
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<td
|
||||
id="column-<?= $column['id'] ?>"
|
||||
class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>"
|
||||
data-column-id="<?= $column['id'] ?>"
|
||||
data-task-limit="<?= $column['task_limit'] ?>"
|
||||
dropzone="copy">
|
||||
<?php foreach ($column['tasks'] as $task): ?>
|
||||
<div class="draggable-item" draggable="true">
|
||||
<div class="task task-<?= $task['color_id'] ?>"
|
||||
data-task-id="<?= $task['id'] ?>"
|
||||
data-owner-id="<?= $task['owner_id'] ?>"
|
||||
data-due-date="<?= $task['date_due'] ?>"
|
||||
title="<?= t('View this task') ?>">
|
||||
|
||||
<a href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> -
|
||||
|
||||
<span class="task-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-nobody"><?= t('Nobody assigned') ?></a>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-title">
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>" title="<?= t('View this task') ?>"><?= Helper\escape($task['title']) ?></a>
|
||||
</div>
|
||||
|
||||
<div class="task-footer">
|
||||
<?php if (! empty($task['date_due'])): ?>
|
||||
<div class="task-date">
|
||||
<?= dt('%B %e, %G', $task['date_due']) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-icons">
|
||||
<?php if (! empty($task['nb_comments'])): ?>
|
||||
<?= $task['nb_comments'] ?> <i class="fa fa-comment-o" title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($task['description'])): ?>
|
||||
<i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?= Helper\template('board_show', array('current_project_id' => $current_project_id, 'board' => $board)) ?>
|
||||
<?php endif ?>
|
||||
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -19,46 +19,44 @@
|
|||
<?php foreach ($columns as $column): ?>
|
||||
<td class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>">
|
||||
<?php foreach ($column['tasks'] as $task): ?>
|
||||
<div class="draggable-item">
|
||||
<div class="task task-<?= $task['color_id'] ?>">
|
||||
<div class="task task-<?= $task['color_id'] ?>">
|
||||
|
||||
#<?= $task['id'] ?> -
|
||||
#<?= $task['id'] ?> -
|
||||
|
||||
<span class="task-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<?= t('Assigned to %s', $task['username']) ?>
|
||||
<?php else: ?>
|
||||
<span class="task-nobody"><?= t('Nobody assigned') ?></span>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
<span class="task-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<?= t('Assigned to %s', $task['username']) ?>
|
||||
<?php else: ?>
|
||||
<span class="task-nobody"><?= t('Nobody assigned') ?></span>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-title">
|
||||
<?= Helper\escape($task['title']) ?>
|
||||
<div class="task-title">
|
||||
<?= Helper\escape($task['title']) ?>
|
||||
</div>
|
||||
|
||||
<div class="task-footer">
|
||||
<?php if (! empty($task['date_due'])): ?>
|
||||
<div class="task-date">
|
||||
<?= dt('%B %e, %G', $task['date_due']) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-footer">
|
||||
<?php if (! empty($task['date_due'])): ?>
|
||||
<div class="task-date">
|
||||
<?= dt('%B %e, %G', $task['date_due']) ?>
|
||||
</div>
|
||||
<div class="task-icons">
|
||||
<?php if (! empty($task['nb_comments'])): ?>
|
||||
<?= $task['nb_comments'] ?> <i class="fa fa-comment-o" title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-icons">
|
||||
<?php if (! empty($task['nb_comments'])): ?>
|
||||
<?= $task['nb_comments'] ?> <i class="fa fa-comment-o" title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($task['description'])): ?>
|
||||
<i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php if (! empty($task['description'])): ?>
|
||||
<i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= BOARD_CHECK_INTERVAL ?>">
|
||||
<tr>
|
||||
<?php $column_with = round(100 / count($board), 2); ?>
|
||||
<?php foreach ($board as $column): ?>
|
||||
<th width="<?= $column_with ?>%">
|
||||
<a href="?controller=task&action=create&project_id=<?= $column['project_id'] ?>&column_id=<?= $column['id'] ?>" title="<?= t('Add a new task') ?>">+</a>
|
||||
<?= Helper\escape($column['title']) ?>
|
||||
<?php if ($column['task_limit']): ?>
|
||||
<span title="<?= t('Task limit') ?>" class="task-limit">
|
||||
(
|
||||
<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>
|
||||
/
|
||||
<?= Helper\escape($column['task_limit']) ?>
|
||||
)
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<?php foreach ($board as $column): ?>
|
||||
<td
|
||||
id="column-<?= $column['id'] ?>"
|
||||
class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>"
|
||||
data-column-id="<?= $column['id'] ?>"
|
||||
data-task-limit="<?= $column['task_limit'] ?>"
|
||||
>
|
||||
<?php foreach ($column['tasks'] as $task): ?>
|
||||
<div class="task draggable-item task-<?= $task['color_id'] ?>"
|
||||
data-task-id="<?= $task['id'] ?>"
|
||||
data-owner-id="<?= $task['owner_id'] ?>"
|
||||
data-due-date="<?= $task['date_due'] ?>"
|
||||
title="<?= t('View this task') ?>">
|
||||
|
||||
<a href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> -
|
||||
|
||||
<span class="task-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-nobody"><?= t('Nobody assigned') ?></a>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-title">
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>" title="<?= t('View this task') ?>"><?= Helper\escape($task['title']) ?></a>
|
||||
</div>
|
||||
|
||||
<div class="task-footer">
|
||||
<?php if (! empty($task['date_due'])): ?>
|
||||
<div class="task-date">
|
||||
<?= dt('%B %e, %G', $task['date_due']) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-icons">
|
||||
<?php if (! empty($task['nb_comments'])): ?>
|
||||
<?= $task['nb_comments'] ?> <i class="fa fa-comment-o" title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($task['description'])): ?>
|
||||
<i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<script src="assets/js/jquery-1.11.1.min.js"></script>
|
||||
<script src="assets/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="assets/js/jquery.ui.touch-punch.min.js"></script>
|
||||
<link rel="stylesheet" href="assets/css/app.css" media="screen">
|
||||
<link rel="stylesheet" href="assets/css/font-awesome.min.css" media="screen">
|
||||
<link rel="icon" type="image/png" href="assets/img/favicon.png">
|
||||
|
|
@ -12,7 +15,7 @@
|
|||
<link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png">
|
||||
<title><?= isset($title) ? Helper\escape($title).' - Kanboard' : 'Kanboard' ?></title>
|
||||
<?php if (isset($auto_refresh)): ?>
|
||||
<meta http-equiv="refresh" content="<?= AUTO_REFRESH_DURATION ?>" >
|
||||
<meta http-equiv="refresh" content="<?= BOARD_PUBLIC_CHECK_INTERVAL ?>" >
|
||||
<?php endif ?>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
Loading…
Reference in New Issue