Merge branch 'master' into project_name_in_task_links

* master:
  ajout captures pour les vues
  essai intégration image
  Append filters instead of replacing value for users and categories dropdowns
  Do not show empty swimlanes in public view
  Show complexity sum across all swimlanes
  Show number of tasks for each column across all swimlanes
  Fix regression (css)
  Change swimlane layout to save space on the screen
  Add the possibility to set/unset max column height (scrolling)
  settings chapter
  Add getPluginHomepage()
  Add page to show the list of plugins
  Do not use preventDefault() with .close-popover when there is no popover opened
  Show "Open this task" in dropdown menu for closed tasks
  Add contributor
  Update app.css
  Update popover.css
  Show assignee on card only when someone is assigned
This commit is contained in:
xavier.vidal 2015-09-29 09:46:02 +02:00
commit 118f265c11
72 changed files with 827 additions and 223 deletions

View File

@ -38,6 +38,7 @@ Contributors:
- [Jean-François Magnier](https://github.com/lefakir)
- [Jesusaplsoft](https://github.com/jesusaplsoft)
- [Jesús Marín](https://github.com/alu0100502114)
- [Jules Verhaeren](https://github.com/julesverhaeren)
- [Karol J](https://github.com/dzudek)
- [Kiswa](https://github.com/kiswa)
- [Kralo](https://github.com/kralo)

View File

@ -18,6 +18,12 @@ Core functionalities moved to plugins:
Improvements:
* Append filters instead of replacing value for users and categories dropdowns
* Do not show empty swimlanes in public view
* Change swimlane layout to save space on the screen
* Add the possibility to set/unset max column height (column scrolling)
* Show "Open this task" in dropdown menu for closed tasks
* Show assignee on card only when someone is assigned (hide nobody text)
* Highlight selected item in dropdown menus
* Gantt chart: change bar color according to task progress
* Replace color dropdown by color picker in task forms

View File

@ -27,7 +27,7 @@ class Board extends Base
}
// Display the board with a specific layout
$this->response->html($this->template->layout('board/public_view', array(
$this->response->html($this->template->layout('board/view_public', array(
'project' => $project,
'swimlanes' => $this->board->getBoard($project['id']),
'title' => $project['name'],
@ -49,7 +49,7 @@ class Board extends Base
{
$params = $this->getProjectFilters('board', 'show');
$this->response->html($this->template->layout('board/private_view', array(
$this->response->html($this->template->layout('board/view_private', array(
'categories_list' => $this->category->getList($params['project']['id'], false),
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
@ -136,7 +136,7 @@ class Board extends Base
}
$values = $this->request->getJson();
$this->userSession->setFilters($project_id, $values['search']);
$this->userSession->setFilters($project_id, empty($values['search']) ? '' : $values['search']);
$this->response->html($this->renderBoard($project_id));
}

View File

@ -77,6 +77,19 @@ class Config extends Base
)));
}
/**
* Display the plugin page
*
* @access public
*/
public function plugins()
{
$this->response->html($this->layout('config/plugins', array(
'plugins' => $this->pluginLoader->plugins,
'title' => t('Settings').' > '.t('Plugins'),
)));
}
/**
* Display the application settings page
*

View File

@ -18,36 +18,8 @@ class Taskstatus extends Base
public function close()
{
$task = $this->getTask();
$redirect = $this->request->getStringParam('redirect');
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
if ($this->taskStatus->close($task['id'])) {
$this->session->flash(t('Task closed successfully.'));
} else {
$this->session->flashError(t('Unable to close this task.'));
}
if ($redirect === 'board') {
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
if ($this->request->isAjax()) {
$this->response->html($this->template->render('task_status/close', array(
'task' => $task,
'redirect' => $redirect,
)));
}
$this->response->html($this->taskLayout('task_status/close', array(
'task' => $task,
'redirect' => $redirect,
)));
$this->changeStatus($task, 'close', t('Task closed successfully.'), t('Unable to close this task.'));
$this->renderTemplate($task, 'task_status/close');
}
/**
@ -58,22 +30,46 @@ class Taskstatus extends Base
public function open()
{
$task = $this->getTask();
$redirect = $this->request->getStringParam('redirect');
$this->changeStatus($task, 'open', t('Task opened successfully.'), t('Unable to open this task.'));
$this->renderTemplate($task, 'task_status/open');
}
private function changeStatus(array $task, $method, $success_message, $failure_message)
{
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
if ($this->taskStatus->open($task['id'])) {
$this->session->flash(t('Task opened successfully.'));
if ($this->taskStatus->$method($task['id'])) {
$this->session->flash($success_message);
} else {
$this->session->flashError(t('Unable to open this task.'));
$this->session->flashError($failure_message);
}
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
if ($this->request->getStringParam('redirect') === 'board') {
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
}
private function renderTemplate(array $task, $template)
{
$redirect = $this->request->getStringParam('redirect');
if ($this->request->isAjax()) {
$this->response->html($this->template->render($template, array(
'task' => $task,
'redirect' => $redirect,
)));
}
$this->response->html($this->taskLayout('task_status/open', array(
$this->response->html($this->taskLayout($template, array(
'task' => $task,
'redirect' => $redirect,
)));
}
}

View File

@ -23,6 +23,7 @@ use Pimple\Container;
* @property \Core\ObjectStorage\ObjectStorageInterface $objectStorage
* @property \Core\Cache\Cache $memoryCache
* @property \Core\Plugin\Hook $hook
* @property \Core\Plugin\Loader $pluginLoader
* @property \Integration\BitbucketWebhook $bitbucketWebhook
* @property \Integration\GithubWebhook $githubWebhook
* @property \Integration\GitlabWebhook $gitlabWebhook

View File

@ -44,4 +44,69 @@ abstract class Base extends \Core\Base
call_user_func($callback, $container);
});
}
/**
* Get plugin name
*
* This method should be overrided by your Plugin class
*
* @access public
* @return string
*/
public function getPluginName()
{
return ucfirst(substr(get_called_class(), 7, -7));
}
/**
* Get plugin description
*
* This method should be overrided by your Plugin class
*
* @access public
* @return string
*/
public function getPluginDescription()
{
return '';
}
/**
* Get plugin author
*
* This method should be overrided by your Plugin class
*
* @access public
* @return string
*/
public function getPluginAuthor()
{
return '?';
}
/**
* Get plugin version
*
* This method should be overrided by your Plugin class
*
* @access public
* @return string
*/
public function getPluginVersion()
{
return '?';
}
/**
* Get plugin homepage
*
* This method should be overrided by your Plugin class
*
* @access public
* @return string
*/
public function getPluginHomepage()
{
return '';
}
}

View File

@ -21,6 +21,14 @@ class Loader extends \Core\Base
*/
const TABLE_SCHEMA = 'plugin_schema_versions';
/**
* Plugin instances
*
* @access public
* @var array
*/
public $plugins = array();
/**
* Scan plugin folder and load plugins
*
@ -55,6 +63,7 @@ class Loader extends \Core\Base
Tool::buildDic($this->container, $instance->getClasses());
$instance->initialize();
$this->plugins[] = $instance;
}
/**

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Tabla de contenido',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Ayuda con permisos del proyecto',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1014,4 +1014,10 @@ return array(
'Table of contents' => 'Table des matières',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Aide avec les permissions des projets',
'Author' => 'Auteur',
'Version' => 'Version',
'Plugins' => 'Extensions',
'There is no plugin loaded.' => 'Il n\'y a aucune extension chargée.',
'Set maximum column height' => 'Définir la hauteur max. des colonnes',
'Remove maximum column height' => 'Enlever la hauteur max. des colonnes',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Daftar isi',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Bantuan dengan izin proyek',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Innholdsfortegnelse',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Hjelp med prosjekttilganger',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Índice',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Ajuda com as permissões dos projetos',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Tabela de conteúdos',
'Gantt' => 'Gantt',
'Help with project permissions' => 'Ajuda com permissões de projecto',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
'Table of contents' => 'Сожержание',
'Gantt' => 'Гантт',
'Help with project permissions' => 'Помощь с правами доступа по проекту',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -1012,4 +1012,10 @@ return array(
// 'Table of contents' => '',
// 'Gantt' => '',
// 'Help with project permissions' => '',
// 'Author' => '',
// 'Version' => '',
// 'Plugins' => '',
// 'There is no plugin loaded.' => '',
// 'Set maximum column height' => '',
// 'Remove maximum column height' => '',
);

View File

@ -252,16 +252,24 @@ class Board extends Base
$swimlanes[$i]['columns'] = $columns;
$swimlanes[$i]['nb_columns'] = $nb_columns;
$swimlanes[$i]['nb_tasks'] = 0;
$swimlanes[$i]['nb_swimlanes'] = $ilen;
for ($j = 0; $j < $nb_columns; $j++) {
$column_id = $columns[$j]['id'];
$swimlane_id = $swimlanes[$i]['id'];
if (! isset($swimlanes[0]['columns'][$j]['nb_column_tasks'])) {
$swimlanes[0]['columns'][$j]['nb_column_tasks'] = 0;
$swimlanes[0]['columns'][$j]['total_score'] = 0;
}
$swimlanes[$i]['columns'][$j]['tasks'] = $callback === null ? $this->taskFinder->getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id) : $callback($project_id, $column_id, $swimlane_id);
$swimlanes[$i]['columns'][$j]['nb_tasks'] = count($swimlanes[$i]['columns'][$j]['tasks']);
$swimlanes[$i]['columns'][$j]['score'] = $this->getColumnSum($swimlanes[$i]['columns'][$j]['tasks'], 'score');
$swimlanes[$i]['nb_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks'];
$swimlanes[0]['columns'][$j]['nb_column_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks'];
$swimlanes[0]['columns'][$j]['total_score'] += $swimlanes[$i]['columns'][$j]['score'];
}
}

View File

@ -2,6 +2,7 @@
namespace ServiceProvider;
use Core\Plugin\Loader;
use Core\ObjectStorage\FileStorage;
use Core\Paginator;
use Core\OAuth2;
@ -117,5 +118,7 @@ class ClassProvider implements ServiceProviderInterface
$container['objectStorage'] = function() {
return new FileStorage(FILES_DIR);
};
$container['pluginLoader'] = new Loader($container);
}
}

View File

@ -0,0 +1,51 @@
<!-- column titles -->
<?php ?>
<tr>
<?php foreach ($swimlane['columns'] as $column): ?>
<th class="board-column-header board-column-header-<?= $column['id'] ?>" data-column-id="<?= $column['id'] ?>">
<!-- column in collapsed mode -->
<div class="board-column-collapsed">
<span title="<?= t('Task count') ?>" class="board-column-header-task-count" title="<?= t('Show this column') ?>">
<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_column_tasks'] ?></span>
</span>
</div>
<!-- column in expanded mode -->
<div class="board-column-expanded">
<?php if (! $not_editable): ?>
<div class="board-add-icon">
<?= $this->url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?>
</div>
<?php endif ?>
<span class="board-column-title" data-column-id="<?= $column['id'] ?>" title="<?= t('Hide this column') ?>">
<?= $this->e($column['title']) ?>
</span>
<?php if (! $not_editable && ! empty($column['description'])): ?>
<span class="tooltip pull-right" title='<?= $this->e($this->text->markdown($column['description'])) ?>'>
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
<?php if (! empty($column['score'])): ?>
<span class="pull-right" title="<?= t('Score') ?>">
<?= $column['total_score'] ?>
</span>
<?php endif ?>
<span title="<?= t('Total number of tasks in this column across all swimlanes') ?>" class="board-column-header-task-count">
(<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_column_tasks'] ?></span>)
</span>
<?php if ($column['task_limit']): ?>
<span title="<?= t('Task limit') ?>">
(<span id="task-number-column-<?= $column['id'] ?>"><?= $this->e($column['task_limit']) ?></span>)
</span>
<?php endif ?>
</div>
</th>
<?php endforeach ?>
</tr>

View File

@ -13,18 +13,37 @@
>
<?php endif ?>
<?php foreach ($swimlanes as $swimlane): ?>
<?php foreach ($swimlanes as $index => $swimlane): ?>
<?php if (empty($swimlane['columns'])): ?>
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
<?php break ?>
<?php else: ?>
<?= $this->render('board/table_swimlane', array(
'project' => $project,
'swimlane' => $swimlane,
'board_highlight_period' => $board_highlight_period,
'hide_swimlane' => count($swimlanes) === 1,
'not_editable' => isset($not_editable),
)) ?>
<?php if ($index === 0): ?>
<?= $this->render('board/table_column', array(
'swimlane' => $swimlane,
'not_editable' => isset($not_editable),
)) ?>
<?php endif ?>
<?php if (! ($swimlane['nb_tasks'] === 0 && isset($not_editable))): ?>
<?php if ($swimlane['nb_swimlanes'] > 1): ?>
<?= $this->render('board/table_swimlane', array(
'project' => $project,
'swimlane' => $swimlane,
'not_editable' => isset($not_editable),
)) ?>
<?php endif ?>
<?= $this->render('board/table_tasks', array(
'project' => $project,
'swimlane' => $swimlane,
'not_editable' => isset($not_editable),
'board_highlight_period' => $board_highlight_period,
)) ?>
<?php endif ?>
<?php endif ?>
<?php endforeach ?>
</table>

View File

@ -1,104 +1,26 @@
<!-- swimlane -->
<tr id="swimlane-<?= $swimlane['id'] ?>">
<!-- swimlane toggle -->
<?php if (! $hide_swimlane): ?>
<th class="board-swimlane-header">
<?php if (! $not_editable): ?>
<a href="#" class="board-swimlane-toggle" data-swimlane-id="<?= $swimlane['id'] ?>">
<i class="fa fa-minus-circle hide-icon-swimlane-<?= $swimlane['id'] ?>"></i>
<i class="fa fa-plus-circle show-icon-swimlane-<?= $swimlane['id'] ?>" style="display: none"></i>
</a>
<th class="board-swimlane-header" colspan="<?= $swimlane['nb_columns'] ?>">
<?php if (! $not_editable): ?>
<a href="#" class="board-swimlane-toggle" data-swimlane-id="<?= $swimlane['id'] ?>">
<i class="fa fa-chevron-circle-up hide-icon-swimlane-<?= $swimlane['id'] ?>" title="<?= t('Collapse swimlane') ?>"></i>
<i class="fa fa-chevron-circle-down show-icon-swimlane-<?= $swimlane['id'] ?>" title="<?= t('Expand swimlane') ?>" style="display: none"></i>
</a>
<?php endif ?>
<?php if (! empty($swimlane['description'])): ?>
<span
title="<?= t('Description') ?>"
class="tooltip"
data-href="<?= $this->url->href('board', 'swimlane', array('swimlane_id' => $swimlane['id'], 'project_id' => $project['id'])) ?>">
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
<?= $this->e($swimlane['name']) ?>
<span title="<?= t('Task count') ?>" class="board-column-header-task-count swimlane-task-count-<?= $swimlane['id'] ?>">
(<?= $swimlane['nb_tasks'] ?>)
</span>
<span class="board-swimlane-toggle-title show-icon-swimlane-<?= $swimlane['id'] ?>"><?= $this->e($swimlane['name']) ?></span>
<?php endif ?>
</th>
<?php endif ?>
<!-- column header title -->
<?php foreach ($swimlane['columns'] as $column): ?>
<th class="board-column-header board-column-header-<?= $column['id'] ?>" data-column-id="<?= $column['id'] ?>">
<div class="board-column-collapsed">
<span title="<?= t('Task count') ?>" class="board-column-header-task-count" title="<?= t('Show this column') ?>">
<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>
<?php if (! $not_editable && ! empty($swimlane['description'])): ?>
<span
title="<?= t('Description') ?>"
class="tooltip"
data-href="<?= $this->url->href('board', 'swimlane', array('swimlane_id' => $swimlane['id'], 'project_id' => $project['id'])) ?>">
<i class="fa fa-info-circle"></i>
</span>
</div>
<div class="board-column-expanded">
<?php if (! $not_editable): ?>
<div class="board-add-icon">
<?= $this->url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?>
</div>
<?php endif ?>
<?php endif ?>
<span class="board-column-title" data-column-id="<?= $column['id'] ?>" title="<?= t('Hide this column') ?>">
<?= $this->e($column['title']) ?>
</span>
<?php if (! $not_editable && ! empty($column['description'])): ?>
<span class="tooltip pull-right" title='<?= $this->e($this->text->markdown($column['description'])) ?>'>
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
<?php if (! empty($column['score'])): ?>
<span class="pull-right" title="<?= t('Score') ?>">
<?= $column['score'] ?>&nbsp;
</span>
<?php endif ?>
<?php if ($column['task_limit']): ?>
<span title="<?= t('Task limit') ?>">
(<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>/<?= $this->e($column['task_limit']) ?>)
</span>
<?php else: ?>
<span title="<?= t('Task count') ?>" class="board-column-header-task-count">
(<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>)
</span>
<?php endif ?>
</div>
<span title="<?= t('Task count') ?>" class="board-column-header-task-count swimlane-task-count-<?= $swimlane['id'] ?>">
(<?= $swimlane['nb_tasks'] ?>)
</span>
</th>
<?php endforeach ?>
</tr>
<tr class="board-swimlane swimlane-row-<?= $swimlane['id'] ?>">
<!-- swimlane title -->
<?php if (! $hide_swimlane): ?>
<th class="board-swimlane-title">
<?= $this->e($swimlane['name']) ?>
</th>
<?php endif ?>
<!-- task list -->
<?php foreach ($swimlane['columns'] as $column): ?>
<td class="board-column-<?= $column['id'] ?> <?= $column['task_limit'] && $column['nb_tasks'] > $column['task_limit'] ? 'board-task-list-limit' : '' ?>">
<div class="board-task-list board-column-expanded" data-column-id="<?= $column['id'] ?>" data-swimlane-id="<?= $swimlane['id'] ?>" data-task-limit="<?= $column['task_limit'] ?>">
<?php foreach ($column['tasks'] as $task): ?>
<?= $this->render($not_editable ? 'board/task_public' : 'board/task_private', array(
'project' => $project,
'task' => $task,
'board_highlight_period' => $board_highlight_period,
'not_editable' => $not_editable,
)) ?>
<?php endforeach ?>
</div>
<div class="board-column-collapsed">
<div class="board-rotation-wrapper">
<div class="board-column-title board-rotation" data-column-id="<?= $column['id'] ?>" title="<?= t('Show this column') ?>">
<?= $this->e($column['title']) ?>
</div>
</div>
</div>
</td>
<?php endforeach ?>
</tr>

View File

@ -0,0 +1,31 @@
<!-- task row -->
<tr class="board-swimlane swimlane-row-<?= $swimlane['id'] ?>">
<?php foreach ($swimlane['columns'] as $column): ?>
<td class="
board-column-<?= $column['id'] ?>
<?= $column['task_limit'] > 0 && $column['nb_tasks'] > $column['task_limit'] ? 'board-task-list-limit' : '' ?>
">
<!-- tasks list -->
<div class="board-task-list board-column-expanded" data-column-id="<?= $column['id'] ?>" data-swimlane-id="<?= $swimlane['id'] ?>" data-task-limit="<?= $column['task_limit'] ?>">
<?php foreach ($column['tasks'] as $task): ?>
<?= $this->render($not_editable ? 'board/task_public' : 'board/task_private', array(
'project' => $project,
'task' => $task,
'board_highlight_period' => $board_highlight_period,
'not_editable' => $not_editable,
)) ?>
<?php endforeach ?>
</div>
<!-- column in collapsed mode (rotated text) -->
<div class="board-column-collapsed">
<div class="board-rotation-wrapper">
<div class="board-column-title board-rotation" data-column-id="<?= $column['id'] ?>" title="<?= t('Show this column') ?>">
<i class="fa fa-chevron-circle-up tooltip" title="<?= $this->e($column['title']) ?>"></i> <?= $this->e($column['title']) ?>
</div>
</div>
</div>
</td>
<?php endforeach ?>
</tr>

View File

@ -8,6 +8,10 @@
<li><i class="fa fa-comment-o fa-fw"></i>&nbsp;<?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-code-fork fa-fw"></i>&nbsp;<?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-camera fa-fw"></i>&nbsp;<?= $this->url->link(t('Add a screenshot'), 'board', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-close fa-fw"></i>&nbsp;<?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li>
<?php if ($task['is_active'] == 1): ?>
<li><i class="fa fa-close fa-fw"></i>&nbsp;<?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li>
<?php else: ?>
<li><i class="fa fa-check-square-o fa-fw"></i>&nbsp;<?= $this->url->link(t('Open this task'), 'taskstatus', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li>
<?php endif ?>
</ul>
</span>

View File

@ -9,10 +9,11 @@
data-task-url="<?= $this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
<div class="task-board-sort-handle" style="display: none;"><i class="fa fa-arrows-alt"></i></div>
<?= $this->render('board/task_menu', array('task' => $task)) ?>
<?php if ($this->board->isCollapsed($project['id'])): ?>
<?php if ($this->board->isCollapsed($task['project_id'])): ?>
<div class="task-board-collapsed">
<?= $this->render('board/task_menu', array('task' => $task)) ?>
<?php if (! empty($task['assignee_username'])): ?>
<span title="<?= $this->e($task['assignee_name'] ?: $task['assignee_username']) ?>">
<?= $this->e($this->user->getInitials($task['assignee_name'] ?: $task['assignee_username'])) ?>
@ -22,6 +23,7 @@
</div>
<?php else: ?>
<div class="task-board-expanded">
<?= $this->render('board/task_menu', array('task' => $task)) ?>
<?php if ($task['reference']): ?>
<span class="task-board-reference" title="<?= t('Reference') ?>">
@ -29,9 +31,10 @@
</span>
<?php endif ?>
<?php if (! empty($task['owner_id'])): ?>
<span class="task-board-user <?= $this->user->isCurrentUser($task['owner_id']) ? 'task-board-current-user' : '' ?>">
<?= $this->url->link(
(! empty($task['owner_id']) ? ($task['assignee_name'] ?: $task['assignee_username']) : t('Nobody assigned')),
$task['assignee_name'] ?: $task['assignee_username'],
'board',
'changeAssignee',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
@ -40,6 +43,7 @@
t('Change assignee')
) ?>
</span>
<?php endif ?>
<?php if ($task['is_active'] == 1): ?>
<div class="task-board-days">

View File

@ -0,0 +1,30 @@
<div class="page-header">
<h2><?= t('Plugins') ?></h2>
</div>
<?php if (empty($plugins)): ?>
<p class="alert"><?= t('There is no plugin loaded.') ?></p>
<?php else: ?>
<table class="table-stripped">
<tr>
<th class="column-20"><?= t('Name') ?></th>
<th class="column-20"><?= t('Author') ?></th>
<th class="column-10"><?= t('Version') ?></th>
<th><?= t('Description') ?></th>
</tr>
<?php foreach($plugins as $plugin): ?>
<tr>
<td>
<?php if ($plugin->getPluginHomepage()): ?>
<a href="<?= $plugin->getPluginHomepage() ?>" target="_blank" rel="noreferrer"><?= $this->e($plugin->getPluginName()) ?></a>
<?php else: ?>
<?= $this->e($plugin->getPluginName()) ?>
<?php endif ?>
</td>
<td><?= $this->e($plugin->getPluginAuthor()) ?></td>
<td><?= $this->e($plugin->getPluginVersion()) ?></td>
<td><?= $this->e($plugin->getPluginDescription()) ?></td>
</tr>
<?php endforeach ?>
<?php endif ?>

View File

@ -4,6 +4,9 @@
<li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('About'), 'config', 'index') ?>
</li>
<li <?= $this->app->getRouterAction() === 'plugins' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Plugins'), 'config', 'plugins') ?>
</li>
<li <?= $this->app->getRouterAction() === 'application' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Application settings'), 'config', 'application') ?>
</li>

View File

@ -21,6 +21,14 @@
<i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a>
</span>
</li>
<li>
<span class="filter-max-height" style="display: none">
<i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a>
</span>
<span class="filter-min-height">
<i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a>
</span>
</li>
<?php endif ?>
<?= $this->render('project/dropdown', array('project' => $project)) ?>
</ul>
@ -59,10 +67,9 @@
<div class="dropdown filters">
<i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Users') ?></a>
<ul>
<li><a href="#" class="filter-helper" data-filter="status:open"><?= t('All users') ?></a></li>
<li><a href="#" class="filter-helper" data-filter="status:open assignee:nobody"><?= t('Not assigned') ?></a></li>
<li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li>
<?php foreach ($users_list as $user): ?>
<li><a href="#" class="filter-helper" data-filter='status:open assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li>
<li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li>
<?php endforeach ?>
</ul>
</div>
@ -72,10 +79,9 @@
<div class="dropdown filters">
<i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Categories') ?></a>
<ul>
<li><a href="#" class="filter-helper" data-filter="status:open"><?= t('All categories') ?></a></li>
<li><a href="#" class="filter-helper" data-filter="status:open category:none"><?= t('No category') ?></a></li>
<li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li>
<?php foreach ($categories_list as $category): ?>
<li><a href="#" class="filter-helper" data-filter='status:open category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li>
<li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li>
<?php endforeach ?>
</ul>
</div>

View File

@ -50,7 +50,7 @@
<?= $this->form->hidden('id', $default_swimlane) ?>
<?= $this->form->label(t('Rename'), 'default_swimlane') ?>
<?= $this->form->text('default_swimlane', $default_swimlane, array(), array('autofocus', 'required', 'maxlength="50"')) ?><br/>
<?= $this->form->text('default_swimlane', $default_swimlane, array(), array('required', 'maxlength="50"')) ?><br/>
<?= $this->form->checkbox('show_default_swimlane', t('Show default swimlane'), 1, isset($default_swimlane['show_default_swimlane']) && $default_swimlane['show_default_swimlane'] == 1) ?>

View File

@ -4,7 +4,7 @@
<div class="confirm">
<p class="alert alert-info">
<?= t('Do you really want to close the task "%s" as well as all subtasks?', $this->e($task['title'])) ?>
<?= t('Do you really want to close the task "%s" as well as all subtasks?', $task['title']) ?>
</p>
<div class="form-actions">

View File

@ -4,12 +4,12 @@
<div class="confirm">
<p class="alert alert-info">
<?= t('Do you really want to open this task: "%s"?', $this->e($task['title'])) ?>
<?= t('Do you really want to open this task: "%s"?', $task['title']) ?>
</p>
<div class="form-actions">
<?= $this->url->link(t('Yes'), 'taskstatus', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?>
<?= $this->url->link(t('Yes'), 'taskstatus', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes', 'redirect' => $redirect), true, 'btn btn-red') ?>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?>
</div>
</div>

View File

@ -33,5 +33,4 @@ if (ENABLE_URL_REWRITE) {
require __DIR__.'/routes.php';
}
$plugin = new Core\Plugin\Loader($container);
$plugin->scan();
$container['pluginLoader']->scan();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -61,10 +61,12 @@ td.board-column-task-collapsed {
.board-rotation-wrapper {
position: relative;
padding: 8px 4px;
min-height: 150px;
overflow: auto;
}
.board-rotation {
min-width: 250px;
white-space: nowrap;
-webkit-backface-visibility: hidden;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
@ -77,6 +79,10 @@ td.board-column-task-collapsed {
}
/* column header */
.board-column-title {
cursor: pointer;
}
.board-add-icon {
float: left;
padding: 0 5px;
@ -105,26 +111,22 @@ th.board-column-header-collapsed .board-column-header-task-count {
}
/* swimlanes */
th.board-swimlane-header {
width: 120px;
}
a.board-swimlane-toggle {
font-size: 0.95em;
text-decoration: none;
}
.board-swimlane-toggle-title {
font-size: 0.85em;
display: none;
}
.board-swimlane-title {
vertical-align: top;
a.board-swimlane-toggle:hover,
a.board-swimlane-toggle:focus {
color: #000;
text-decoration: none;
border: none;
}
/* board task list */
.board-task-list {
overflow: auto;
min-height: 60px;
}
.board-task-list-limit {

View File

@ -18,6 +18,6 @@
top: 1%;
padding: 15px;
background: #fff;
overflow: scroll;
overflow: auto;
max-height: 85%;
}

View File

@ -42,8 +42,6 @@ a.task-board-collapsed-title {
}
.task-board .dropdown {
float: left;
margin-right: 5px;
font-size: 1.1em;
}

File diff suppressed because one or more lines are too long

View File

@ -8,13 +8,13 @@ Board.prototype.execute = function() {
this.app.swimlane.listen();
this.restoreColumnViewMode();
this.compactView();
this.columnScrolling();
this.poll();
this.keyboardShortcuts();
this.resizeColumnHeight();
this.listen();
this.dragAndDrop();
$(window).resize(this.resizeColumnHeight);
$(window).resize(this.columnScrolling);
};
Board.prototype.poll = function() {
@ -85,7 +85,7 @@ Board.prototype.refresh = function(data) {
this.app.refresh();
this.app.swimlane.refresh();
this.app.swimlane.listen();
this.resizeColumnHeight();
this.columnScrolling();
this.app.hideLoadingIcon();
this.listen();
this.dragAndDrop();
@ -93,22 +93,6 @@ Board.prototype.refresh = function(data) {
this.restoreColumnViewMode();
};
Board.prototype.resizeColumnHeight = function() {
if ($(".board-swimlane").length > 1) {
$(".board-task-list").each(function() {
if ($(this).height() > 500) {
$(this).height(500);
}
else {
$(this).css("min-height", 320); // Min height is the height of the menu dropdown
}
});
}
else {
$(".board-task-list").height($(window).height() - 145);
}
};
Board.prototype.dragAndDrop = function() {
var self = this;
var params = {
@ -155,11 +139,58 @@ Board.prototype.listen = function() {
self.toggleCompactView();
});
$(document).on('click', ".filter-toggle-height", function(e) {
e.preventDefault();
self.toggleColumnScrolling();
});
$(document).on("click", ".board-column-title", function() {
self.toggleColumnViewMode($(this).data("column-id"));
});
};
Board.prototype.toggleColumnScrolling = function() {
var scrolling = localStorage.getItem("column_scroll") || 1;
localStorage.setItem("column_scroll", scrolling == 0 ? 1 : 0);
this.columnScrolling();
};
Board.prototype.columnScrolling = function() {
if (localStorage.getItem("column_scroll") == 0) {
$(".filter-max-height").show();
$(".filter-min-height").hide();
$(".board-task-list").each(function() {
$(this).css("min-height", 80);
$(this).css("height", '');
$(".board-rotation-wrapper").css("min-height", '');
});
}
else {
$(".filter-max-height").hide();
$(".filter-min-height").show();
if ($(".board-swimlane").length > 1) {
$(".board-task-list").each(function() {
if ($(this).height() > 500) {
$(this).css("height", 500);
}
else {
$(this).css("min-height", 320); // Height of the dropdown menu
$(".board-rotation-wrapper").css("min-height", 320);
}
});
}
else {
var height = $(window).height() - 145;
$(".board-task-list").css("height", height);
$(".board-rotation-wrapper").css("min-height", height);
}
}
};
Board.prototype.toggleCompactView = function() {
var scrolling = localStorage.getItem("horizontal_scroll") || 1;
localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
@ -233,7 +264,6 @@ Board.prototype.hideColumn = function(columnId) {
});
$(".board-column-" + columnId + " .board-rotation").each(function() {
var position = $(".board-swimlane").position();
$(this).css("width", $(".board-column-" + columnId + "").height());
});

View File

@ -21,11 +21,14 @@ Popover.prototype.open = function(link) {
};
Popover.prototype.close = function(e) {
if (e) {
e.preventDefault();
}
if (this.isOpen()) {
$('#popover-container').remove();
if (e) {
e.preventDefault();
}
$('#popover-container').remove();
}
};
Popover.prototype.onClick = function(e) {

View File

@ -18,7 +18,13 @@ Search.prototype.listen = function() {
// Filter helper for search
$(document).on("click", ".filter-helper", function (e) {
e.preventDefault();
var filter = $(this).data("filter");
var appendFilter = $(this).data("append-filter");
if (appendFilter) {
filter = $("#form-search").val() + " " + appendFilter;
}
$("#form-search").val(filter);

View File

@ -16,9 +16,8 @@ Swimlane.prototype.expand = function(swimlaneId) {
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
$('.swimlane-row-' + swimlaneId).css('display', 'table-row');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
$('.swimlane-task-count-' + swimlaneId).css('display', 'inline');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
};
Swimlane.prototype.collapse = function(swimlaneId) {
@ -30,9 +29,8 @@ Swimlane.prototype.collapse = function(swimlaneId) {
}
$('.swimlane-row-' + swimlaneId).css('display', 'none');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
$('.swimlane-task-count-' + swimlaneId).css('display', 'none');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
};
Swimlane.prototype.isCollapsed = function(swimlaneId) {

View File

@ -0,0 +1,41 @@
Paramètres de l'application
====================
Certains paramètres de l'application peuvent être modifiés sur la page des paramètres.
Seuls les administrateurs peuvent modifier ces paramètres.
Allez au menu **Paramètres**, puis choisissez **Paramètres de l'application** sur la gauche.
![Paramètres de l'application](http://kanboard.net/screenshots/documentation/application-settings.png)
### URL de l'application
Ce paramètre est utilisé pour les notifications par mail.
Le pied de page du mail contiendra un lien vers la tâche du Kanboard.
### Langue
La langue de l'application peut être modifiée à tout moment.
Elle sera définie pour tous les utilisateurs.
### Fuseau horaire
Par défaut, Kanboard utilise le TUC comme fuseau horaire, mais vous pouvez définir votre propre fuseau horaire.
La liste contient tous les fuseaux horaires pris en charge par votre serveur web.
### Format de date
Format d'entrée utilisé pour les champs de saisie de date, par exemple la date d'échéance pour les tâches.
Kanboard propose 4 différents formats:
- JJ/MM/AAAA
- MM/JJ/AAAA (par défaut)
- AAAA/MM/JJ
- MM.JJ.AAAA
Le format [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) est toujours accepté (AAAA-MM-JJ ou AAAA_MM_JJ).
### Feuille de style personnalisée
Écrivez votre propre CSS pour remplacer ou améliorer le style par défaut de Kanboard.

View File

@ -0,0 +1,41 @@
Paramètres de l'application
====================
Certains paramètres de l'application peuvent être modifiés sur la page des paramètres.
Seuls les administrateurs peuvent modifier ces paramètres.
Allez au menu **Paramètres**, puis choisissez **Paramètres de l'application** sur la gauche.
![Paramètres de l'application](http://kanboard.net/screenshots/documentation/application-settings.png)
### URL de l'application
Ce paramètre est utilisé pour les notifications par mail.
Le pied de page du mail contiendra un lien vers la tâche du Kanboard.
### Langue
La langue de l'application peut être modifiée à tout moment.
Elle sera définie pour tous les utilisateurs.
### Fuseau horaire
Par défaut, Kanboard utilise le TUC comme fuseau horaire, mais vous pouvez définir votre propre fuseau horaire.
La liste contient tous les fuseaux horaires pris en charge par votre serveur web.
### Format de date
Format d'entrée utilisé pour les champs de saisie de date, par exemple la date d'échéance pour les tâches.
Kanboard propose 4 différents formats:
- JJ/MM/AAAA
- MM/JJ/AAAA (par défaut)
- AAAA/MM/JJ
- MM.JJ.AAAA
Le format [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) est toujours accepté (AAAA-MM-JJ ou AAAA_MM_JJ).
### Feuille de style personnalisée
Écrivez votre propre CSS pour remplacer ou améliorer le style par défaut de Kanboard.

View File

@ -0,0 +1,24 @@
Paramètres du tableau
==============
Allez dans le menu **Paramètres** puis choisissez *Paramètres du tableau** sur la gauche
![Paramètres du tableau](http://kanboard.net/screenshots/documentation/board-settings.png)
### Mise en avant d'une tâche
Cette fonctionnalité affiche une ombre autour de la tâche lorsqu'une tâche à été déplacée récemment
Initialisez la fonctionnalité à 0 pour la désactiver, par défaut 2 jours (172800 secondes)
Toutes les tâches qui ont été déplacées depuis 2 jours seront entourées d'une ombre
### Intervalle pour rafraîchir un tableau public
Lorsque vous partagez un tableau, la page sera, par défaut, automatiquement rafraîchie toutes les 60 secondes
### Intervalle pour rafraîchir un tableau privé
Lorsque votre navigateur web est ouvert sur un tableau, Kanboard vérifie toutes les 10 secondes si quelque chose à été modifié par un autre utilisateur.
Techniquement, ce processus est fait par Ajax polling.

View File

@ -0,0 +1,43 @@
Paramètres du calendrier
=================
Allez au menu **Paramètres**, puis choisissez **Paramètres du calendrier** sur la gauche.
![Paramètres du calendrier](http://kanboard.net/screenshots/documentation/calendar-settings.png)
il existe deux calendriers distincts dans Kanboard :
- le calendrier du projet
- le calendrier de l'utilisateur, disponible dans le tableau de bord
Le calendrier du projet
----------------
Ce calendrier affiche les tâches avec les dates d'échéance et les tâches selon leur date de création ou de début.
### Afficher les tâches selon leur date de création
- La date de début d'un évènement du calendrier est la date de création de la tâche.
- la date de fin de l'évènement est la date d'achèvement de la tâche.
### Afficher les tâches selon leur date de début
- La date de début d'un évènement du calendrier est la date du démarrage effectif de la tâche.
- Cette date ne peut pas être définie manuellement.
- La date de fin de l'évènement est la date de l'achèvement de la tâche.
- S'il n'existe pas de date de début la tâche ne figurera pas sur le calendrier .
Calendrier de l'utilisateur
-------------
Ce calendrier n'affiche que les tâches assignées à l'utilisateur et de façon facultative des informations sur les sous-tâches.
### Afficher les sous-tâches selon le suivi du temps passé
- Affiche les sous-tâches dans le calendrier d'après les informations recueillies dans l afeuille de suivi du temps.
- Le croisement des données avec l'emploi du temps de l'utilisateur est également calculé.
### Afficher les estimations des sous-tâches (anticipation sur le travail à venir)
- Affiche l'estimation du travail à venir pour les sous-tâches qui ont le statut « à faire » et avec une valeur définie à « estimé ».

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,11 @@
Taux de change des devises
==============
Chaque utilisateur peut avoir un taux horaire prédéfini dans différentes devises
Si vous avez à manipuler plusieurs devises, vous définissez ici le taux en fonction de la devise de référence
Cette fonctionnalité est utilisée pour le calculer le budget du projet
![Currency Rate](http://kanboard.net/screenshots/documentation/currency-rate.png)
Les paramètres pour le taux de change des devises sont situés dans **Paramètres > Taux de change**

View File

@ -49,3 +49,13 @@ Utiliser Kanboard
- [Gestion des utilisateurs](user-management.markdown)
- [Notifications](notifications.markdown)
- [Authentification à deux facteurs](2fa.markdown)
### Paramètres
- [Raccourcis clavier](keyboard-shortcuts.markdown)
- [Paramètres de l'application](application-configuration.markdown)
- [Paramètres du projet](project-configuration.markdown)
- [Paramètres du tableau](board-configuration.markdown)
- [Paramètres du calendrier](calendar-configuration.markdown)
- [Paramètres du lien](link-labels.markdown)
- [Taux de change](currency-rate.markdown)

View File

@ -0,0 +1,28 @@
Raccourcis clavier
==================
La disponibilité des raccourcis clavier dépend de la page sur laquelle vous êtes couramment.
Vues par projets (Tableau, Agenda, Liste, Gantt)
--------------------------------------------
- Passer à la vue tableau = **v b** (appuyer sur **v** puis **b**)
- Passer à la vue agenda = **v c**
- Passer à la vue liste = **v l**
- Passer à la vue Gantt = **v g**
Vue tableau
----------
- Nouvelle tâche = **n**
- Étendre / replier une tâche = **s**
- Vue compacte / vue étendue = **c**
Application
-----------
- Ouvrir le changement de tableau = **b**
- Aller au moteur de recherche = **f**
- Restaurer la boîte de recherche = **r**
- Fermer la fenêtre de dialogue = **ESC**
- Soumettre un formulaire = **CTRL+ENTER** ou **⌘+ENTER**

View File

@ -0,0 +1,13 @@
Paramètres des liens
=============
Les relations entre les tâches peuvent être modifiées depuis les paramètres de l'application (**Paramètres > Paramètres des liens**)
![Libellé des liens](http://kanboard.net/screenshots/documentation/link-labels.png)
Chaque nom du libellé peut avoir un nom du libellé opposé.
Si il n'y a pas d'opposé, le nom du libellé sera considéré comme étant bidirectionnel.
![Création d'un libellé de lien](http://kanboard.net/screenshots/documentation/link-label-creation.png)

View File

@ -0,0 +1,42 @@
Paramètres du projet
================
Aller dans le menu **Préférences**; puis choisissez **Paramètres du projet** sur la gauche
![Paramètres du projet](http://kanboard.net/screenshots/documentation/project-settings.png)
###Colonnes par défaut pour les nouveaux projets
Vous pouvez changer le nom des colonnes par défaut.
C'est utile si vous créez toujours des projets comprenant les même colonnes
Chaque nom de colonne doit être séparé par une virgule.
Par défaut, Kanboard utilise les noms de colonne suivants : en attente, prêt, en cours, terminé.
###Catégories par défaut pour les nouveaux projets
Les catégories ne sont pas globales à l'application mais rattachées à un projet.
Chaque projet peut avoir plusieurs catégories.
De plus, si vous créez toujours la même catégorie pour tous vos projets, vous pouvez définir ici la liste des catégories à créer automatiquement
### Autoriser une seule sous-tâche en cours à la fois pour un utilisateur
Lorsque cette option est sélectionnée, un utilisateur ne peut travailler que sur une seule sous-tâche à la fois
Si une autre sous-tâche possède le statut « en cours », l'utilisateur verra cette boite de dialogue :
![Limite des sous-tâches pour l'utilisateur](http://kanboard.net/screenshots/documentation/subtask-user-restriction.png)
### Déclencher automatiquement le suivi du temps pour les sous-tâches
- Si activé, lorsque le statut d'une sous-tâche devient « en cours », le chrono va démarrer automatiquement
- Désactivez cette option si vous n'utilisez pas le suivi du temps.
### Inclure les tâches fermées dans le diagramme de flux cumulé
- Si l'option est activée, les tâches fermées seront incluses dans le diagramme de flux cumulé
- Si l'option est désactivée, seules les tâches ouvertes seront incluses dans le diagramme de flux cumulé
- Cette option affecte la colonne "total" de la table "project_daily_column_stats"

View File

@ -4,7 +4,7 @@ Pour chaque projet, les tâches peuvent être visualisées dans différentes vue
Vue Tableau
----------
![Vue Tableau](http://kanboard.net/screenshots/documentation/board-view.png)
![Vue Tableau](captures/kanboard-board.png)
- Dans cette vue, il est possible de glisser-déposer facilement des tâches d'une colonne à l'autre.
- Il est également possible d'utiliser le raccourci clavier **"v b"** pour afficher la vue Tableau.
@ -19,7 +19,7 @@ Lorsque la limite de tâches est atteinte pour une colonne, l'arrière-plan devi
Vue Agenda
--------------
![Vue Agenda](http://kanboard.net/screenshots/documentation/calendar-view.png)
![Vue Agenda](captures/kanboard-vue-calendrier.png)
- Dans cette vue, il est possible de voir les tâches avec des dates d'échéance.
- Selon les paramètres, il est également possible de voir les tâches en cours.
@ -29,7 +29,7 @@ Vue Agenda
Vue Liste
----------
![Vue liste](http://kanboard.net/screenshots/documentation/list-view.png)
![Vue liste](captures/kanboard-vue-liste.png)
- Dans cette vue, tous les résultats de votre recherche sont affichés dans un tableau.
- Il est également possible d'utiliser le raccourci clavier **"v l"** pour afficher la vue Liste.
@ -37,7 +37,7 @@ Vue Liste
Vue Gantt
----------
![Vue Gantt](http://kanboard.net/screenshots/documentation/gantt-view.png)
![Vue Gantt](captures/kanboard-vue-gantt.png)
- La vue Gantt affiche les tâches dans une fresque horizontale
- Le diagramme utilise la date de début et la date d'échéance pour afficher les tâches

View File

@ -70,6 +70,11 @@ Available methods from `Core\Plugin\Base`:
- `initialize()`: Executed when the plugin is loaded
- `getClasses()`: Return all classes that should be stored in the dependency injection container
- `on($event, $callback)`: Listen on internal events
- `getPluginName()`: Should return plugin name
- `getPluginAuthor()`: Should return plugin author
- `getPluginVersion()`: Should return plugin version
- `getPluginDescription()`: Should return plugin description
- `getPluginHomepage()`: Should return plugin Homepage (link)
Your plugin registration class also inherit from `Core\Base`, that means you can access to all classes and methods of Kanboard easily.

View File

@ -54,11 +54,15 @@ class BoardTest extends Base
$board = $b->getBoard(1);
$this->assertNotEmpty($board);
$this->assertEquals(1, count($board));
$this->assertEquals(5, count($board[0]));
$this->assertTrue(array_key_exists('name', $board[0]));
$this->assertTrue(array_key_exists('columns', $board[0]));
$this->assertTrue(array_key_exists('tasks', $board[0]['columns'][2]));
$this->assertTrue(array_key_exists('title', $board[0]['columns'][2]));
$this->assertEquals(6, count($board[0]));
$this->assertArrayHasKey('name', $board[0]);
$this->assertArrayHasKey('nb_tasks', $board[0]);
$this->assertArrayHasKey('columns', $board[0]);
$this->assertArrayHasKey('tasks', $board[0]['columns'][2]);
$this->assertArrayHasKey('nb_tasks', $board[0]['columns'][2]);
$this->assertArrayHasKey('title', $board[0]['columns'][2]);
$this->assertArrayHasKey('nb_column_tasks', $board[0]['columns'][0]);
$this->assertArrayHasKey('total_score', $board[0]['columns'][0]);
}
public function testGetBoardWithSwimlane()
@ -75,18 +79,26 @@ class BoardTest extends Base
$this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 3)));
$this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
$this->assertEquals(4, $tc->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 3)));
$this->assertEquals(5, $tc->create(array('title' => 'Task #5', 'project_id' => 1, 'column_id' => 4)));
$this->assertEquals(6, $tc->create(array('title' => 'Task #6', 'project_id' => 1, 'column_id' => 4, 'swimlane_id' => 1)));
$this->assertEquals(5, $tc->create(array('title' => 'Task #5', 'project_id' => 1, 'column_id' => 4, 'score' => 2)));
$this->assertEquals(6, $tc->create(array('title' => 'Task #6', 'project_id' => 1, 'column_id' => 4, 'score' => 3, 'swimlane_id' => 1)));
$board = $b->getBoard(1);
$this->assertNotEmpty($board);
$this->assertEquals(2, count($board));
$this->assertEquals(5, count($board[0]));
$this->assertTrue(array_key_exists('nb_tasks', $board[0]));
$this->assertTrue(array_key_exists('name', $board[0]));
$this->assertTrue(array_key_exists('columns', $board[0]));
$this->assertTrue(array_key_exists('tasks', $board[0]['columns'][2]));
$this->assertTrue(array_key_exists('title', $board[0]['columns'][2]));
$this->assertEquals(6, count($board[0]));
$this->assertArrayHasKey('name', $board[0]);
$this->assertArrayHasKey('nb_tasks', $board[0]);
$this->assertArrayHasKey('columns', $board[0]);
$this->assertArrayHasKey('tasks', $board[0]['columns'][2]);
$this->assertArrayHasKey('nb_tasks', $board[0]['columns'][2]);
$this->assertArrayHasKey('title', $board[0]['columns'][2]);
$this->assertArrayHasKey('nb_column_tasks', $board[0]['columns'][0]);
$this->assertArrayNotHasKey('nb_column_tasks', $board[1]['columns'][0]);
$this->assertArrayNotHasKey('total_score', $board[1]['columns'][0]);
$this->assertArrayHasKey('score', $board[0]['columns'][3]);
$this->assertArrayHasKey('total_score', $board[0]['columns'][3]);
$this->assertEquals(2, $board[0]['columns'][3]['score']);
$this->assertEquals(5, $board[0]['columns'][3]['total_score']);
$task = $tf->getById(1);
$this->assertEquals(1, $task['id']);