summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Controller/Board.php2
-rw-r--r--app/Locale/zh_CN/translations.php222
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Postgres.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/Template/app/projects.php10
-rw-r--r--app/Template/board/edit.php8
-rw-r--r--app/Template/board/swimlane.php2
-rw-r--r--app/Template/board/task_footer.php10
-rw-r--r--app/Template/layout.php8
-rw-r--r--app/Template/project/edit.php23
-rw-r--r--app/Template/project/index.php8
-rw-r--r--app/Template/project/show.php12
-rw-r--r--assets/js/app.js14
-rw-r--r--assets/js/src/base.js16
-rw-r--r--docs/api-json-rpc.markdown284
-rw-r--r--jsonrpc.php22
17 files changed, 518 insertions, 144 deletions
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 90b7f357..a6e002f2 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -127,6 +127,7 @@ class Board extends Base
'swimlanes' => $this->board->getBoard($project['id']),
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'],
+ 'description' => $project['description'],
'no_layout' => true,
'not_editable' => true,
'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'),
@@ -187,6 +188,7 @@ class Board extends Base
'swimlanes' => $this->board->getBoard($project['id']),
'categories' => $this->category->getList($project['id'], true, true),
'title' => $project['name'],
+ 'description' => $project['description'],
'board_selector' => $board_selector,
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index cd1e2ebe..2bb16fee 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -379,7 +379,7 @@ return array(
'Created by %s' => '创建者:%s',
'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M',
'Tasks Export' => '任务导出',
- 'Tasks exportation for "%s"' => '导出任务"%s"',
+ 'Tasks exportation for "%s"' => '导出"%s"的任务',
'Start Date' => '开始时间',
'End Date' => '结束时间',
'Execute' => '执行',
@@ -408,13 +408,13 @@ return array(
'Comment updated' => '更新了评论',
'New comment posted by %s' => '%s 的新评论',
'List of due tasks for the project "%s"' => '项目"%s"的到期任务列表',
- // 'New attachment' => '',
- // 'New comment' => '',
- // 'New subtask' => '',
- // 'Subtask updated' => '',
- // 'Task updated' => '',
- // 'Task closed' => '',
- // 'Task opened' => '',
+ 'New attachment' => '新建附件',
+ 'New comment' => '新建评论',
+ 'New subtask' => '新建子任务',
+ 'Subtask updated' => '子任务更新',
+ 'Task updated' => '任务更新',
+ 'Task closed' => '任务关闭',
+ 'Task opened' => '任务开启',
'[%s][Due tasks]' => '[%s][到期任务]',
'[Kanboard] Notification' => '[Kanboard] 通知',
'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:',
@@ -498,9 +498,9 @@ return array(
'Task assignee change' => '任务分配变更',
'%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s',
'%s changed the assignee of the task %s to %s' => '%s 将任务 %s 分配给 %s',
- // 'Column Change' => '',
- // 'Position Change' => '',
- // 'Assignee Change' => '',
+ 'Column Change' => '栏目变更',
+ 'Position Change' => '位置变更',
+ 'Assignee Change' => '负责人变更',
'New password for the user "%s"' => '用户"%s"的新密码',
'Choose an event' => '选择一个事件',
'Github commit received' => '收到了Github提交',
@@ -607,8 +607,8 @@ return array(
'Default swimlane' => '默认泳道',
'Do you really want to remove this swimlane: "%s"?' => '确定要删除泳道:"%s"?',
'Inactive swimlanes' => '非活动泳道',
- // 'Set project manager' => '',
- // 'Set project member' => '',
+ 'Set project manager' => '设为项目经理',
+ 'Set project member' => '设为项目成员',
'Remove a swimlane' => '删除泳道',
'Rename' => '重命名',
'Show default swimlane' => '显示默认泳道',
@@ -622,92 +622,92 @@ return array(
'Unable to remove this swimlane.' => '无法删除此泳道',
'Unable to update this swimlane.' => '无法更新此泳道',
'Your swimlane have been created successfully.' => '已经成功创建泳道。',
- // 'Example: "Bug, Feature Request, Improvement"' => '',
- // 'Default categories for new projects (Comma-separated)' => '',
- // 'Gitlab commit received' => '',
- // 'Gitlab issue opened' => '',
- // 'Gitlab issue closed' => '',
- // 'Gitlab webhooks' => '',
- // 'Help on Gitlab webhooks' => '',
- // 'Integrations' => '',
- // 'Integration with third-party services' => '',
- // 'Role for this project' => '',
- // 'Project manager' => '',
- // 'Project member' => '',
- // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
- // 'Gitlab Issue' => '',
- // 'Subtask Id' => '',
- // 'Subtasks' => '',
- // 'Subtasks Export' => '',
- // 'Subtasks exportation for "%s"' => '',
- // 'Task Title' => '',
- // 'Untitled' => '',
- // 'Application default' => '',
- // 'Language:' => '',
- // 'Timezone:' => '',
- // 'All columns' => '',
- // 'Calendar for "%s"' => '',
- // 'Filter by column' => '',
- // 'Filter by status' => '',
- // 'Calendar' => '',
- // 'Next' => '',
- // '#%d' => '',
- // 'Filter by color' => '',
- // 'Filter by swimlane' => '',
- // 'All swimlanes' => '',
- // 'All colors' => '',
- // 'All status' => '',
- // 'Add a comment logging moving the task between columns' => '',
- // 'Moved to column %s' => '',
- // 'Change description' => '',
- // 'User dashboard' => '',
- // 'Allow only one subtask in progress at the same time for a user' => '',
- // 'Edit column "%s"' => '',
- // 'Enable time tracking for subtasks' => '',
- // 'Select the new status of the subtask: "%s"' => '',
- // 'Subtask timesheet' => '',
- // 'There is nothing to show.' => '',
- // 'Time Tracking' => '',
- // 'You already have one subtask in progress' => '',
- // 'Which parts of the project do you want to duplicate?' => '',
- // 'Change dashboard view' => '',
- // 'Show/hide activities' => '',
- // 'Show/hide projects' => '',
- // 'Show/hide subtasks' => '',
- // 'Show/hide tasks' => '',
- // 'Disable login form' => '',
- // 'Show/hide calendar' => '',
- // 'User calendar' => '',
- // 'Bitbucket commit received' => '',
- // 'Bitbucket webhooks' => '',
- // 'Help on Bitbucket webhooks' => '',
- // 'Start' => '',
- // 'End' => '',
- // 'Task age in days' => '',
- // 'Days in this column' => '',
- // '%dd' => '',
- // 'Add a link' => '',
- // 'Add a new link' => '',
- // 'Do you really want to remove this link: "%s"?' => '',
- // 'Do you really want to remove this link with task #%d?' => '',
- // 'Field required' => '',
- // 'Link added successfully.' => '',
- // 'Link updated successfully.' => '',
- // 'Link removed successfully.' => '',
- // 'Link labels' => '',
- // 'Link modification' => '',
- // 'Links' => '',
- // 'Link settings' => '',
- // 'Opposite label' => '',
- // 'Remove a link' => '',
- // 'Task\'s links' => '',
- // 'The labels must be different' => '',
- // 'There is no link.' => '',
- // 'This label must be unique' => '',
- // 'Unable to create your link.' => '',
- // 'Unable to update your link.' => '',
- // 'Unable to remove this link.' => '',
- // 'relates to' => '',
+ 'Example: "Bug, Feature Request, Improvement"' => '示例:“缺陷,功能需求,提升',
+ 'Default categories for new projects (Comma-separated)' => '新项目的默认分类(用逗号分隔)',
+ 'Gitlab commit received' => '收到 Gitlab 提交',
+ 'Gitlab issue opened' => '开启 Gitlab 问题',
+ 'Gitlab issue closed' => '关闭 Gitlab 问题',
+ 'Gitlab webhooks' => 'Gitlab 网络钩子',
+ 'Help on Gitlab webhooks' => 'Gitlab 网络钩子帮助',
+ 'Integrations' => '整合',
+ 'Integration with third-party services' => '与第三方服务进行整合',
+ 'Role for this project' => '项目角色',
+ 'Project manager' => '项目管理员',
+ 'Project member' => '项目成员',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => '项目经理可以修改项目的设置,比标准用户多了一些权限',
+ 'Gitlab Issue' => 'Gitlab 问题',
+ 'Subtask Id' => '子任务 Id',
+ 'Subtasks' => '子任务',
+ 'Subtasks Export' => '子任务导出',
+ 'Subtasks exportation for "%s"' => '导出"%s"的子任务',
+ 'Task Title' => '任务标题',
+ 'Untitled' => '无标题',
+ 'Application default' => '程序默认',
+ 'Language:' => '语言:',
+ 'Timezone:' => '时区:',
+ 'All columns' => '全部栏目',
+ 'Calendar for "%s"' => '"%s"的日程表',
+ 'Filter by column' => '按栏目过滤',
+ 'Filter by status' => '按状态过滤',
+ 'Calendar' => '日程表',
+ 'Next' => '前进',
+ '#%d' => '#%d',
+ 'Filter by color' => '按颜色过滤',
+ 'Filter by swimlane' => '按泳道过滤',
+ 'All swimlanes' => '全部泳道',
+ 'All colors' => '全部颜色',
+ 'All status' => '全部状态',
+ 'Add a comment logging moving the task between columns' => '在不同栏目间移动任务时添加一个评论',
+ 'Moved to column %s' => '移动到栏目 %s',
+ 'Change description' => '修改描述',
+ 'User dashboard' => '用户仪表板',
+ 'Allow only one subtask in progress at the same time for a user' => '每用户同时仅有一个活动子任务',
+ 'Edit column "%s"' => '编辑栏目"%s"',
+ 'Enable time tracking for subtasks' => '启用子任务的时间记录',
+ 'Select the new status of the subtask: "%s"' => '选择子任务的新状态:"%s"',
+ 'Subtask timesheet' => '子任务时间',
+ 'There is nothing to show.' => '无内容。',
+ 'Time Tracking' => '时间记录',
+ 'You already have one subtask in progress' => '你已经有了一个进行中的子任务',
+ 'Which parts of the project do you want to duplicate?' => '要复制项目的哪些内容?',
+ 'Change dashboard view' => '修改仪表板视图',
+ 'Show/hide activities' => '显示/隐藏活动',
+ 'Show/hide projects' => '显示/隐藏项目',
+ 'Show/hide subtasks' => '显示/隐藏子任务',
+ 'Show/hide tasks' => '显示/隐藏任务',
+ 'Disable login form' => '禁用登录界面',
+ 'Show/hide calendar' => '显示/隐藏日程表',
+ 'User calendar' => '用户日程表',
+ 'Bitbucket commit received' => '收到Bitbucket提交',
+ 'Bitbucket webhooks' => 'Bitbucket网络钩子',
+ 'Help on Bitbucket webhooks' => 'Bitbucket网络钩子帮助',
+ 'Start' => '开始',
+ 'End' => '结束',
+ 'Task age in days' => '任务存在天数',
+ 'Days in this column' => '在此栏目的天数',
+ '%dd' => '%d天',
+ 'Add a link' => '添加一个关联',
+ 'Add a new link' => '添加一个新关联',
+ 'Do you really want to remove this link: "%s"?' => '确认要删除此关联吗:"%s"?',
+ 'Do you really want to remove this link with task #%d?' => '确认要删除到任务 #%d 的关联吗?',
+ 'Field required' => '必须的字段',
+ 'Link added successfully.' => '成功添加关联。',
+ 'Link updated successfully.' => '成功更新关联。',
+ 'Link removed successfully.' => '成功删除关联。',
+ 'Link labels' => '关联标签',
+ 'Link modification' => '关联修改',
+ 'Links' => '关联',
+ 'Link settings' => '关联设置',
+ 'Opposite label' => '反向标签',
+ 'Remove a link' => '删除关联',
+ 'Task\'s links' => '任务的关联',
+ 'The labels must be different' => '标签不能一样',
+ 'There is no link.' => '没有关联',
+ 'This label must be unique' => '关联必须唯一',
+ 'Unable to create your link.' => '无法创建关联。',
+ 'Unable to update your link.' => '无法更新关联。',
+ 'Unable to remove this link.' => '无法删除关联。',
+ 'relates to' => '关联到',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
@@ -718,20 +718,20 @@ return array(
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
- // 'This task' => '',
+ 'This task' => '此任务',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
- // 'Expand tasks' => '',
- // 'Collapse tasks' => '',
- // 'Expand/collapse tasks' => '',
- // 'Close dialog box' => '',
- // 'Submit a form' => '',
- // 'Board view' => '',
- // 'Keyboard shortcuts' => '',
- // 'Open board switcher' => '',
- // 'Application' => '',
- // 'Filter recently updated' => '',
+ 'Expand tasks' => '展开任务',
+ 'Collapse tasks' => '收缩任务',
+ 'Expand/collapse tasks' => '展开/收缩任务',
+ 'Close dialog box' => '关闭对话框',
+ 'Submit a form' => '提交表单',
+ 'Board view' => '面板视图',
+ 'Keyboard shortcuts' => '键盘快捷方式',
+ 'Open board switcher' => '打开面板切换器',
+ 'Application' => '应用程序',
+ 'Filter recently updated' => '过滤最近的更新',
// 'since %B %e, %Y at %k:%M %p' => '',
- // 'More filters' => '',
+ 'More filters' => '更多过滤',
);
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 947a62b3..eeab24d6 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 46;
+const VERSION = 47;
+
+function version_47($pdo)
+{
+ $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT');
+}
function version_46($pdo)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 027401ff..c3e8fbda 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 27;
+const VERSION = 28;
+
+function version_28($pdo)
+{
+ $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT');
+}
function version_27($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index c6dec33f..eefa0ae1 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,12 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 45;
+const VERSION = 46;
+
+function version_46($pdo)
+{
+ $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT');
+}
function version_45($pdo)
{
diff --git a/app/Template/app/projects.php b/app/Template/app/projects.php
index 4740c4b8..92675729 100644
--- a/app/Template/app/projects.php
+++ b/app/Template/app/projects.php
@@ -17,9 +17,15 @@
<?php if ($this->isManager($project['id'])): ?>
<?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>&nbsp;
<?php endif ?>
-
+
<?= $this->a('<i class="fa fa-calendar"></i>', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?>&nbsp;
+
<?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?>
+ <?php if (! empty($project['description'])): ?>
+ <span class="column-tooltip" title='<?= $this->markdown($project['description']) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
</td>
<td class="dashboard-project-stats">
<?php foreach ($project['columns'] as $column): ?>
@@ -32,4 +38,4 @@
</table>
<?= $paginator ?>
-<?php endif ?> \ No newline at end of file
+<?php endif ?>
diff --git a/app/Template/board/edit.php b/app/Template/board/edit.php
index b9b1788a..cf0c28e6 100644
--- a/app/Template/board/edit.php
+++ b/app/Template/board/edit.php
@@ -13,7 +13,7 @@
<tr>
<td class="column-60"><?= $this->e($column['title']) ?>
<?php if (! empty($column['description'])): ?>
- <span class="column-tooltip" title="<?= $this->markdown($column['description']) ?>">
+ <span class="column-tooltip" title='<?= $this->markdown($column['description']) ?>'>
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
@@ -52,12 +52,12 @@
<?= $this->formLabel(t('Title'), 'title') ?>
<?= $this->formText('title', $values, $errors, array('required', 'maxlength="50"')) ?>
-
+
<?= $this->formLabel(t('Task limit'), 'task_limit') ?>
<?= $this->formNumber('task_limit', $values, $errors) ?>
-
+
<?= $this->formLabel(t('Description'), 'description') ?>
-
+
<div class="form-tabs">
<div class="write-area">
<?= $this->formTextarea('description', $values, $errors) ?>
diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php
index ec298e24..4be92e58 100644
--- a/app/Template/board/swimlane.php
+++ b/app/Template/board/swimlane.php
@@ -28,7 +28,7 @@
<?= $this->e($column['title']) ?>
<?php if (! empty($column['description'])): ?>
- <span class="column-tooltip pull-right" title="<?= $this->markdown($column['description']) ?>">
+ <span class="column-tooltip pull-right" title='<?= $this->markdown($column['description']) ?>'>
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php
index d413692c..8abea043 100644
--- a/app/Template/board/task_footer.php
+++ b/app/Template/board/task_footer.php
@@ -22,19 +22,19 @@
<?php endif ?>
<?php if (! empty($task['nb_links'])): ?>
- <span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_links'] ?> <i class="fa fa-code-fork"></i></span>
+ <span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i>&nbsp;<?= $task['nb_links'] ?></span>
<?php endif ?>
<?php if (! empty($task['nb_subtasks'])): ?>
- <span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?> <i class="fa fa-bars"></i></span>
+ <span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-bars"></i>&nbsp;<?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?></span>
<?php endif ?>
<?php if (! empty($task['nb_files'])): ?>
- <span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_files'] ?> <i class="fa fa-paperclip"></i></span>
+ <span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-paperclip"></i>&nbsp;<?= $task['nb_files'] ?></span>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
- <span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_comments'] ?> <i class="fa fa-comment-o"></i></span>
+ <span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-comment-o"></i>&nbsp;<?= $task['nb_comments'] ?></span>
<?php endif ?>
<?php if (! empty($task['description'])): ?>
@@ -42,4 +42,4 @@
<i class="fa fa-file-text-o"></i>
</span>
<?php endif ?>
-</div> \ No newline at end of file
+</div>
diff --git a/app/Template/layout.php b/app/Template/layout.php
index ad4c4084..6c88d33b 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -35,7 +35,13 @@
<?php else: ?>
<header>
<nav>
- <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?></h1>
+ <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?>
+ <?php if (! empty($description)): ?>
+ <span class="column-tooltip" title='<?= $this->markdown($description) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+ </h1>
<ul>
<?php if (isset($board_selector) && ! empty($board_selector)): ?>
<li>
diff --git a/app/Template/project/edit.php b/app/Template/project/edit.php
index a1b945cd..37b03fe1 100644
--- a/app/Template/project/edit.php
+++ b/app/Template/project/edit.php
@@ -9,7 +9,28 @@
<?= $this->formLabel(t('Name'), 'name') ?>
<?= $this->formText('name', $values, $errors, array('required', 'maxlength="50"')) ?>
+ <?= $this->formLabel(t('Description'), 'description') ?>
+
+ <div class="form-tabs">
+
+ <div class="write-area">
+ <?= $this->formTextarea('description', $values, $errors) ?>
+ </div>
+ <div class="preview-area">
+ <div class="markdown"></div>
+ </div>
+ <ul class="form-tabs-nav">
+ <li class="form-tab form-tab-selected">
+ <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
+ </li>
+ <li class="form-tab">
+ <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
+ </li>
+ </ul>
+ </div>
+ <div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
+
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
-</form> \ No newline at end of file
+</form>
diff --git a/app/Template/project/index.php b/app/Template/project/index.php
index a36a9ce1..9a0f641c 100644
--- a/app/Template/project/index.php
+++ b/app/Template/project/index.php
@@ -39,7 +39,13 @@
<?php if ($project['is_private']): ?>
<i class="fa fa-lock fa-fw"></i>
<?php endif ?>
+
<?= $this->a($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?>
+ <?php if (! empty($project['description'])): ?>
+ <span class="column-tooltip" title='<?= $this->markdown($project['description']) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
</td>
<td class="dashboard-project-stats">
<?php foreach ($project['columns'] as $column): ?>
@@ -54,4 +60,4 @@
<?= $paginator ?>
<?php endif ?>
</section>
-</section> \ No newline at end of file
+</section>
diff --git a/app/Template/project/show.php b/app/Template/project/show.php
index b8bfd510..4ad710a8 100644
--- a/app/Template/project/show.php
+++ b/app/Template/project/show.php
@@ -50,7 +50,7 @@
<td>
<?= $this->e($column['title']) ?>
<?php if (! empty($column['description'])): ?>
- <span class="column-tooltip" title="<?= $this->markdown($column['description']) ?>">
+ <span class="column-tooltip" title='<?= $this->markdown($column['description']) ?>'>
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
@@ -60,3 +60,13 @@
</tr>
<?php endforeach ?>
</table>
+
+<?php if (! empty($project['description'])): ?>
+ <div class="page-header">
+ <h2><?= t('Description') ?></h2>
+ </div>
+
+ <article class="markdown">
+ <?= $this->markdown($project['description']) ?>
+ </article>
+<?php endif ?>
diff --git a/assets/js/app.js b/assets/js/app.js
index 64ea371d..a9b1e7b7 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -141,13 +141,13 @@ $("#popover-content").click(function(a){a.stopPropagation()});$(".close-popover"
return""!=a?"visible"==document[a]:!0},SetStorageItem:function(a,c){"undefined"!==typeof Storage&&localStorage.setItem(a,c)},GetStorageItem:function(a){return"undefined"!==typeof Storage?localStorage.getItem(a):""},MarkdownPreview:function(a){a.preventDefault();var c=$(this),b=$(this).closest("ul"),d=$(".write-area"),e=$(".preview-area"),f=$("textarea");$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:f.val()})}).done(function(a){b.find("li").removeClass("form-tab-selected");
c.parent().addClass("form-tab-selected");e.find(".markdown").html(a);e.css("height",f.css("height"));e.css("width",f.css("width"));d.hide();e.show()})},MarkdownWriter:function(a){a.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()},CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=
$("body").data("login-url")}}})},Init:function(){$("#board-selector").chosen({width:180});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(a){a.preventDefault();$("#board-selector").trigger("chosen:open")});$(".column-tooltip").tooltip({content:function(){return'<div class="markdown">'+
-$(this).attr("title")+"</div>"}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();
-$(".dropdown").not(".dropit").dropit()}}}();
-Kanboard.Board=function(){function a(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function c(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?(e(),Kanboard.SetStorageItem(d(),"collapsed")):(f(),Kanboard.SetStorageItem(d(),"expanded"))})}function b(){$(".filter-expand-link").click(function(a){a.preventDefault();f();
-Kanboard.SetStorageItem(d(),"expanded")});$(".filter-collapse-link").click(function(a){a.preventDefault();e();Kanboard.SetStorageItem(d(),"collapsed")});g()}function d(){return"board_stacking_"+$("#board").data("project-id")}function e(){$(".filter-collapse").hide();$(".task-board-collapsed").show();$(".filter-expand").show();$(".task-board-expanded").hide()}function f(){$(".filter-collapse").show();$(".task-board-collapsed").hide();$(".filter-expand").hide();$(".task-board-expanded").show()}function g(){"expanded"===
-(Kanboard.GetStorageItem(d())||"expanded")?f():e()}function k(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",stop:function(a,b){n(b.item.attr("data-task-id"),b.item.parent().attr("data-column-id"),b.item.index()+1,b.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",a);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,position:{my:"left-20 top",
-at:"center bottom+9",using:function(a,b){$(this).css(a);var c=b.target.left+b.target.width/2-b.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(b.vertical).addClass(0==c?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var b=this;$.get(a,function p(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var c=$(b).tooltip("option","position");c.of=$(b);a.position(c);
-$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),$(b).tooltip("close")):$.get($(this).attr("href"),p)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation();
+$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(0==b?"align-left":"align-right").appendTo(this)}}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$(".form-date").datepicker({showOtherMonths:!0,
+selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit()}}}();
+Kanboard.Board=function(){function a(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function c(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?(e(),Kanboard.SetStorageItem(d(),"collapsed")):(f(),Kanboard.SetStorageItem(d(),"expanded"))})}function b(){$(".filter-expand-link").click(function(a){a.preventDefault();
+f();Kanboard.SetStorageItem(d(),"expanded")});$(".filter-collapse-link").click(function(a){a.preventDefault();e();Kanboard.SetStorageItem(d(),"collapsed")});g()}function d(){return"board_stacking_"+$("#board").data("project-id")}function e(){$(".filter-collapse").hide();$(".task-board-collapsed").show();$(".filter-expand").show();$(".task-board-expanded").hide()}function f(){$(".filter-collapse").show();$(".task-board-collapsed").hide();$(".filter-expand").hide();$(".task-board-expanded").show()}
+function g(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?f():e()}function k(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",stop:function(a,b){n(b.item.attr("data-task-id"),b.item.parent().attr("data-column-id"),b.item.index()+1,b.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",a);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,
+position:{my:"left-20 top",at:"center bottom+9",using:function(a,b){$(this).css(a);var c=b.target.left+b.target.width/2-b.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(b.vertical).addClass(0==c?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var b=this;$.get(a,function p(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var c=$(b).tooltip("option","position");
+c.of=$(b);a.position(c);$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),$(b).tooltip("close")):$.get($(this).attr("href"),p)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation();
var b=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(b).tooltip("close")},100)});var b=parseInt($("#board").attr("data-check-interval"));0<b&&(l=window.setInterval(m,1E3*b))}function n(a,b,c,d){clearInterval(l);$.ajax({cache:!1,url:$("#board").attr("data-save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a,column_id:b,swimlane_id:d,position:c}),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();
k();h();g()}})}function m(){Kanboard.IsVisible()&&$.ajax({cache:!1,url:$("#board").attr("data-check-url"),statusCode:{200:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(l);k();h();g()}}})}function h(){var a=$("#form-user_id").val(),b=$("#form-category_id").val(),c=$("#more-filters option[value=filter-due-date]").is(":selected"),d=$("#more-filters option[value=filter-recent]").is(":selected"),e=$("#board").data("project-id");$("[data-task-id]").each(function(e,
g){var k=g.getAttribute("data-owner-id"),f=g.getAttribute("data-due-date"),m=g.getAttribute("data-category-id"),h=$(g).hasClass("task-board-recent");g.style.display=k!=a&&-1!=a?"none":"block";!c||""!=f&&"0"!=f||(g.style.display="none");m!=b&&-1!=b&&(g.style.display="none");d&&!h&&(g.style.display="none")});Kanboard.SetStorageItem("board_filter_"+e+"_form-user_id",a);Kanboard.SetStorageItem("board_filter_"+e+"_form-category_id",b);Kanboard.SetStorageItem("board_filter_"+e+"_filter-due-date",~~c);Kanboard.SetStorageItem("board_filter_"+
diff --git a/assets/js/src/base.js b/assets/js/src/base.js
index 75e04b3a..46d6cd9b 100644
--- a/assets/js/src/base.js
+++ b/assets/js/src/base.js
@@ -192,6 +192,22 @@ var Kanboard = (function() {
$(".column-tooltip").tooltip({
content: function() {
return '<div class="markdown">' + $(this).attr("title") + '</div>';
+ },
+ position: {
+ my: 'left-20 top',
+ at: 'center bottom+9',
+ using: function(position, feedback) {
+
+ $(this).css(position);
+
+ var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
+
+ $("<div>")
+ .addClass("tooltip-arrow")
+ .addClass(feedback.vertical)
+ .addClass(arrow_pos == 0 ? "align-left" : "align-right")
+ .appendTo(this);
+ }
}
});
diff --git a/docs/api-json-rpc.markdown b/docs/api-json-rpc.markdown
index 346b1dec..eba47dc7 100644
--- a/docs/api-json-rpc.markdown
+++ b/docs/api-json-rpc.markdown
@@ -176,6 +176,7 @@ Response example:
- Purpose: **Create a new project**
- Parameters:
- **name** (string, required)
+ - **description** (string, optional)
- Result on success: **project_id**
- Result on failure: **false**
@@ -235,7 +236,8 @@ Response example:
"is_active": "1",
"token": "",
"last_modified": "1410263246",
- "is_public": "0"
+ "is_public": "0",
+ "description": "A sample project"
}
}
```
@@ -273,7 +275,8 @@ Response example:
"is_active": "1",
"token": "",
"last_modified": "0",
- "is_public": "0"
+ "is_public": "0",
+ "description": "A sample project"
}
}
```
@@ -309,7 +312,8 @@ Response example:
"is_active": "1",
"token": "",
"last_modified": "0",
- "is_public": "0"
+ "is_public": "0",
+ "description": "PHP client project"
},
{
"id": "1",
@@ -317,7 +321,8 @@ Response example:
"is_active": "1",
"token": "",
"last_modified": "0",
- "is_public": "0"
+ "is_public": "0",
+ "description": "Test project"
}
]
}
@@ -332,6 +337,7 @@ Response example:
- **is_active** (integer, optional)
- **token** (string, optional)
- **is_public** (integer, optional)
+ - **description** (string, optional)
- Result on success: **true**
- Result on failure: **false**
@@ -973,6 +979,276 @@ Response example:
}
```
+### getSwimlanes
+
+- Purpose: **Get the list of enabled swimlanes of a project**
+- Parameters:
+ - **project_id** (integer, required)
+- Result on success: **swimlane properties**
+- Result on failure: **null**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "getSwimlanes",
+ "id": 1242049935,
+ "params": [
+ 2
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1242049935,
+ "result": [
+ {
+ "id": "0",
+ "name": "Default"
+ },
+ {
+ "id": "2",
+ "name": "Version 7.0"
+ },
+ ]
+}
+```
+
+### moveSwimlaneUp
+
+- Purpose: **Move up the swimlane position**
+- Parameters:
+ - **project_id** (integer, required)
+ - **swimlane_id** (integer, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "moveSwimlaneUp",
+ "id": 99275573,
+ "params": [
+ 1,
+ 2
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 99275573,
+ "result": true
+}
+```
+
+### moveSwimlaneDown
+
+- Purpose: **Move down the swimlane position**
+- Parameters:
+ - **project_id** (integer, required)
+ - **swimlane_id** (integer, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "moveSwimlaneDown",
+ "id": 957090649,
+ "params": {
+ "project_id": 1,
+ "swimlane_id": 2
+ }
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 957090649,
+ "result": true
+}
+```
+
+### updateSwimlane
+
+- Purpose: **Update swimlane properties**
+- Parameters:
+ - **swimlane_id** (integer, required)
+ - **name** (string, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "updateSwimlane",
+ "id": 480740641,
+ "params": [
+ 2,
+ "Version 4.1"
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 480740641,
+ "result": true
+}
+```
+
+### addSwimlane
+
+- Purpose: **Add a new swimlane**
+- Parameters:
+ - **project_id** (integer, required)
+ - **name** (string, required)
+- Result on success: **swimlane_id**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "addSwimlane",
+ "id": 638544704,
+ "params": [
+ 1,
+ "Version 1.0"
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 638544704,
+ "result": 5
+}
+```
+
+### removeSwimlane
+
+- Purpose: **Remove a swimlane**
+- Parameters:
+ - **project_id** (integer, required)
+ - **swimlane_id** (integer, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "removeSwimlane",
+ "id": 1433237746,
+ "params": [
+ 2,
+ 1
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1433237746,
+ "result": true
+}
+```
+
+### disableSwimlane
+
+- Purpose: **Enable a swimlane**
+- Parameters:
+ - **project_id** (integer, required)
+ - **swimlane_id** (integer, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "disableSwimlane",
+ "id": 1433237746,
+ "params": [
+ 2,
+ 1
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1433237746,
+ "result": true
+}
+```
+
+### enableSwimlane
+
+- Purpose: **Enable a swimlane**
+- Parameters:
+ - **project_id** (integer, required)
+ - **swimlane_id** (integer, required)
+- Result on success: **true**
+- Result on failure: **false**
+
+Request example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "enableSwimlane",
+ "id": 1433237746,
+ "params": [
+ 2,
+ 1
+ ]
+}
+```
+
+Response example:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1433237746,
+ "result": true
+}
+```
### createTask
diff --git a/jsonrpc.php b/jsonrpc.php
index 566976cd..03f007c2 100644
--- a/jsonrpc.php
+++ b/jsonrpc.php
@@ -22,13 +22,16 @@ $server->bind('enableProjectPublicAccess', $container['project'], 'enablePublicA
$server->bind('disableProjectPublicAccess', $container['project'], 'disablePublicAccess');
$server->bind('getProjectActivity', $container['projectActivity'], 'getProjects');
-$server->register('createProject', function($name) use ($container) {
- $values = array('name' => $name);
+$server->register('createProject', function($name, $description = null) use ($container) {
+ $values = array(
+ 'name' => $name,
+ 'description' => $description
+ );
list($valid,) = $container['project']->validateCreation($values);
return $valid ? $container['project']->create($values) : false;
});
-$server->register('updateProject', function($id, $name, $is_active = null, $is_public = null, $token = null) use ($container) {
+$server->register('updateProject', function($id, $name, $is_active = null, $is_public = null, $token = null, $description = null) use ($container) {
$values = array(
'id' => $id,
@@ -36,6 +39,7 @@ $server->register('updateProject', function($id, $name, $is_active = null, $is_p
'is_active' => $is_active,
'is_public' => $is_public,
'token' => $token,
+ 'description' => $description
);
foreach ($values as $key => $value) {
@@ -61,6 +65,18 @@ $server->bind('addColumn', $container['board'], 'addColumn');
$server->bind('removeColumn', $container['board'], 'removeColumn');
/**
+ * Swimlane procedures
+ */
+$server->bind('getSwimlanes', $container['swimlane'], 'getSwimlanes');
+$server->bind('addSwimlane', $container['swimlane'], 'create');
+$server->bind('updateSwimlane', $container['swimlane'], 'rename');
+$server->bind('removeSwimlane', $container['swimlane'], 'remove');
+$server->bind('disableSwimlane', $container['swimlane'], 'disable');
+$server->bind('enableSwimlane', $container['swimlane'], 'enable');
+$server->bind('moveSwimlaneUp', $container['swimlane'], 'moveUp');
+$server->bind('moveSwimlaneDown', $container['swimlane'], 'moveDown');
+
+/**
* Project permissions procedures
*/
$server->bind('getMembers', $container['projectPermission'], 'getMembers');