Rewrite board drag and drop with jquery (touch devices, IE, auto-update)

This commit is contained in:
Frédéric Guillot 2014-05-17 17:35:39 -04:00
parent 09da5720e8
commit 5e4b40665f
18 changed files with 375 additions and 379 deletions

View File

@ -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 {

View File

@ -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 + "&timestamp=" + 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();
});
}());

4
assets/js/jquery-1.11.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
assets/js/jquery.ui.touch-punch.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, 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);

View File

@ -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', '');

View File

@ -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();
}
/**

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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'),

View File

@ -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);
}
}
}

View File

@ -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)");

View File

@ -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");

View File

@ -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&amp;action=create&amp;project_id=<?= $column['project_id'] ?>&amp;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&amp;action=edit&amp;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&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
<?php else: ?>
<a href="?controller=board&amp;action=assign&amp;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&amp;action=show&amp;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>

View File

@ -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>

75
templates/board_show.php Normal file
View File

@ -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&amp;action=create&amp;project_id=<?= $column['project_id'] ?>&amp;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&amp;action=edit&amp;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&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
<?php else: ?>
<a href="?controller=board&amp;action=assign&amp;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&amp;action=show&amp;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>

View File

@ -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>