diff options
32 files changed, 616 insertions, 68 deletions
@@ -14,3 +14,7 @@ # RewriteCond %{HTTPS} !=on # RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L] </IfModule> + +<IfModule pagespeed_module> + ModPagespeed Off +</IfModule> @@ -4,11 +4,14 @@ Version 1.0.36 (unreleased) New features: * Add slideshow for images +* Add API calls to manage tags Improvements: +* Handle username with dots in user mentions * Replace Chosen jQuery plugin by custom UI component * Rewrite UI component that change user/group roles +* Disable PageSpeed module from .htaccess if present Bug fixes: diff --git a/app/Api/Authorization/ProjectAuthorization.php b/app/Api/Authorization/ProjectAuthorization.php index 21ecf311..7dcdc445 100644 --- a/app/Api/Authorization/ProjectAuthorization.php +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -23,13 +23,13 @@ class ProjectAuthorization extends Base protected function checkProjectPermission($class, $method, $project_id) { if (empty($project_id)) { - throw new AccessDeniedException('Project not found'); + throw new AccessDeniedException('Project Not Found'); } $role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId()); if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) { - throw new AccessDeniedException('Project access denied'); + throw new AccessDeniedException('Project Access Denied'); } } } diff --git a/app/Api/Authorization/TagAuthorization.php b/app/Api/Authorization/TagAuthorization.php new file mode 100644 index 00000000..247f57db --- /dev/null +++ b/app/Api/Authorization/TagAuthorization.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TagAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TagAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $tag_id) + { + if ($this->userSession->isLogged()) { + $tag = $this->tagModel->getById($tag_id); + + if (! empty($tag)) { + $this->checkProjectPermission($class, $method, $tag['project_id']); + } + } + } +} diff --git a/app/Api/Authorization/TaskAuthorization.php b/app/Api/Authorization/TaskAuthorization.php index db93b76b..6e044211 100644 --- a/app/Api/Authorization/TaskAuthorization.php +++ b/app/Api/Authorization/TaskAuthorization.php @@ -10,10 +10,10 @@ namespace Kanboard\Api\Authorization; */ class TaskAuthorization extends ProjectAuthorization { - public function check($class, $method, $category_id) + public function check($class, $method, $task_id) { if ($this->userSession->isLogged()) { - $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($category_id)); + $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($task_id)); } } } diff --git a/app/Api/Middleware/AuthenticationMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php index c4fa874a..174dc467 100644 --- a/app/Api/Middleware/AuthenticationMiddleware.php +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -31,7 +31,7 @@ class AuthenticationMiddleware extends Base implements MiddlewareInterface $this->sessionStorage->scope = 'API'; if ($this->isUserAuthenticated($username, $password)) { - $this->userSession->initialize($this->userModel->getByUsername($username)); + $this->userSession->initialize($this->userCacheDecorator->getByUsername($username)); } elseif (! $this->isAppAuthenticated($username, $password)) { $this->logger->error('API authentication failure for '.$username); throw new AuthenticationFailureException('Wrong credentials'); diff --git a/app/Api/Procedure/TagProcedure.php b/app/Api/Procedure/TagProcedure.php new file mode 100644 index 00000000..f1c06d01 --- /dev/null +++ b/app/Api/Procedure/TagProcedure.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TagAuthorization; + +/** + * Class TagProcedure + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TagProcedure extends BaseProcedure +{ + public function getAllTags() + { + return $this->tagModel->getAll(); + } + + public function getTagsByProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTagsByProject', $project_id); + return $this->tagModel->getAllByProject($project_id); + } + + public function createTag($project_id, $tag) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTag', $project_id); + return $this->tagModel->findOrCreateTag($project_id, $tag); + } + + public function updateTag($tag_id, $tag) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTag', $tag_id); + return $this->tagModel->update($tag_id, $tag); + } + + public function removeTag($tag_id) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTag', $tag_id); + return $this->tagModel->remove($tag_id); + } +} diff --git a/app/Api/Procedure/TaskProcedure.php b/app/Api/Procedure/TaskProcedure.php index ee9242d1..af67f3de 100644 --- a/app/Api/Procedure/TaskProcedure.php +++ b/app/Api/Procedure/TaskProcedure.php @@ -87,9 +87,9 @@ class TaskProcedure extends BaseProcedure } public function createTask($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, - $date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0, $priority = 0, - $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, - $recurrence_basedate = 0, $reference = '') + $date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0, $priority = 0, + $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, + $recurrence_basedate = 0, $reference = '', array $tags = array()) { ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id); @@ -120,6 +120,7 @@ class TaskProcedure extends BaseProcedure 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, + 'tags' => $tags, ); list($valid, ) = $this->taskValidator->validateCreation($values); @@ -128,9 +129,9 @@ class TaskProcedure extends BaseProcedure } public function updateTask($id, $title = null, $color_id = null, $owner_id = null, - $date_due = null, $description = null, $category_id = null, $score = null, $priority = null, - $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, - $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null) + $date_due = null, $description = null, $category_id = null, $score = null, $priority = null, + $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, + $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null, $tags = null) { TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id); $project_id = $this->taskFinderModel->getProjectId($id); @@ -159,6 +160,7 @@ class TaskProcedure extends BaseProcedure 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, + 'tags' => $tags, )); list($valid) = $this->taskValidator->validateApiModification($values); diff --git a/app/Api/Procedure/TaskTagProcedure.php b/app/Api/Procedure/TaskTagProcedure.php new file mode 100644 index 00000000..8596f507 --- /dev/null +++ b/app/Api/Procedure/TaskTagProcedure.php @@ -0,0 +1,26 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\TaskAuthorization; + +/** + * Class TaskTagProcedure + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TaskTagProcedure extends BaseProcedure +{ + public function setTaskTags($project_id, $task_id, array $tags) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setTaskTags', $task_id); + return $this->taskTagModel->save($project_id, $task_id, $tags); + } + + public function getTaskTags($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskTags', $task_id); + return $this->taskTagModel->getList($task_id); + } +} diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php index 02afc302..bf71e71e 100644 --- a/app/Auth/ReverseProxyAuth.php +++ b/app/Auth/ReverseProxyAuth.php @@ -45,7 +45,7 @@ class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterfac $username = $this->request->getRemoteUser(); if (! empty($username)) { - $userProfile = $this->userModel->getByUsername($username); + $userProfile = $this->userCacheDecorator->getByUsername($username); $this->userInfo = new ReverseProxyUserProvider($username, $userProfile ?: array()); return true; } diff --git a/app/Controller/PasswordResetController.php b/app/Controller/PasswordResetController.php index a1780ed9..6189f946 100644 --- a/app/Controller/PasswordResetController.php +++ b/app/Controller/PasswordResetController.php @@ -109,7 +109,7 @@ class PasswordResetController extends BaseController $token = $this->passwordResetModel->create($username); if ($token !== false) { - $user = $this->userModel->getByUsername($username); + $user = $this->userCacheDecorator->getByUsername($username); $this->emailClient->send( $user['email'], diff --git a/app/Controller/ProjectFileController.php b/app/Controller/ProjectFileController.php index cbe48679..9c38f684 100644 --- a/app/Controller/ProjectFileController.php +++ b/app/Controller/ProjectFileController.php @@ -21,7 +21,7 @@ class ProjectFileController extends BaseController $this->response->html($this->template->render('project_file/create', array( 'project' => $project, - 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), + 'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), ))); } diff --git a/app/Controller/TaskFileController.php b/app/Controller/TaskFileController.php index 77c0c026..8a0971e4 100644 --- a/app/Controller/TaskFileController.php +++ b/app/Controller/TaskFileController.php @@ -40,7 +40,7 @@ class TaskFileController extends BaseController $this->response->html($this->template->render('task_file/create', array( 'task' => $task, - 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), + 'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), ))); } diff --git a/app/Controller/TaskImportController.php b/app/Controller/TaskImportController.php index aff2d390..3ce275a5 100644 --- a/app/Controller/TaskImportController.php +++ b/app/Controller/TaskImportController.php @@ -27,7 +27,7 @@ class TaskImportController extends BaseController 'project' => $project, 'values' => $values, 'errors' => $errors, - 'max_size' => ini_get('upload_max_filesize'), + 'max_size' => get_upload_max_size(), 'delimiters' => Csv::getDelimiters(), 'enclosures' => Csv::getEnclosures(), 'title' => t('Import tasks from CSV file'), diff --git a/app/Controller/UserImportController.php b/app/Controller/UserImportController.php index fec9a31d..6a9d5992 100644 --- a/app/Controller/UserImportController.php +++ b/app/Controller/UserImportController.php @@ -23,7 +23,7 @@ class UserImportController extends BaseController $this->response->html($this->template->render('user_import/show', array( 'values' => $values, 'errors' => $errors, - 'max_size' => ini_get('upload_max_filesize'), + 'max_size' => get_upload_max_size(), 'delimiters' => Csv::getDelimiters(), 'enclosures' => Csv::getEnclosures(), ))); diff --git a/app/Core/Base.php b/app/Core/Base.php index e7ccafaa..881cccbd 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -58,7 +58,8 @@ use Pimple\Container; * @property \Kanboard\Core\Paginator $paginator * @property \Kanboard\Core\Template $template * @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator - * @property \Kanboard\Decorator\columnRestrictionCacheDecorator $columnRestrictionCacheDecorator + * @property \Kanboard\Decorator\UserCacheDecorator $userCacheDecorator + * @property \Kanboard\Decorator\ColumnRestrictionCacheDecorator $columnRestrictionCacheDecorator * @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator * @property \Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator $projectRoleRestrictionCacheDecorator * @property \Kanboard\Model\ActionModel $actionModel diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php index 799aefb4..4487bf2a 100644 --- a/app/Core/Markdown.php +++ b/app/Core/Markdown.php @@ -86,18 +86,23 @@ class Markdown extends Parsedown */ protected function inlineUserLink(array $Excerpt) { - if (! $this->isPublicLink && preg_match('/^@([^\s,!.:?]+)/', $Excerpt['text'], $matches)) { - $user_id = $this->container['userModel']->getIdByUsername($matches[1]); + if (! $this->isPublicLink && preg_match('/^@([^\s,!:?]+)/', $Excerpt['text'], $matches)) { + $username = rtrim($matches[1], '.'); + $user = $this->container['userCacheDecorator']->getByUsername($username); - if (! empty($user_id)) { - $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user_id)); + if (! empty($user)) { + $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user['id'])); return array( - 'extent' => strlen($matches[0]), + 'extent' => strlen($username) + 1, 'element' => array( - 'name' => 'a', - 'text' => $matches[0], - 'attributes' => array('href' => $url, 'class' => 'user-mention-link'), + 'name' => 'a', + 'text' => '@' . $username, + 'attributes' => array( + 'href' => $url, + 'class' => 'user-mention-link', + 'title' => $user['name'] ?: $user['username'], + ), ), ); } diff --git a/app/Decorator/UserCacheDecorator.php b/app/Decorator/UserCacheDecorator.php new file mode 100644 index 00000000..1cfe31c9 --- /dev/null +++ b/app/Decorator/UserCacheDecorator.php @@ -0,0 +1,59 @@ +<?php + +namespace Kanboard\Decorator; + +use Kanboard\Core\Cache\CacheInterface; +use Kanboard\Model\UserModel; + +/** + * Class UserCacheDecorator + * + * @package Kanboard\Decorator + * @author Frederic Guillot + */ +class UserCacheDecorator +{ + protected $cachePrefix = 'user_model:'; + + /** + * @var CacheInterface + */ + protected $cache; + + /** + * @var UserModel + */ + private $userModel; + + /** + * UserCacheDecorator constructor. + * + * @param CacheInterface $cache + * @param UserModel $userModel + */ + public function __construct(CacheInterface $cache, UserModel $userModel) + { + $this->cache = $cache; + $this->userModel = $userModel; + } + + /** + * Get a specific user by the username + * + * @access public + * @param string $username Username + * @return array + */ + public function getByUsername($username) + { + $key = $this->cachePrefix.$username; + $user = $this->cache->get($key); + + if ($user === null) { + $user = $this->userModel->getByUsername($username); + $this->cache->set($key, $user); + } + + return $user; + } +} diff --git a/app/Job/UserMentionJob.php b/app/Job/UserMentionJob.php index bbb27131..355095bb 100644 --- a/app/Job/UserMentionJob.php +++ b/app/Job/UserMentionJob.php @@ -58,7 +58,8 @@ class UserMentionJob extends BaseJob { $users = array(); - if (preg_match_all('/@([^\s,!.:?]+)/', $text, $matches)) { + if (preg_match_all('/@([^\s,!:?]+)/', $text, $matches)) { + array_walk($matches[1], function (&$username) { $username = rtrim($username, '.'); }); $users = $this->db->table(UserModel::TABLE) ->columns('id', 'username', 'name', 'email', 'language') ->eq('notifications_enabled', 1) diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index d5d1f260..2c9abec7 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -10,6 +10,7 @@ use Kanboard\Api\Procedure\CategoryProcedure; use Kanboard\Api\Procedure\ColumnProcedure; use Kanboard\Api\Procedure\CommentProcedure; use Kanboard\Api\Procedure\ProjectFileProcedure; +use Kanboard\Api\Procedure\TagProcedure; use Kanboard\Api\Procedure\TaskExternalLinkProcedure; use Kanboard\Api\Procedure\TaskFileProcedure; use Kanboard\Api\Procedure\GroupProcedure; @@ -25,6 +26,7 @@ use Kanboard\Api\Procedure\SwimlaneProcedure; use Kanboard\Api\Procedure\TaskMetadataProcedure; use Kanboard\Api\Procedure\TaskProcedure; use Kanboard\Api\Procedure\TaskLinkProcedure; +use Kanboard\Api\Procedure\TaskTagProcedure; use Kanboard\Api\Procedure\UserProcedure; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -71,9 +73,11 @@ class ApiProvider implements ServiceProviderInterface ->withObject(new TaskLinkProcedure($container)) ->withObject(new TaskExternalLinkProcedure($container)) ->withObject(new TaskMetadataProcedure($container)) + ->withObject(new TaskTagProcedure($container)) ->withObject(new UserProcedure($container)) ->withObject(new GroupProcedure($container)) ->withObject(new GroupMemberProcedure($container)) + ->withObject(new TagProcedure($container)) ->withBeforeMethod('beforeProcedure') ; diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index c2dad0e6..80882456 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -136,7 +136,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('BoardViewController', 'readonly', Role::APP_PUBLIC); $acl->add('ICalendarController', '*', Role::APP_PUBLIC); $acl->add('FeedController', '*', Role::APP_PUBLIC); - $acl->add('AvatarFileController', 'show', Role::APP_PUBLIC); + $acl->add('AvatarFileController', array('show', 'image'), Role::APP_PUBLIC); $acl->add('ConfigController', '*', Role::APP_ADMIN); $acl->add('TagController', '*', Role::APP_ADMIN); @@ -210,6 +210,8 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskTagProcedure', array('setTaskTags'), Role::PROJECT_MEMBER); + $acl->add('TagProcedure', array('createTag', 'updateTag', 'removeTag'), Role::PROJECT_MEMBER); return $acl; } diff --git a/app/ServiceProvider/CacheProvider.php b/app/ServiceProvider/CacheProvider.php index e93dd502..af8a8e7a 100644 --- a/app/ServiceProvider/CacheProvider.php +++ b/app/ServiceProvider/CacheProvider.php @@ -8,6 +8,7 @@ use Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator; use Kanboard\Decorator\ColumnRestrictionCacheDecorator; use Kanboard\Decorator\MetadataCacheDecorator; use Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator; +use Kanboard\Decorator\UserCacheDecorator; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -40,6 +41,13 @@ class CacheProvider implements ServiceProviderInterface $container['cacheDriver'] = $container['memoryCache']; } + $container['userCacheDecorator'] = function($c) { + return new UserCacheDecorator( + $c['memoryCache'], + $c['userModel'] + ); + }; + $container['userMetadataCacheDecorator'] = function($c) { return new MetadataCacheDecorator( $c['cacheDriver'], diff --git a/app/functions.php b/app/functions.php index e732f308..9dd054fb 100644 --- a/app/functions.php +++ b/app/functions.php @@ -136,6 +136,16 @@ function build_app_version($ref, $commit_hash) } /** + * Get upload max size. + * + * @return string + */ +function get_upload_max_size() +{ + return min(ini_get('upload_max_filesize'), ini_get('post_max_size')); +} + +/** * Translate a string * * @return string diff --git a/assets/css/app.min.css b/assets/css/app.min.css index a79615aa..b1ff20c1 100644 --- a/assets/css/app.min.css +++ b/assets/css/app.min.css @@ -1 +1 @@ -h1,li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0;font-size:100%}body{margin-left:10px;margin-right:10px;padding-bottom:10px;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}small{font-size:0.8em}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,0.1);border-bottom:1px solid rgba(255,255,255,0.3)}.pull-right{text-align:right}ul.no-bullet li{list-style-type:none;margin-left:0}.chosen-select{min-height:27px}#app-loading-icon{position:fixed;right:3px;bottom:3px}.assign-me{vertical-align:bottom}a{color:#36c;border:none}a:focus{outline:0;color:#DF5353;text-decoration:none}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h1{font-size:1.5em}h2{font-size:1.4em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px}table.table-fixed{table-layout:fixed;white-space:nowrap}table.table-fixed th{overflow:hidden}table.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.table-small{font-size:0.8em}table.table-striped tr:nth-child(odd){background:#fefefe}@media (max-width: 768px){table.table-scrolling{overflow-x:auto;display:inline-block;vertical-align:top;max-width:100%;white-space:nowrap}}table th{text-align:left;padding:0.5em 3px;border:1px solid #eee;background:#fbfbfb}table th a{text-decoration:none;color:#333}table th a:focus,table th a:hover{text-decoration:underline}table td{border:1px solid #eee;padding:0.5em 3px;vertical-align:top}table td li{margin-left:20px}.column-1{width:1%}.column-2{width:2%}.column-3{width:3%}.column-4{width:4%}.column-5{width:5%}.column-6{width:6%}.column-7{width:7%}.column-8{width:8%}.column-9{width:9%}.column-10{width:10%}.column-11{width:11%}.column-12{width:12%}.column-13{width:13%}.column-14{width:14%}.column-15{width:15%}.column-16{width:16%}.column-17{width:17%}.column-18{width:18%}.column-19{width:19%}.column-20{width:20%}.column-21{width:21%}.column-22{width:22%}.column-23{width:23%}.column-24{width:24%}.column-25{width:25%}.column-26{width:26%}.column-27{width:27%}.column-28{width:28%}.column-29{width:29%}.column-30{width:30%}.column-31{width:31%}.column-32{width:32%}.column-33{width:33%}.column-34{width:34%}.column-35{width:35%}.column-36{width:36%}.column-37{width:37%}.column-38{width:38%}.column-39{width:39%}.column-40{width:40%}.column-41{width:41%}.column-42{width:42%}.column-43{width:43%}.column-44{width:44%}.column-45{width:45%}.column-46{width:46%}.column-47{width:47%}.column-48{width:48%}.column-49{width:49%}.column-50{width:50%}.column-51{width:51%}.column-52{width:52%}.column-53{width:53%}.column-54{width:54%}.column-55{width:55%}.column-56{width:56%}.column-57{width:57%}.column-58{width:58%}.column-59{width:59%}.column-60{width:60%}.column-61{width:61%}.column-62{width:62%}.column-63{width:63%}.column-64{width:64%}.column-65{width:65%}.column-66{width:66%}.column-67{width:67%}.column-68{width:68%}.column-69{width:69%}.column-70{width:70%}.column-71{width:71%}.column-72{width:72%}.column-73{width:73%}.column-74{width:74%}.column-75{width:75%}.column-76{width:76%}.column-77{width:77%}.column-78{width:78%}.column-79{width:79%}.column-80{width:80%}.column-81{width:81%}.column-82{width:82%}.column-83{width:83%}.column-84{width:84%}.column-85{width:85%}.column-86{width:86%}.column-87{width:87%}.column-88{width:88%}.column-89{width:89%}.column-90{width:90%}.column-91{width:91%}.column-92{width:92%}.column-93{width:93%}.column-94{width:94%}.column-95{width:95%}.column-96{width:96%}.column-97{width:97%}.column-98{width:98%}.column-99{width:99%}.column-100{width:100%}.draggable-row-handle{cursor:move;color:#dedede}.draggable-row-handle:hover{color:#333}tr.draggable-item-selected{background:#fff;border:2px solid #666;box-shadow:4px 2px 10px -4px rgba(0,0,0,0.55)}tr.draggable-item-selected td{border-top:none;border-bottom:none}tr.draggable-item-selected td:first-child{border-left:none}tr.draggable-item-selected td:last-child{border-right:none}.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover{background:#FEFFF2}form{margin-bottom:20px}label{cursor:pointer;display:block;margin-top:10px}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field){color:#999;border:1px solid #ccc;width:300px;max-width:95%;font-size:1em;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;-moz-appearance:none}input[type="number"]::-webkit-input-placeholder,input[type="date"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder{color:#dedede}input[type="number"]::-moz-placeholder,input[type="date"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="text"]:not(.input-addon-field)::-moz-placeholder{color:#dedede}input[type="number"]:-ms-input-placeholder,input[type="date"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="text"]:not(.input-addon-field):-ms-input-placeholder{color:#dedede}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}input[type="number"]{width:70px}input[type="text"]:not(.input-addon-field).form-numeric{width:70px}input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date{width:150px}input[type="text"]:not(.input-addon-field).form-input-large{width:400px}input[type="text"]:not(.input-addon-field).form-input-small{width:150px}textarea:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}textarea{padding:3px;border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-family:sans-serif;font-size:1em}textarea::-webkit-input-placeholder{color:#dedede}textarea::-moz-placeholder{color:#dedede}textarea:-ms-input-placeholder{color:#dedede}select{font-size:1.0em;max-width:95%}select:focus{outline:0}select[multiple]{width:300px}.tag-autocomplete{width:400px}span.select2-container{margin-top:2px}.form-actions{padding-top:20px;clear:both}.form-required{color:red;padding-left:5px;font-weight:bold}@media (max-width: 480px){.form-required{display:none}}input.form-error,textarea.form-error{border:2px solid #b94a48}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid #b94a48}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:0.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:none}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0 15px 0 0}.form-inline .form-required{display:none}.form-inline-group{display:inline}.form-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.form-columns .form-column{margin-right:25px;flex-grow:1}.form-login{max-width:350px;margin:8% auto 0}.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-weight:bold}.reset-password{margin-top:20px}.reset-password a{color:#999}.input-addon{display:flex}.input-addon-field{flex:1;font-size:1em;color:#999;margin:0;-webkit-appearance:none;-moz-appearance:none}.input-addon-field:first-child{border-radius:5px 0 0 5px}.input-addon-field:last-child{border-radius:0 5px 5px 0}.input-addon-item{background-color:rgba(147,128,108,0.1);color:#666;font:inherit;font-weight:normal}.input-addon-item:first-child{border-radius:5px 0 0 5px}.input-addon-item:last-child{border-radius:0 5px 5px 0}@media (max-width: 480px){.input-addon-item .dropdown .fa-caret-down{display:none}}.input-addon-field,.input-addon-item{border:1px solid rgba(147,128,108,0.25);padding:4px 0.75em}.input-addon-field:not(:first-child),.input-addon-item:not(:first-child){border-left:0}.icon-success{color:#468847}.icon-error{color:#b94a48}.icon-fade-out{opacity:1;animation:icon-fadeout 5s linear forwards}@keyframes icon-fadeout{0%{opacity:1}100%{opacity:0}}.alert{padding:8px 35px 8px 14px;margin-top:5px;margin-bottom:5px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-normal{color:#333;background-color:#f0f0f0;border-color:#ddd}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.alert-fade-out{text-align:center;position:fixed;bottom:0;left:20%;width:60%;padding-top:5px;padding-bottom:5px;margin-bottom:0;border-width:1px 0 0;border-radius:4px 4px 0 0;z-index:9999;opacity:1;animation:fadeout 5s linear forwards}@keyframes fadeout{0%{opacity:1}100%{opacity:0}}a.btn{text-decoration:none}.btn{-webkit-appearance:none;-moz-appearance:none;font-size:1.2em;font-weight:normal;cursor:pointer;display:inline-block;border-radius:2px;padding:3px 10px;margin:0;border:1px solid #ddd;background:#f5f5f5;color:#333}.btn:hover,.btn:focus{border-color:#bbb;background:#fafafa;color:#000}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}.btn-red:hover,.btn-red:focus{border-color:#b0281a;background:#c53727;color:#fff}.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}.btn-blue:hover,.btn-blue:focus{border-color:#3079ed;background:#357ae8;color:#fff}.btn:disabled{color:#ccc;border-color:#ccc;background:#f7f7f7}.buttons-header{font-size:0.8em;margin-top:5px;margin-bottom:15px}.tooltip-arrow:after{background:#fff;border:1px solid #aaaaaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px}.tooltip-arrow{width:20px;height:10px;overflow:hidden;position:absolute}.tooltip-arrow.top{top:-10px}.tooltip-arrow.bottom{bottom:-10px}.tooltip-arrow.align-left{left:10px}.tooltip-arrow.align-right{right:10px}.tooltip-arrow:after{content:"";position:absolute;width:14px;height:14px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.tooltip-arrow.bottom:after{top:-10px}.tooltip-arrow.top:after{bottom:-10px}.tooltip-arrow.align-left:after{left:0}.tooltip-arrow.align-right:after{right:0}.tooltip-large{width:600px}.ui-tooltip-content .markdown p{margin-bottom:0}.ui-tooltip li{list-style-type:none}.tooltip .fa-info-circle{color:#999}h2 .dropdown ul{display:none}.dropdown{display:inline;position:relative}.dropdown ul{display:none}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}.dropdown-submenu-open li{display:block;margin:0;padding:8px 10px;font-size:0.9em;border-bottom:1px solid #f8f8f8;cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.dropdown-submenu-open li:last-child{border:none}.dropdown-submenu-open li:not(.no-hover):hover{background:#4078C0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:#333;text-decoration:none}.dropdown-menu-link-text:hover{text-decoration:underline}.accordion-title{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NEQ5RDgxQzc2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NEQ5RDgxQzg2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0RDlEODFDNTZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0RDlEODFDNjZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvXFWFAAAAAYSURBVHjaYvj//z8D0/Pnz/8zgFgAAQYAS5UJscReGMIAAAAASUVORK5CYII=) repeat-x scroll 0 10px}.accordion-title h3{display:inline;padding-right:5px;background:#fff}.accordion-content{margin-top:15px;margin-bottom:25px}.accordion-toggle{color:#333;text-decoration:none}.accordion-toggle:focus{color:#333}.accordion-toggle:hover{color:#999}.accordion-toggle:before{content:"\f0d7"}.accordion-collapsed{margin-bottom:25px}.accordion-collapsed .accordion-toggle:before{content:"\f0da"}.accordion-collapsed .accordion-content{display:none}#select-dropdown-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,0.175);overflow:scroll}.select-dropdown-menu-item{white-space:nowrap;overflow:hidden;padding:3px 10px;color:#555;cursor:pointer;border-bottom:1px solid #f8f8f8;line-height:1.5em;font-weight:400}.select-dropdown-menu-item.active{color:#fff;background:#428bca}.select-dropdown-menu-item:last-child{border:none}.select-dropdown-input-container{position:relative;border:1px solid #ccc;border-radius:5px}.select-dropdown-input-container input.select-dropdown-input{margin:0 0 0 5px;border:none}.select-dropdown-input-container input.select-dropdown-input:focus{border:none;box-shadow:none}.select-dropdown-input-container .select-dropdown-chevron{color:#555;position:absolute;top:4px;right:5px;cursor:pointer}#suggest-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.suggest-menu-item{white-space:nowrap;padding:3px 10px;color:#333;font-weight:bold;cursor:pointer}.suggest-menu-item.active{color:#fff;background:#428bca}.suggest-menu-item.active small{color:#fff}.suggest-menu-item small{color:#999;font-weight:normal}#main .confirm{max-width:700px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);overflow:auto;z-index:100}#popover-content{position:fixed;width:950px;max-width:95%;max-height:calc(100% - 50px);top:5%;left:50%;transform:translateX(-50%);padding:0 15px 15px;background:#fff;overflow:auto}#popover-content-header{text-align:right}#popover-close-button{color:#333}#popover-close-button:hover{color:#b94a48}.popover-form{margin-bottom:0}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}header{box-sizing:border-box;display:flex;flex-wrap:wrap;margin-top:5px;margin-bottom:5px;border-bottom:1px solid #dedede}header>*{box-sizing:border-box}header>*{width:1%}header .menus-container{width:10%}@media (min-width: 768px) and (max-width: 1150px){header .menus-container{width:15%}}@media (max-width: 768px){header .menus-container{width:30%;order:2}}header .board-selector-container{width:25%}@media (min-width: 768px) and (max-width: 1150px){header .board-selector-container{width:20%}}@media (max-width: 768px){header .board-selector-container{width:70%;order:1;margin-bottom:5px}}header .title-container{width:65%}@media (max-width: 768px){header .title-container{width:100%;order:3}}header h1{font-size:1.5em}header h1 .tooltip{opacity:0.3;font-size:0.7em}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}.logo a{opacity:0.5;color:#d40000;text-decoration:none}.logo span{color:#333}.logo a:hover{opacity:0.8;color:#333}.logo a:focus span,.logo a:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header .dropdown{padding-right:10px}.page-header h2{margin:0;padding:0;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#333;text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:#999}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.page-header li{display:inline;padding-right:15px}@media (max-width: 480px){.page-header li{display:block;line-height:1.5em}}.page-header li.active a{color:#333;text-decoration:none;font-weight:bold}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}.menu-inline li{display:inline;padding-right:15px}.menu-inline li .active a{font-weight:bold;color:#000;text-decoration:none}.sidebar-container{box-sizing:border-box;display:flex;flex-wrap:wrap}.sidebar-container>*{box-sizing:border-box}.sidebar-container>*{width:1%}.sidebar-content{padding-left:10px;width:82%}@media (max-width: 480px){.sidebar-content{width:100%}}.sidebar{max-width:240px;min-width:190px;width:18%}@media (max-width: 480px){.sidebar{width:100%;max-width:99%;min-width:0}}.sidebar h2{margin-top:0}.sidebar>ul a{text-decoration:none;color:#999;font-weight:300}.sidebar>ul a:hover{color:#333}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef;padding-left:13px}.sidebar>ul li:hover{border-left:5px solid #555;padding-left:8px}.sidebar>ul li.active{border-left:5px solid #333;padding-left:8px}.sidebar>ul li.active a{color:#333;font-weight:bold}.sidebar-icons>ul li{padding-left:0}.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active{padding-left:0;border-left:none}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:#555}.sidebar>ul li:last-child{margin-bottom:15px}.avatar img{vertical-align:bottom}.avatar-left{float:left;margin-right:10px}.avatar-inline{display:inline-block;margin-right:3px}.avatar-48 img,.avatar-48 div{border-radius:30px}.avatar-48 .avatar-letter{line-height:48px;width:48px;font-size:25px}.avatar-20 img,.avatar-20 div{border-radius:10px}.avatar-20 .avatar-letter{line-height:20px;width:20px;font-size:11px}.avatar-letter{color:#fff;text-align:center}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li.file-error{font-weight:bold;color:#b94a48}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:4px 2px 10px -6px rgba(0,0,0,0.55);margin-right:15px}.file-thumbnail img{cursor:pointer;border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:0.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:0.9em;color:#555}.file-thumbnail-description{font-size:0.8em;color:#999;margin-top:8px;margin-bottom:5px}.file-viewer{position:relative}.file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.color-picker{width:180px}.color-picker-option{height:25px}.color-picker-square{display:inline-block;width:18px;height:18px;margin-right:5px;border:1px solid #000}.color-picker-label{display:inline-block;vertical-align:bottom;padding-bottom:3px}.filter-box{max-width:800px}.action-menu{color:#333;text-decoration:none}.action-menu:hover,.action-menu:focus{text-decoration:underline}.js-project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.project-overview-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin-bottom:20px;font-size:1.4em}@media (max-width: 480px){.project-overview-columns{display:block}}.project-overview-column{text-align:center;margin-right:3%;margin-top:5px;padding:3px 15px 3px 15px;border:1px dashed #ddd}@media (max-width: 480px){.project-overview-column{text-align:left}}.project-overview-column small{color:#999}.project-overview-column strong{color:#555;display:block}@media (max-width: 480px){.project-overview-column strong{display:inline}}.project-header{box-sizing:border-box;display:flex;flex-wrap:wrap;margin-bottom:8px}.project-header>*{box-sizing:border-box}.project-header>*{width:1%}.project-header .dropdown-component{margin-top:4px;width:5%}@media (min-width: 768px) and (max-width: 1150px){.project-header .dropdown-component{width:8%}}@media (max-width: 768px){.project-header .dropdown-component{width:100%}}.project-header .views-switcher-component{margin-top:4px;width:38%}@media (max-width: 1300px){.project-header .views-switcher-component{width:45%}}@media (min-width: 768px) and (max-width: 1150px){.project-header .views-switcher-component{width:92%}}@media (max-width: 768px){.project-header .views-switcher-component{margin-top:0;width:100%}}.project-header .filter-box-component{margin:0;width:55%}@media (max-width: 1300px){.project-header .filter-box-component{width:50%}}@media (min-width: 768px) and (max-width: 1150px){.project-header .filter-box-component{width:100%;margin-top:10px}.project-header .filter-box-component .filter-box{max-width:100%}}@media (max-width: 768px){.project-header .filter-box-component{width:100%;margin-top:10px}.project-header .filter-box-component .filter-box{max-width:100%}}.project-header .filter-box-component form{margin:0}.views{display:inline-block;margin-right:10px;font-size:0.9em}@media (max-width: 560px){.views{width:100%}}@media (max-width: 768px){.views{margin-top:10px;font-size:1em}}@media (max-width: 480px){.views{margin-top:5px}}.views li{white-space:nowrap;background:#fafafa;border:1px solid #ddd;border-right:none;padding:4px 8px;display:inline}@media (max-width: 560px){.views li{display:block;margin-top:5px;border-radius:5px;border:1px solid #ddd}}.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-right:1px solid #ddd;border-top-right-radius:5px;border-bottom-right-radius:5px}.views a{color:#555;text-decoration:none}.views a:hover{color:#333;text-decoration:underline}.dashboard-project-stats small{margin-right:10px;color:#999}.dashboard-table-link{font-weight:bold;color:#000;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.public-board{margin-top:5px}.public-task{max-width:800px;margin:5px auto 0}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast: active), (-ms-high-contrast: none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:1.6em;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}a.board-swimlane-toggle{text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:none}.board-task-list{min-height:60px}.board-task-list-limit{background-color:#DF5353}.draggable-item{cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;word-wrap:break-word;font-size:0.9em}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-board a{color:#000;text-decoration:none}.task-board .dropdown-menu{font-weight:bold}.task-board .task-score{font-weight:bold}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-board-title{margin-top:5px;margin-bottom:8px}.task-board-title a:hover{text-decoration:underline}.task-board-saving-state{opacity:0.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board-category-container{text-align:right;margin-top:8px;margin-bottom:8px}.task-board-category{font-weight:500;color:#000;border:1px solid #555;padding:1px 2px 1px 2px;border-radius:4px}.task-board-category:hover{opacity:0.6}.task-board-avatars{text-align:right;float:right}.task-board-change-assignee{cursor:pointer}.task-board-change-assignee:hover{opacity:0.6}.task-board-icons{font-size:0.8em;text-align:right;margin-top:4px;margin-bottom:2px}.task-board-icons a{opacity:0.5}.task-board-icons span{opacity:0.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.flag-milestone{color:green}.task-board-age{display:inline-block}span.task-board-age-total{border:#666 1px solid;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-board-age-column{border:#666 1px solid;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.task-board-date{font-weight:bold;color:#000}span.task-board-date-today{opacity:1.0;color:#36c}span.task-board-date-overdue{opacity:1.0;color:#b94a48}.task-tags li{display:inline-block;margin:3px 3px 0 0;padding:1px 3px 1px 3px;color:#333;border:1px solid #333;border-radius:4px}.task-summary-container .task-tags{margin-top:10px}#task-summary{margin-bottom:15px}#task-summary h2{color:#555;font-size:1.6em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px}.task-summary-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}@media (max-width: 480px){.task-summary-columns{display:block}}.task-summary-column{color:#333}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}#external-task-view{padding:10px;margin-top:10px;margin-bottom:10px;border:1px dotted #ccc}.task-table .dropdown-menu{color:#000;text-decoration:none;font-weight:bold}.task-table .dropdown-menu:focus,.task-table .dropdown-menu:hover{text-decoration:underline}td.task-table a{color:#000;text-decoration:none}td.task-table a:hover{text-decoration:underline}.comment-sorting{text-align:right}.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}.comment-sorting a:hover{color:#999}.comment{padding:5px;margin-bottom:15px}.comment:hover{background:#fafafa}.comment-title{border-bottom:1px dotted #eee;margin-left:55px;margin-bottom:10px}.comment-date{color:#999;font-weight:200}.comment-actions{font-size:0.8em;margin-left:55px;margin-top:8px}.comment-actions li{display:inline}.comment-actions a{color:#999;text-decoration:none}.comment-actions a:focus,.comment-actions a:hover{color:#333;text-decoration:underline}.comment-content{margin-left:55px}.subtasks-table td{vertical-align:middle}.task-links-table td{vertical-align:middle}.task-links-task-count{color:#999}.task-link-closed{text-decoration:line-through}.text-editor a{font-size:1em;color:#999;text-decoration:none;margin-right:10px}.text-editor a:hover{color:#36c}.text-editor .text-editor-preview-area{border:1px solid #dedede;width:400px;height:200px;overflow:auto;padding:2px}.markdown{line-height:1.4em}.markdown h1{margin-top:5px;margin-bottom:10px;font-weight:bold}.markdown h2{font-weight:bold}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#555}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;margin-bottom:30px}.documentation h2{text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fcfcfc;overflow:auto}.listing li{list-style-type:square;margin-left:20px;margin-bottom:3px}.listing ul{margin-top:15px;margin-bottom:15px}.activity-event{margin-bottom:15px;padding:10px}.activity-event:hover{background:#fafafa}.activity-date{margin-left:10px;font-weight:normal;color:#999}.activity-content{margin-left:55px}.activity-title{font-weight:bold;color:#000;border-bottom:1px dotted #efefef}.activity-description{color:#555;margin-top:10px}@media (max-width: 480px){.activity-description{overflow:auto}}.activity-description li{list-style-type:circle}.activity-description ul{margin-top:10px;margin-left:20px}div.ganttview-hzheader-month,div.ganttview-hzheader-day,div.ganttview-vtheader,div.ganttview-vtheader-item-name,div.ganttview-vtheader-series,div.ganttview-grid,div.ganttview-grid-row-cell{float:left}div.ganttview-hzheader-month,div.ganttview-hzheader-day{text-align:center}div.ganttview-grid-row-cell.last,div.ganttview-hzheader-day.last,div.ganttview-hzheader-month.last{border-right:none}div.ganttview{border:1px solid #999}div.ganttview-hzheader-month{width:60px;height:20px;border-right:1px solid #d0d0d0;line-height:20px;overflow:hidden}div.ganttview-hzheader-day{width:20px;height:20px;border-right:1px solid #f0f0f0;border-top:1px solid #d0d0d0;line-height:20px;color:#555}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#555}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#555;text-decoration:none}div.ganttview-vtheader-series-name a:hover{color:#333;text-decoration:underline}div.ganttview-vtheader-series-name a i{color:#000}div.ganttview-vtheader-series-name a:hover i{color:#555}div.ganttview-slide-container{overflow:auto;border-left:1px solid #999}div.ganttview-grid-row-cell{width:20px;height:31px;border-right:1px solid #f0f0f0;border-top:1px solid #f0f0f0}div.ganttview-grid-row-cell.ganttview-weekend{background-color:#fafafa}div.ganttview-blocks{margin-top:40px}div.ganttview-block-container{height:28px;padding-top:4px}div.ganttview-block{position:relative;height:25px;background-color:#E5ECF9;border:1px solid #c0c0c0;border-radius:3px}.ganttview-block-movable{cursor:move}div.ganttview-block-not-defined{border-color:#000;background-color:#000}div.ganttview-block-text{position:absolute;height:12px;font-size:0.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.image-slideshow-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.95);overflow:auto;z-index:100}.image-slideshow-overlay img{display:block;margin:auto}.image-slideshow-overlay figcaption{color:#fff;opacity:0.7;position:absolute;bottom:5px;right:15px}.slideshow-icon{color:#fff;position:absolute;font-size:2.5em;opacity:0.6}.slideshow-icon:hover{opacity:0.9;cursor:pointer}.slideshow-previous-icon{left:10px;top:45%}.slideshow-next-icon{right:10px;top:45%}.slideshow-close-icon{right:10px;top:10px;font-size:1.4em}.slideshow-download-icon{left:10px;bottom:10px;font-size:1.3em} +h1,li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0;font-size:100%}body{margin-left:10px;margin-right:10px;padding-bottom:10px;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}small{font-size:0.8em}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,0.1);border-bottom:1px solid rgba(255,255,255,0.3)}.pull-right{text-align:right}ul.no-bullet li{list-style-type:none;margin-left:0}.chosen-select{min-height:27px}#app-loading-icon{position:fixed;right:3px;bottom:3px}.assign-me{vertical-align:bottom}a{color:#36c;border:none}a:focus{outline:0;color:#DF5353;text-decoration:none}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h1{font-size:1.5em}h2{font-size:1.4em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px}table.table-fixed{table-layout:fixed;white-space:nowrap}table.table-fixed th{overflow:hidden}table.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.table-small{font-size:0.8em}table.table-striped tr:nth-child(odd){background:#fefefe}@media (max-width: 768px){table.table-scrolling{overflow-x:auto;display:inline-block;vertical-align:top;max-width:100%;white-space:nowrap}}table th{text-align:left;padding:0.5em 3px;border:1px solid #eee;background:#fbfbfb}table th a{text-decoration:none;color:#333}table th a:focus,table th a:hover{text-decoration:underline}table td{border:1px solid #eee;padding:0.5em 3px;vertical-align:top}table td li{margin-left:20px}.column-1{width:1%}.column-2{width:2%}.column-3{width:3%}.column-4{width:4%}.column-5{width:5%}.column-6{width:6%}.column-7{width:7%}.column-8{width:8%}.column-9{width:9%}.column-10{width:10%}.column-11{width:11%}.column-12{width:12%}.column-13{width:13%}.column-14{width:14%}.column-15{width:15%}.column-16{width:16%}.column-17{width:17%}.column-18{width:18%}.column-19{width:19%}.column-20{width:20%}.column-21{width:21%}.column-22{width:22%}.column-23{width:23%}.column-24{width:24%}.column-25{width:25%}.column-26{width:26%}.column-27{width:27%}.column-28{width:28%}.column-29{width:29%}.column-30{width:30%}.column-31{width:31%}.column-32{width:32%}.column-33{width:33%}.column-34{width:34%}.column-35{width:35%}.column-36{width:36%}.column-37{width:37%}.column-38{width:38%}.column-39{width:39%}.column-40{width:40%}.column-41{width:41%}.column-42{width:42%}.column-43{width:43%}.column-44{width:44%}.column-45{width:45%}.column-46{width:46%}.column-47{width:47%}.column-48{width:48%}.column-49{width:49%}.column-50{width:50%}.column-51{width:51%}.column-52{width:52%}.column-53{width:53%}.column-54{width:54%}.column-55{width:55%}.column-56{width:56%}.column-57{width:57%}.column-58{width:58%}.column-59{width:59%}.column-60{width:60%}.column-61{width:61%}.column-62{width:62%}.column-63{width:63%}.column-64{width:64%}.column-65{width:65%}.column-66{width:66%}.column-67{width:67%}.column-68{width:68%}.column-69{width:69%}.column-70{width:70%}.column-71{width:71%}.column-72{width:72%}.column-73{width:73%}.column-74{width:74%}.column-75{width:75%}.column-76{width:76%}.column-77{width:77%}.column-78{width:78%}.column-79{width:79%}.column-80{width:80%}.column-81{width:81%}.column-82{width:82%}.column-83{width:83%}.column-84{width:84%}.column-85{width:85%}.column-86{width:86%}.column-87{width:87%}.column-88{width:88%}.column-89{width:89%}.column-90{width:90%}.column-91{width:91%}.column-92{width:92%}.column-93{width:93%}.column-94{width:94%}.column-95{width:95%}.column-96{width:96%}.column-97{width:97%}.column-98{width:98%}.column-99{width:99%}.column-100{width:100%}.draggable-row-handle{cursor:move;color:#dedede}.draggable-row-handle:hover{color:#333}tr.draggable-item-selected{background:#fff;border:2px solid #666;box-shadow:4px 2px 10px -4px rgba(0,0,0,0.55)}tr.draggable-item-selected td{border-top:none;border-bottom:none}tr.draggable-item-selected td:first-child{border-left:none}tr.draggable-item-selected td:last-child{border-right:none}.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover{background:#FEFFF2}form{margin-bottom:20px}label{cursor:pointer;display:block;margin-top:10px}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field){color:#999;border:1px solid #ccc;width:300px;max-width:95%;font-size:1em;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;-moz-appearance:none}input[type="number"]::-webkit-input-placeholder,input[type="date"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder{color:#dedede}input[type="number"]::-moz-placeholder,input[type="date"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="text"]:not(.input-addon-field)::-moz-placeholder{color:#dedede}input[type="number"]:-ms-input-placeholder,input[type="date"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="text"]:not(.input-addon-field):-ms-input-placeholder{color:#dedede}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}input[type="number"]{width:70px}input[type="text"]:not(.input-addon-field).form-numeric{width:70px}input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date{width:150px}input[type="text"]:not(.input-addon-field).form-input-large{width:400px}input[type="text"]:not(.input-addon-field).form-input-small{width:150px}textarea:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}textarea{padding:3px;border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-family:sans-serif;font-size:1em}textarea::-webkit-input-placeholder{color:#dedede}textarea::-moz-placeholder{color:#dedede}textarea:-ms-input-placeholder{color:#dedede}select{font-size:1.0em;max-width:95%}select:focus{outline:0}select[multiple]{width:300px}.tag-autocomplete{width:400px}span.select2-container{margin-top:2px}.form-actions{padding-top:20px;clear:both}.form-required{color:red;padding-left:5px;font-weight:bold}@media (max-width: 480px){.form-required{display:none}}input.form-error,textarea.form-error{border:2px solid #b94a48}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid #b94a48}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:0.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:none}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0 15px 0 0}.form-inline .form-required{display:none}.form-inline-group{display:inline}.form-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.form-columns .form-column{margin-right:25px;flex-grow:1}.form-login{max-width:350px;margin:8% auto 0}.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-weight:bold}.reset-password{margin-top:20px}.reset-password a{color:#999}.input-addon{display:flex}.input-addon-field{flex:1;font-size:1em;color:#999;margin:0;-webkit-appearance:none;-moz-appearance:none}.input-addon-field:first-child{border-radius:5px 0 0 5px}.input-addon-field:last-child{border-radius:0 5px 5px 0}.input-addon-item{background-color:rgba(147,128,108,0.1);color:#666;font:inherit;font-weight:normal}.input-addon-item:first-child{border-radius:5px 0 0 5px}.input-addon-item:last-child{border-radius:0 5px 5px 0}@media (max-width: 480px){.input-addon-item .dropdown .fa-caret-down{display:none}}.input-addon-field,.input-addon-item{border:1px solid rgba(147,128,108,0.25);padding:4px 0.75em}.input-addon-field:not(:first-child),.input-addon-item:not(:first-child){border-left:0}.icon-success{color:#468847}.icon-error{color:#b94a48}.icon-fade-out{opacity:1;animation:icon-fadeout 5s linear forwards}@keyframes icon-fadeout{0%{opacity:1}100%{opacity:0}}.alert{padding:8px 35px 8px 14px;margin-top:5px;margin-bottom:5px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-normal{color:#333;background-color:#f0f0f0;border-color:#ddd}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.alert-fade-out{text-align:center;position:fixed;bottom:0;left:20%;width:60%;padding-top:5px;padding-bottom:5px;margin-bottom:0;border-width:1px 0 0;border-radius:4px 4px 0 0;z-index:9999;opacity:1;animation:fadeout 5s linear forwards}@keyframes fadeout{0%{opacity:1}100%{opacity:0}}a.btn{text-decoration:none}.btn{-webkit-appearance:none;-moz-appearance:none;font-size:1.2em;font-weight:normal;cursor:pointer;display:inline-block;border-radius:2px;padding:3px 10px;margin:0;border:1px solid #ddd;background:#f5f5f5;color:#333}.btn:hover,.btn:focus{border-color:#bbb;background:#fafafa;color:#000}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}.btn-red:hover,.btn-red:focus{border-color:#b0281a;background:#c53727;color:#fff}.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}.btn-blue:hover,.btn-blue:focus{border-color:#3079ed;background:#357ae8;color:#fff}.btn:disabled{color:#ccc;border-color:#ccc;background:#f7f7f7}.buttons-header{font-size:0.8em;margin-top:5px;margin-bottom:15px}.tooltip-arrow:after{background:#fff;border:1px solid #aaaaaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px}.tooltip-arrow{width:20px;height:10px;overflow:hidden;position:absolute}.tooltip-arrow.top{top:-10px}.tooltip-arrow.bottom{bottom:-10px}.tooltip-arrow.align-left{left:10px}.tooltip-arrow.align-right{right:10px}.tooltip-arrow:after{content:"";position:absolute;width:14px;height:14px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.tooltip-arrow.bottom:after{top:-10px}.tooltip-arrow.top:after{bottom:-10px}.tooltip-arrow.align-left:after{left:0}.tooltip-arrow.align-right:after{right:0}.tooltip-large{width:600px}.ui-tooltip-content .markdown p{margin-bottom:0}.ui-tooltip li{list-style-type:none}.tooltip .fa-info-circle{color:#999}h2 .dropdown ul{display:none}.dropdown{display:inline;position:relative}.dropdown ul{display:none}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}.dropdown-submenu-open li{display:block;margin:0;padding:8px 10px;font-size:0.9em;border-bottom:1px solid #f8f8f8;cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.dropdown-submenu-open li:last-child{border:none}.dropdown-submenu-open li:not(.no-hover):hover{background:#4078C0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:#333;text-decoration:none}.dropdown-menu-link-text:hover{text-decoration:underline}.accordion-title{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NEQ5RDgxQzc2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NEQ5RDgxQzg2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0RDlEODFDNTZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0RDlEODFDNjZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvXFWFAAAAAYSURBVHjaYvj//z8D0/Pnz/8zgFgAAQYAS5UJscReGMIAAAAASUVORK5CYII=) repeat-x scroll 0 10px}.accordion-title h3{display:inline;padding-right:5px;background:#fff}.accordion-content{margin-top:15px;margin-bottom:25px}.accordion-toggle{color:#333;text-decoration:none}.accordion-toggle:focus{color:#333}.accordion-toggle:hover{color:#999}.accordion-toggle:before{content:"\f0d7"}.accordion-collapsed{margin-bottom:25px}.accordion-collapsed .accordion-toggle:before{content:"\f0da"}.accordion-collapsed .accordion-content{display:none}#select-dropdown-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,0.175);overflow:scroll}.select-dropdown-menu-item{white-space:nowrap;overflow:hidden;padding:3px 10px;color:#555;cursor:pointer;border-bottom:1px solid #f8f8f8;line-height:1.5em;font-weight:400}.select-dropdown-menu-item.active{color:#fff;background:#428bca}.select-dropdown-menu-item:last-child{border:none}.select-dropdown-input-container{position:relative;border:1px solid #ccc;border-radius:5px}.select-dropdown-input-container input.select-dropdown-input{margin:0 0 0 5px;border:none;height:23px}.select-dropdown-input-container input.select-dropdown-input:focus{border:none;box-shadow:none}.select-dropdown-input-container .select-dropdown-chevron{color:#555;position:absolute;top:4px;right:5px;cursor:pointer}#suggest-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.suggest-menu-item{white-space:nowrap;padding:3px 10px;color:#333;font-weight:bold;cursor:pointer}.suggest-menu-item.active{color:#fff;background:#428bca}.suggest-menu-item.active small{color:#fff}.suggest-menu-item small{color:#999;font-weight:normal}#main .confirm{max-width:700px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);overflow:auto;z-index:100}#popover-content{position:fixed;width:950px;max-width:95%;max-height:calc(100% - 50px);top:5%;left:50%;transform:translateX(-50%);padding:0 15px 15px;background:#fff;overflow:auto}#popover-content-header{text-align:right}#popover-close-button{color:#333}#popover-close-button:hover{color:#b94a48}.popover-form{margin-bottom:0}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}header{box-sizing:border-box;display:flex;flex-wrap:wrap;margin-top:5px;margin-bottom:5px;border-bottom:1px solid #dedede}header>*{box-sizing:border-box}header>*{width:1%}header .menus-container{width:10%}@media (min-width: 768px) and (max-width: 1150px){header .menus-container{width:15%}}@media (max-width: 768px){header .menus-container{width:30%;order:2}}header .board-selector-container{width:25%}@media (min-width: 768px) and (max-width: 1150px){header .board-selector-container{width:20%}}@media (max-width: 768px){header .board-selector-container{width:70%;order:1;margin-bottom:5px}}header .title-container{width:65%}@media (max-width: 768px){header .title-container{width:100%;order:3}}header h1{font-size:1.5em}header h1 .tooltip{opacity:0.3;font-size:0.7em}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}.logo a{opacity:0.5;color:#d40000;text-decoration:none}.logo span{color:#333}.logo a:hover{opacity:0.8;color:#333}.logo a:focus span,.logo a:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header .dropdown{padding-right:10px}.page-header h2{margin:0;padding:0;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#333;text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:#999}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.page-header li{display:inline;padding-right:15px}@media (max-width: 480px){.page-header li{display:block;line-height:1.5em}}.page-header li.active a{color:#333;text-decoration:none;font-weight:bold}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}.menu-inline li{display:inline;padding-right:15px}.menu-inline li .active a{font-weight:bold;color:#000;text-decoration:none}.sidebar-container{box-sizing:border-box;display:flex;flex-wrap:wrap}.sidebar-container>*{box-sizing:border-box}.sidebar-container>*{width:1%}.sidebar-content{padding-left:10px;width:82%}@media (max-width: 480px){.sidebar-content{width:100%}}.sidebar{max-width:240px;min-width:190px;width:18%}@media (max-width: 480px){.sidebar{width:100%;max-width:99%;min-width:0}}.sidebar h2{margin-top:0}.sidebar>ul a{text-decoration:none;color:#999;font-weight:300}.sidebar>ul a:hover{color:#333}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef;padding-left:13px}.sidebar>ul li:hover{border-left:5px solid #555;padding-left:8px}.sidebar>ul li.active{border-left:5px solid #333;padding-left:8px}.sidebar>ul li.active a{color:#333;font-weight:bold}.sidebar-icons>ul li{padding-left:0}.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active{padding-left:0;border-left:none}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:#555}.sidebar>ul li:last-child{margin-bottom:15px}.avatar img{vertical-align:bottom}.avatar-left{float:left;margin-right:10px}.avatar-inline{display:inline-block;margin-right:3px}.avatar-48 img,.avatar-48 div{border-radius:30px}.avatar-48 .avatar-letter{line-height:48px;width:48px;font-size:25px}.avatar-20 img,.avatar-20 div{border-radius:10px}.avatar-20 .avatar-letter{line-height:20px;width:20px;font-size:11px}.avatar-letter{color:#fff;text-align:center}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li.file-error{font-weight:bold;color:#b94a48}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:4px 2px 10px -6px rgba(0,0,0,0.55);margin-right:15px}.file-thumbnail img{cursor:pointer;border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:0.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:0.9em;color:#555}.file-thumbnail-description{font-size:0.8em;color:#999;margin-top:8px;margin-bottom:5px}.file-viewer{position:relative}.file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.color-picker{width:180px}.color-picker-option{height:25px}.color-picker-square{display:inline-block;width:18px;height:18px;margin-right:5px;border:1px solid #000}.color-picker-label{display:inline-block;vertical-align:bottom;padding-bottom:3px}.filter-box{max-width:800px}.action-menu{color:#333;text-decoration:none}.action-menu:hover,.action-menu:focus{text-decoration:underline}.js-project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.project-overview-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin-bottom:20px;font-size:1.4em}@media (max-width: 480px){.project-overview-columns{display:block}}.project-overview-column{text-align:center;margin-right:3%;margin-top:5px;padding:3px 15px 3px 15px;border:1px dashed #ddd}@media (max-width: 480px){.project-overview-column{text-align:left}}.project-overview-column small{color:#999}.project-overview-column strong{color:#555;display:block}@media (max-width: 480px){.project-overview-column strong{display:inline}}.project-header{box-sizing:border-box;display:flex;flex-wrap:wrap;margin-bottom:8px}.project-header>*{box-sizing:border-box}.project-header>*{width:1%}.project-header .dropdown-component{margin-top:4px;width:5%}@media (min-width: 768px) and (max-width: 1150px){.project-header .dropdown-component{width:8%}}@media (max-width: 768px){.project-header .dropdown-component{width:100%}}.project-header .views-switcher-component{margin-top:4px;width:38%}@media (max-width: 1300px){.project-header .views-switcher-component{width:45%}}@media (min-width: 768px) and (max-width: 1150px){.project-header .views-switcher-component{width:92%}}@media (max-width: 768px){.project-header .views-switcher-component{margin-top:0;width:100%}}.project-header .filter-box-component{margin:0;width:55%}@media (max-width: 1300px){.project-header .filter-box-component{width:50%}}@media (min-width: 768px) and (max-width: 1150px){.project-header .filter-box-component{width:100%;margin-top:10px}.project-header .filter-box-component .filter-box{max-width:100%}}@media (max-width: 768px){.project-header .filter-box-component{width:100%;margin-top:10px}.project-header .filter-box-component .filter-box{max-width:100%}}.project-header .filter-box-component form{margin:0}.views{display:inline-block;margin-right:10px;font-size:0.9em}@media (max-width: 560px){.views{width:100%}}@media (max-width: 768px){.views{margin-top:10px;font-size:1em}}@media (max-width: 480px){.views{margin-top:5px}}.views li{white-space:nowrap;background:#fafafa;border:1px solid #ddd;border-right:none;padding:4px 8px;display:inline}@media (max-width: 560px){.views li{display:block;margin-top:5px;border-radius:5px;border:1px solid #ddd}}.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-right:1px solid #ddd;border-top-right-radius:5px;border-bottom-right-radius:5px}.views a{color:#555;text-decoration:none}.views a:hover{color:#333;text-decoration:underline}.dashboard-project-stats small{margin-right:10px;color:#999}.dashboard-table-link{font-weight:bold;color:#000;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.public-board{margin-top:5px}.public-task{max-width:800px;margin:5px auto 0}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast: active), (-ms-high-contrast: none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:1.6em;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}a.board-swimlane-toggle{text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:none}.board-task-list{min-height:60px}.board-task-list-limit{background-color:#DF5353}.draggable-item{cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;word-wrap:break-word;font-size:0.9em}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-board a{color:#000;text-decoration:none}.task-board .dropdown-menu{font-weight:bold}.task-board .task-score{font-weight:bold}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-board-title{margin-top:5px;margin-bottom:8px}.task-board-title a:hover{text-decoration:underline}.task-board-saving-state{opacity:0.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board-category-container{text-align:right;margin-top:8px;margin-bottom:8px}.task-board-category{font-weight:500;color:#000;border:1px solid #555;padding:1px 2px 1px 2px;border-radius:4px}.task-board-category:hover{opacity:0.6}.task-board-avatars{text-align:right;float:right}.task-board-change-assignee{cursor:pointer}.task-board-change-assignee:hover{opacity:0.6}.task-board-icons{font-size:0.8em;text-align:right;margin-top:4px;margin-bottom:2px}.task-board-icons a{opacity:0.5}.task-board-icons span{opacity:0.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.flag-milestone{color:green}.task-board-age{display:inline-block}span.task-board-age-total{border:#666 1px solid;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-board-age-column{border:#666 1px solid;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.task-board-date{font-weight:bold;color:#000}span.task-board-date-today{opacity:1.0;color:#36c}span.task-board-date-overdue{opacity:1.0;color:#b94a48}.task-tags li{display:inline-block;margin:3px 3px 0 0;padding:1px 3px 1px 3px;color:#333;border:1px solid #333;border-radius:4px}.task-summary-container .task-tags{margin-top:10px}#task-summary{margin-bottom:15px}#task-summary h2{color:#555;font-size:1.6em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px}.task-summary-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}@media (max-width: 480px){.task-summary-columns{display:block}}.task-summary-column{color:#333}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}#external-task-view{padding:10px;margin-top:10px;margin-bottom:10px;border:1px dotted #ccc}.task-table .dropdown-menu{color:#000;text-decoration:none;font-weight:bold}.task-table .dropdown-menu:focus,.task-table .dropdown-menu:hover{text-decoration:underline}td.task-table a{color:#000;text-decoration:none}td.task-table a:hover{text-decoration:underline}.comment-sorting{text-align:right}.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}.comment-sorting a:hover{color:#999}.comment{padding:5px;margin-bottom:15px}.comment:hover{background:#fafafa}.comment-title{border-bottom:1px dotted #eee;margin-left:55px;margin-bottom:10px}.comment-date{color:#999;font-weight:200}.comment-actions{font-size:0.8em;margin-left:55px;margin-top:8px}.comment-actions li{display:inline}.comment-actions a{color:#999;text-decoration:none}.comment-actions a:focus,.comment-actions a:hover{color:#333;text-decoration:underline}.comment-content{margin-left:55px}.subtasks-table td{vertical-align:middle}.task-links-table td{vertical-align:middle}.task-links-task-count{color:#999}.task-link-closed{text-decoration:line-through}.text-editor a{font-size:1em;color:#999;text-decoration:none;margin-right:10px}.text-editor a:hover{color:#36c}.text-editor .text-editor-preview-area{border:1px solid #dedede;width:400px;height:200px;overflow:auto;padding:2px}.markdown{line-height:1.4em}.markdown h1{margin-top:5px;margin-bottom:10px;font-weight:bold}.markdown h2{font-weight:bold}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#555}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;margin-bottom:30px}.documentation h2{text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fcfcfc;overflow:auto}.listing li{list-style-type:square;margin-left:20px;margin-bottom:3px}.listing ul{margin-top:15px;margin-bottom:15px}.activity-event{margin-bottom:15px;padding:10px}.activity-event:hover{background:#fafafa}.activity-date{margin-left:10px;font-weight:normal;color:#999}.activity-content{margin-left:55px}.activity-title{font-weight:bold;color:#000;border-bottom:1px dotted #efefef}.activity-description{color:#555;margin-top:10px}@media (max-width: 480px){.activity-description{overflow:auto}}.activity-description li{list-style-type:circle}.activity-description ul{margin-top:10px;margin-left:20px}div.ganttview-hzheader-month,div.ganttview-hzheader-day,div.ganttview-vtheader,div.ganttview-vtheader-item-name,div.ganttview-vtheader-series,div.ganttview-grid,div.ganttview-grid-row-cell{float:left}div.ganttview-hzheader-month,div.ganttview-hzheader-day{text-align:center}div.ganttview-grid-row-cell.last,div.ganttview-hzheader-day.last,div.ganttview-hzheader-month.last{border-right:none}div.ganttview{border:1px solid #999}div.ganttview-hzheader-month{width:60px;height:20px;border-right:1px solid #d0d0d0;line-height:20px;overflow:hidden}div.ganttview-hzheader-day{width:20px;height:20px;border-right:1px solid #f0f0f0;border-top:1px solid #d0d0d0;line-height:20px;color:#555}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#555}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#555;text-decoration:none}div.ganttview-vtheader-series-name a:hover{color:#333;text-decoration:underline}div.ganttview-vtheader-series-name a i{color:#000}div.ganttview-vtheader-series-name a:hover i{color:#555}div.ganttview-slide-container{overflow:auto;border-left:1px solid #999}div.ganttview-grid-row-cell{width:20px;height:31px;border-right:1px solid #f0f0f0;border-top:1px solid #f0f0f0}div.ganttview-grid-row-cell.ganttview-weekend{background-color:#fafafa}div.ganttview-blocks{margin-top:40px}div.ganttview-block-container{height:28px;padding-top:4px}div.ganttview-block{position:relative;height:25px;background-color:#E5ECF9;border:1px solid #c0c0c0;border-radius:3px}.ganttview-block-movable{cursor:move}div.ganttview-block-not-defined{border-color:#000;background-color:#000}div.ganttview-block-text{position:absolute;height:12px;font-size:0.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.image-slideshow-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.95);overflow:auto;z-index:100}.image-slideshow-overlay img{display:block;margin:auto}.image-slideshow-overlay figcaption{color:#fff;opacity:0.7;position:absolute;bottom:5px;right:15px}.slideshow-icon{color:#fff;position:absolute;font-size:2.5em;opacity:0.6}.slideshow-icon:hover{opacity:0.9;cursor:pointer}.slideshow-previous-icon{left:10px;top:45%}.slideshow-next-icon{right:10px;top:45%}.slideshow-close-icon{right:10px;top:10px;font-size:1.4em}.slideshow-download-icon{left:10px;bottom:10px;font-size:1.3em} diff --git a/assets/sass/_select_dropdown.sass b/assets/sass/_select_dropdown.sass index cdd15c64..16481a2a 100644 --- a/assets/sass/_select_dropdown.sass +++ b/assets/sass/_select_dropdown.sass @@ -35,6 +35,7 @@ input.select-dropdown-input margin: 0 0 0 5px border: none + height: 23px &:focus border: none box-shadow: none diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index fc612682..fad19466 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -63,6 +63,7 @@ Usage - [Task Files](api-task-file-procedures.markdown) - [Project Files](api-project-file-procedures.markdown) - [Links](api-link-procedures.markdown) +- [Tags](api-tags-procedures.markdown) - [Internal Task Links](api-internal-task-link-procedures.markdown) - [External Task Links](api-external-task-link-procedures.markdown) - [Comments](api-comment-procedures.markdown) diff --git a/doc/api-tags-procedures.markdown b/doc/api-tags-procedures.markdown new file mode 100644 index 00000000..8d69430c --- /dev/null +++ b/doc/api-tags-procedures.markdown @@ -0,0 +1,195 @@ +API Tags Procedures +=================== + +getAllTags +---------- + +- Purpose: **Get all tags** +- Parameters: none +- Result on success: **List of tags** +- Result on failure: **false|null** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getAllTags","id":45253426} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": [ + { + "id": "1", + "name": "another tag", + "project_id": "33" + } + ], + "id": 45253426 +} +``` + +getTagsByProject +---------------- + +- Purpose: **Get all tags for a given project** +- Parameters: + - **project_id** (integer) +- Result on success: **List of tags** +- Result on failure: **false|null** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getTagsByProject","id":1217591720,"params":[33]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": [ + { + "id": "1", + "name": "some tag", + "project_id": "33" + } + ], + "id": 1217591720 +} +``` + +createTag +--------- + +- Purpose: **Create a new tag** +- Parameters: + - **project_id** (integer) + - **tag** (string) +- Result on success: **tag_id** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"createTag","id":1775436017,"params":[33,"some tag"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": 1, + "id": 1775436017 +} +``` + +updateTag +--------- + +- Purpose: **Rename a tag** +- Parameters: + - **tag_id** (integer) + - **tag** (string) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"updateTag","id":2037516512,"params":["1","another tag"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 2037516512 +} +``` + +removeTag +--------- + +- Purpose: **removeTag** +- Parameters: + - **tag_id** (integer) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"removeTag","id":907581298,"params":["1"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 907581298 +} +``` + +setTaskTags +----------- + +- Purpose: **Assign/Create/Update tags for a task** +- Parameters: + - **project_id** (integer) + - **task_id** (integer) + - **tags** List of tags ([]string) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"setTaskTags","id":1524522873,"params":[39,17,["tag1","tag2"]]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1524522873 +} +``` + +getTaskTags +----------- + +- Purpose: **Get assigned tags to a task** +- Parameters: + - **task_id** (integer) +- Result on success: **Dictionary of tags** +- Result on failure: **false|null** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getTaskTags","id":1667157705,"params":[17]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "1": "tag1", + "2": "tag2" + }, + "id": 1667157705 +} +``` diff --git a/doc/api-task-procedures.markdown b/doc/api-task-procedures.markdown index 2897c81a..db106f74 100644 --- a/doc/api-task-procedures.markdown +++ b/doc/api-task-procedures.markdown @@ -17,11 +17,12 @@ API Task Procedures - **score** (integer, optional) - **swimlane_id** (integer, optional) - **priority** (integer, optional) - - **recurrence_status** (integer, optional) - - **recurrence_trigger** (integer, optional) - - **recurrence_factor** (integer, optional) - - **recurrence_timeframe** (integer, optional) - - **recurrence_basedate** (integer, optional) + - **recurrence_status** (integer, optional) + - **recurrence_trigger** (integer, optional) + - **recurrence_factor** (integer, optional) + - **recurrence_timeframe** (integer, optional) + - **recurrence_basedate** (integer, optional) + - **tags** ([]string, optional) - Result on success: **task_id** - Result on failure: **false** @@ -400,11 +401,12 @@ Response example: - **category_id** (integer, optional) - **score** (integer, optional) - **priority** (integer, optional) - - **recurrence_status** (integer, optional) - - **recurrence_trigger** (integer, optional) - - **recurrence_factor** (integer, optional) - - **recurrence_timeframe** (integer, optional) - - **recurrence_basedate** (integer, optional) + - **recurrence_status** (integer, optional) + - **recurrence_trigger** (integer, optional) + - **recurrence_factor** (integer, optional) + - **recurrence_timeframe** (integer, optional) + - **recurrence_basedate** (integer, optional) + - **tags** ([]string, optional) - Result on success: **true** - Result on failure: **false** diff --git a/tests/integration/TagProcedureTest.php b/tests/integration/TagProcedureTest.php new file mode 100644 index 00000000..f15efa3c --- /dev/null +++ b/tests/integration/TagProcedureTest.php @@ -0,0 +1,58 @@ +<?php + +require_once __DIR__.'/BaseProcedureTest.php'; + +class TagProcedureTest extends BaseProcedureTest +{ + protected $projectName = 'My project with tags'; + + public function testAll() + { + $this->assertCreateTeamProject(); + $this->assertCreateTag(); + $this->assertGetProjectTags(); + $this->assertGetAllTags(); + $this->assertUpdateTag(); + $this->assertRemoveTag(); + } + + public function assertCreateTag() + { + $this->assertNotFalse($this->app->createTag($this->projectId, 'some tag')); + } + + public function assertGetProjectTags() + { + $tags = $this->app->getTagsByProject($this->projectId); + $this->assertCount(1, $tags); + $this->assertEquals('some tag', $tags[0]['name']); + } + + public function assertGetAllTags() + { + $tags = $this->app->getAllTags(); + $this->assertCount(1, $tags); + $this->assertEquals('some tag', $tags[0]['name']); + } + + public function assertUpdateTag() + { + $tags = $this->app->getAllTags(); + $this->assertCount(1, $tags); + $this->assertTrue($this->app->updateTag($tags[0]['id'], 'another tag')); + + $tags = $this->app->getAllTags(); + $this->assertCount(1, $tags); + $this->assertEquals('another tag', $tags[0]['name']); + } + + public function assertRemoveTag() + { + $tags = $this->app->getAllTags(); + $this->assertCount(1, $tags); + $this->assertTrue($this->app->removeTag($tags[0]['id'])); + + $tags = $this->app->getAllTags(); + $this->assertCount(0, $tags); + } +} diff --git a/tests/integration/TaskTagProcedureTest.php b/tests/integration/TaskTagProcedureTest.php new file mode 100644 index 00000000..4b820c9a --- /dev/null +++ b/tests/integration/TaskTagProcedureTest.php @@ -0,0 +1,54 @@ +<?php + +require_once __DIR__.'/BaseProcedureTest.php'; + +class TaskTagProcedureTest extends BaseProcedureTest +{ + protected $projectName = 'My project with tasks and tags'; + + public function testAll() + { + $this->assertCreateTeamProject(); + $this->assertCreateTask(); + $this->assertSetTaskTags(); + $this->assertGetTaskTags(); + $this->assertCreateTaskWithTags(); + $this->assertUpdateTaskWithTags(); + } + + public function assertSetTaskTags() + { + $this->assertTrue($this->app->setTaskTags($this->projectId, $this->taskId, array('tag1', 'tag2'))); + } + + public function assertGetTaskTags() + { + $tags = $this->app->getTaskTags($this->taskId); + $this->assertEquals(array('tag1', 'tag2'), array_values($tags)); + } + + public function assertCreateTaskWithTags() + { + $this->taskId = $this->app->createTask(array( + 'title' => $this->taskTitle, + 'project_id' => $this->projectId, + 'tags' => array('tag A', 'tag B'), + )); + + $this->assertNotFalse($this->taskId); + + $tags = $this->app->getTaskTags($this->taskId); + $this->assertEquals(array('tag A', 'tag B'), array_values($tags)); + } + + public function assertUpdateTaskWithTags() + { + $this->assertTrue($this->app->updateTask(array( + 'id' => $this->taskId, + 'tags' => array('tag C'), + ))); + + $tags = $this->app->getTaskTags($this->taskId); + $this->assertEquals(array('tag C'), array_values($tags)); + } +} diff --git a/tests/units/Helper/TextHelperTest.php b/tests/units/Helper/TextHelperTest.php index a54c5780..5c8a10f6 100644 --- a/tests/units/Helper/TextHelperTest.php +++ b/tests/units/Helper/TextHelperTest.php @@ -5,12 +5,13 @@ require_once __DIR__.'/../Base.php'; use Kanboard\Helper\TextHelper; use Kanboard\Model\ProjectModel; use Kanboard\Model\TaskCreationModel; +use Kanboard\Model\UserModel; class TextHelperTest extends Base { public function testMarkdownTaskLink() { - $helper = new TextHelper($this->container); + $textHelper = new TextHelper($this->container); $projectModel = new ProjectModel($this->container); $taskCreationModel = new TaskCreationModel($this->container); @@ -19,26 +20,26 @@ class TextHelperTest extends Base $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1))); $project = $projectModel->getById(1); - $this->assertEquals('<p>Test</p>', $helper->markdown('Test')); + $this->assertEquals('<p>Test</p>', $textHelper->markdown('Test')); $this->assertEquals( '<p>Task <a href="?controller=TaskViewController&action=show&task_id=123">#123</a></p>', - $helper->markdown('Task #123') + $textHelper->markdown('Task #123') ); $this->assertEquals( '<p>Task #123</p>', - $helper->markdown('Task #123', true) + $textHelper->markdown('Task #123', true) ); $this->assertEquals( '<p>Task <a href="http://localhost/?controller=TaskViewController&action=readonly&token='.$project['token'].'&task_id=1">#1</a></p>', - $helper->markdown('Task #1', true) + $textHelper->markdown('Task #1', true) ); $this->assertEquals( '<p>Check that: <a href="http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454">http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454</a></p>', - $helper->markdown( + $textHelper->markdown( 'Check that: http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454' ) ); @@ -46,44 +47,82 @@ class TextHelperTest extends Base public function testMarkdownUserLink() { - $h = new TextHelper($this->container); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a> @notfound</p>', $h->markdown('Text @admin @notfound')); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a>,</p>', $h->markdown('Text @admin,')); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a>!</p>', $h->markdown('Text @admin!')); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a>? </p>', $h->markdown('Text @admin? ')); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a>.</p>', $h->markdown('Text @admin.')); - $this->assertEquals('<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link">@admin</a>: test</p>', $h->markdown('Text @admin: test')); - $this->assertEquals('<p>Text @admin @notfound</p>', $h->markdown('Text @admin @notfound', true)); + $textHelper = new TextHelper($this->container); + $userModel = new UserModel($this->container); + + $this->assertEquals(2, $userModel->create(array('username' => 'firstname.lastname', 'name' => 'Firstname Lastname'))); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a> @notfound</p>', + $textHelper->markdown('Text @admin @notfound') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>,</p>', + $textHelper->markdown('Text @admin,') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>!</p>', + $textHelper->markdown('Text @admin!') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>? </p>', + $textHelper->markdown('Text @admin? ') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>.</p>', + $textHelper->markdown('Text @admin.') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>: test</p>', + $textHelper->markdown('Text @admin: test') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=1" class="user-mention-link" title="admin">@admin</a>: test</p>', + $textHelper->markdown('Text @admin: test') + ); + + $this->assertEquals( + '<p>Text <a href="?controller=UserViewController&action=profile&user_id=2" class="user-mention-link" title="Firstname Lastname">@firstname.lastname</a>. test</p>', + $textHelper->markdown('Text @firstname.lastname. test') + ); + + $this->assertEquals('<p>Text @admin @notfound</p>', $textHelper->markdown('Text @admin @notfound', true)); } public function testMarkdownAttribute() { - $helper = new TextHelper($this->container); - $this->assertEquals('<p>Ça marche</p>', $helper->markdownAttribute('Ça marche')); - $this->assertEquals('<p>Test with &quot;double quotes&quot;</p>', $helper->markdownAttribute('Test with "double quotes"')); - $this->assertEquals('<p>Test with 'single quotes'</p>', $helper->markdownAttribute("Test with 'single quotes'")); + $textHelper = new TextHelper($this->container); + $this->assertEquals('<p>Ça marche</p>', $textHelper->markdownAttribute('Ça marche')); + $this->assertEquals('<p>Test with &quot;double quotes&quot;</p>', $textHelper->markdownAttribute('Test with "double quotes"')); + $this->assertEquals('<p>Test with 'single quotes'</p>', $textHelper->markdownAttribute("Test with 'single quotes'")); } public function testFormatBytes() { - $h = new TextHelper($this->container); + $textHelper = new TextHelper($this->container); - $this->assertEquals('1k', $h->bytes(1024)); - $this->assertEquals('33.71k', $h->bytes(34520)); + $this->assertEquals('1k', $textHelper->bytes(1024)); + $this->assertEquals('33.71k', $textHelper->bytes(34520)); } public function testContains() { - $h = new TextHelper($this->container); + $textHelper = new TextHelper($this->container); - $this->assertTrue($h->contains('abc', 'b')); - $this->assertFalse($h->contains('abc', 'd')); + $this->assertTrue($textHelper->contains('abc', 'b')); + $this->assertFalse($textHelper->contains('abc', 'd')); } public function testInList() { - $h = new TextHelper($this->container); - $this->assertEquals('?', $h->in('a', array('b' => 'c'))); - $this->assertEquals('c', $h->in('b', array('b' => 'c'))); + $textHelper = new TextHelper($this->container); + $this->assertEquals('?', $textHelper->in('a', array('b' => 'c'))); + $this->assertEquals('c', $textHelper->in('b', array('b' => 'c'))); } } diff --git a/tests/units/Job/UserMentionJobTest.php b/tests/units/Job/UserMentionJobTest.php index 4cd4ac9b..04ffa0d3 100644 --- a/tests/units/Job/UserMentionJobTest.php +++ b/tests/units/Job/UserMentionJobTest.php @@ -53,13 +53,19 @@ class UserMentionJobTest extends Base $this->assertNotFalse($userModel->create(array('username' => 'user1'))); $this->assertNotFalse($userModel->create(array('username' => 'user2', 'name' => 'Foobar', 'notifications_enabled' => 1))); + $this->assertNotFalse($userModel->create(array('username' => 'user3.with.dot', 'notifications_enabled' => 1))); - $users = $userMentionJob->getMentionedUsers('test @user2, test'); - $this->assertCount(1, $users); + $users = $userMentionJob->getMentionedUsers('test @user2, test, @user3.with.dot.'); + $this->assertCount(2, $users); $this->assertEquals('user2', $users[0]['username']); $this->assertEquals('Foobar', $users[0]['name']); $this->assertEquals('', $users[0]['email']); $this->assertEquals('', $users[0]['language']); + + $this->assertEquals('user3.with.dot', $users[1]['username']); + $this->assertEquals('', $users[1]['name']); + $this->assertEquals('', $users[1]['email']); + $this->assertEquals('', $users[1]['language']); } public function testGetMentionedUsersWithNotficationEnabledAndUserLoggedIn() |