diff options
76 files changed, 3640 insertions, 383 deletions
@@ -3,11 +3,18 @@ Version 1.0.23 (unreleased) New features: +- Added support of user mentions (@username) - Added report to compare working hours between open and closed tasks - Added the possiblity to define custom routes from plugins +Improvements: + +- Improving performance during task position change (SQL queries are 3 times faster than before) +- Do not show window scrollbars when individual column scrolling is enabled + Bug fixes: +- Fix compatiblity issue with FreeBSD for session.hash_function parameter - Fix wrong constant name that cause a PHP error in project management section - Fix pagination in group members listing - Avoid PHP error when enabling LDAP group provider with PHP < 5.5 @@ -5,7 +5,7 @@ CSS_PRINT = $(addprefix assets/css/src/, $(addsuffix .css, print links table boa CSS_VENDOR = $(addprefix assets/css/vendor/, $(addsuffix .css, jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min)) JS_APP = $(addprefix assets/js/src/, $(addsuffix .js, Popover Dropdown Tooltip Markdown Sidebar Search App Screenshot Calendar Board Swimlane Gantt Task Project TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart CompareHoursColumnChart Router)) -JS_VENDOR = $(addprefix assets/js/vendor/, $(addsuffix .js, jquery-1.11.3.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min)) +JS_VENDOR = $(addprefix assets/js/vendor/, $(addsuffix .js, jquery-1.11.3.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min jquery.textcomplete)) JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, cs da de es fi fr hu id it ja nl nb pl pt pt-br ru sv sr th tr zh-cn)) all: css js diff --git a/app/Api/Task.php b/app/Api/Task.php index 4a7ee932..1491cd35 100644 --- a/app/Api/Task.php +++ b/app/Api/Task.php @@ -71,7 +71,7 @@ class Task extends Base { $this->checkProjectPermission($project_id); - if ($owner_id !== 0 && ! $this->projectPermission->isMember($project_id, $owner_id)) { + if ($owner_id !== 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) { return false; } @@ -117,7 +117,7 @@ class Task extends Base return false; } - if ($owner_id !== null && ! $this->projectPermission->isMember($project_id, $owner_id)) { + if ($owner_id !== null && ! $this->projectPermission->isAssignable($project_id, $owner_id)) { return false; } diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 6d0ecae9..b090356e 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -17,18 +17,18 @@ abstract class Base extends \Kanboard\Core\Base * * @access public */ - public function beforeAction($controller, $action) + public function beforeAction() { $this->sessionManager->open(); $this->dispatcher->dispatch('app.bootstrap'); - $this->sendHeaders($action); + $this->sendHeaders(); $this->authenticationManager->checkCurrentSession(); - if (! $this->applicationAuthorization->isAllowed($controller, $action, Role::APP_PUBLIC)) { + if (! $this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) { $this->handleAuthentication(); - $this->handlePostAuthentication($controller, $action); - $this->checkApplicationAuthorization($controller, $action); - $this->checkProjectAuthorization($controller, $action); + $this->handlePostAuthentication(); + $this->checkApplicationAuthorization(); + $this->checkProjectAuthorization(); } } @@ -37,7 +37,7 @@ abstract class Base extends \Kanboard\Core\Base * * @access private */ - private function sendHeaders($action) + private function sendHeaders() { // HTTP secure headers $this->response->csp($this->container['cspRules']); @@ -45,7 +45,7 @@ abstract class Base extends \Kanboard\Core\Base $this->response->xss(); // Allow the public board iframe inclusion - if (ENABLE_XFRAME && $action !== 'readonly') { + if (ENABLE_XFRAME && $this->router->getAction() !== 'readonly') { $this->response->xframe(); } @@ -76,8 +76,10 @@ abstract class Base extends \Kanboard\Core\Base * * @access private */ - private function handlePostAuthentication($controller, $action) + private function handlePostAuthentication() { + $controller = strtolower($this->router->getController()); + $action = strtolower($this->router->getAction()); $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout'); if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) { @@ -94,9 +96,9 @@ abstract class Base extends \Kanboard\Core\Base * * @access private */ - private function checkApplicationAuthorization($controller, $action) + private function checkApplicationAuthorization() { - if (! $this->helper->user->hasAccess($controller, $action)) { + if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) { $this->forbidden(); } } @@ -106,7 +108,7 @@ abstract class Base extends \Kanboard\Core\Base * * @access private */ - private function checkProjectAuthorization($controller, $action) + private function checkProjectAuthorization() { $project_id = $this->request->getIntegerParam('project_id'); $task_id = $this->request->getIntegerParam('task_id'); @@ -116,7 +118,7 @@ abstract class Base extends \Kanboard\Core\Base $project_id = $this->taskFinder->getProjectId($task_id); } - if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($controller, $action, $project_id)) { + if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) { $this->forbidden(); } } diff --git a/app/Controller/UserHelper.php b/app/Controller/UserHelper.php index f164d0a6..041ed2c8 100644 --- a/app/Controller/UserHelper.php +++ b/app/Controller/UserHelper.php @@ -21,4 +21,17 @@ class UserHelper extends Base $users = $this->userFilterAutoCompleteFormatter->create($search)->filterByUsernameOrByName()->format(); $this->response->json($users); } + + /** + * User mention autocompletion (Ajax) + * + * @access public + */ + public function mention() + { + $project_id = $this->request->getStringParam('project_id'); + $query = $this->request->getStringParam('q'); + $users = $this->projectPermission->findUsernames($project_id, $query); + $this->response->json($users); + } } diff --git a/app/Core/Http/Router.php b/app/Core/Http/Router.php index 8b58a947..0fe80ecc 100644 --- a/app/Core/Http/Router.php +++ b/app/Core/Http/Router.php @@ -147,7 +147,7 @@ class Router extends Base } $instance = new $class($this->container); - $instance->beforeAction($this->controller, $this->action); + $instance->beforeAction(); $instance->{$this->action}(); return $instance; } diff --git a/app/Core/Session/SessionManager.php b/app/Core/Session/SessionManager.php index 776d02da..4f9f2c0a 100644 --- a/app/Core/Session/SessionManager.php +++ b/app/Core/Session/SessionManager.php @@ -100,7 +100,7 @@ class SessionManager extends Base ini_set('session.use_strict_mode', '1'); // Better session hash - ini_set('session.hash_function', 'sha512'); + ini_set('session.hash_function', '1'); // 'sha512' is not compatible with FreeBSD, only MD5 '0' and SHA-1 '1' seems to work ini_set('session.hash_bits_per_character', 6); // Set an additional entropy diff --git a/app/Helper/App.php b/app/Helper/App.php index 6e6028fc..2015d896 100644 --- a/app/Helper/App.php +++ b/app/Helper/App.php @@ -13,6 +13,30 @@ use Kanboard\Core\Base; class App extends Base { /** + * Make sidebar menu active + * + * @access public + * @param string $controller + * @param string $action + * @param string $plugin + * @return string + */ + public function checkMenuSelection($controller, $action = '', $plugin = '') + { + $result = strtolower($this->getRouterController()) === strtolower($controller); + + if ($result && $action !== '') { + $result = strtolower($this->getRouterAction()) === strtolower($action); + } + + if ($result && $plugin !== '') { + $result = strtolower($this->getPluginName()) === strtolower($plugin); + } + + return $result ? 'class="active"' : ''; + } + + /** * Get plugin name from route * * @access public diff --git a/app/Integration/BitbucketWebhook.php b/app/Integration/BitbucketWebhook.php index 97a39437..3814e35c 100644 --- a/app/Integration/BitbucketWebhook.php +++ b/app/Integration/BitbucketWebhook.php @@ -81,7 +81,7 @@ class BitbucketWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['actor']['username']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } @@ -213,7 +213,7 @@ class BitbucketWebhook extends \Kanboard\Core\Base return false; } - if (! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { return false; } diff --git a/app/Integration/GithubWebhook.php b/app/Integration/GithubWebhook.php index c8b53e37..6dd7a8d9 100644 --- a/app/Integration/GithubWebhook.php +++ b/app/Integration/GithubWebhook.php @@ -149,7 +149,7 @@ class GithubWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['comment']['user']['login']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } @@ -266,7 +266,7 @@ class GithubWebhook extends \Kanboard\Core\Base $user = $this->user->getByUsername($issue['assignee']['login']); $task = $this->taskFinder->getByReference($this->project_id, $issue['number']); - if (! empty($user) && ! empty($task) && $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! empty($task) && $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $event = array( 'project_id' => $this->project_id, 'task_id' => $task['id'], diff --git a/app/Integration/GitlabWebhook.php b/app/Integration/GitlabWebhook.php index 17b6da70..7ab4cedf 100644 --- a/app/Integration/GitlabWebhook.php +++ b/app/Integration/GitlabWebhook.php @@ -273,7 +273,7 @@ class GitlabWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['user']['username']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index 08fd382e..a179de05 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Članovi projekta', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index 6b5ad8dd..655e77b0 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 1428626e..abccba95 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index e244ede5..e778d786 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1099,4 +1099,18 @@ return array( 'Enter group name...' => 'Geben Sie den Gruppennamen ein...', 'Role:' => 'Rolle:', 'Project members' => 'Projektmitglieder', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index b6ad2dfa..97a3953e 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Miembros de proyecto', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + 'Estimated vs actual time' => 'Tiempo estimado vs real', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index e4e18ab8..e4badefc 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 5c730eb7..677eeb0a 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -714,7 +714,7 @@ return array( 'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs', 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?', 'Edit link' => 'Modifier un lien', - 'Start to type task title...' => 'Entrez le titre de la tâche…', + 'Start to type task title...' => 'Entrez le titre de la tâche...', 'A task cannot be linked to itself' => 'Une tâche ne peut être liée à elle-même', 'The exact same link already exists' => 'Un lien identique existe déjà', 'Recurrent task is scheduled to be generated' => 'La tâche récurrente est programmée pour être créée', @@ -1102,4 +1102,18 @@ return array( 'Enter group name...' => 'Entrez le nom du groupe...', 'Role:' => 'Rôle :', 'Project members' => 'Membres du project', + 'Compare hours for "%s"' => 'Comparer les heures pour « %s »', + '%s mentioned you in the task #%d' => '%s vous a mentionné dans la tâche n°%d', + '%s mentioned you in a comment on the task #%d' => '%s vous a mentionné dans un commentaire de la tâche n°%d', + 'You were mentioned in the task #%d' => 'Vous avez été mentionné dans la tâche n°%d', + 'You were mentioned in a comment on the task #%d' => 'Vous avez été mentionné dans un commentaire de la tâche n°%d', + 'Mentioned' => 'Mentionné', + 'Compare Estimated Time vs Actual Time' => 'Comparer le temps estimé et le temps actuel', + 'Estimated hours: ' => 'Heures estimées :', + 'Actual hours: ' => 'Heures actuelles :', + 'Hours Spent' => 'Heures passées', + 'Hours Estimated' => 'Heures estimées', + 'Estimated Time' => 'Temps estimé', + 'Actual Time' => 'Temps actuel', + 'Estimated vs actual time' => 'Temps estimé vs actuel', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 6fb18019..c7845c8c 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 686124fb..c1f55a5f 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Anggota proyek', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 69975f43..1e0ea8a3 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index b4bcfacd..17c93db0 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 5d039592..a3c00adb 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Prosjektmedlemmer', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 16621650..09fd807a 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 0a3fed4c..d9e87651 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -811,7 +811,7 @@ return array( // 'The field "%s" have been updated' => '', // 'The description have been modified' => '', // 'Do you really want to close the task "%s" as well as all subtasks?' => '', - //'Swimlane: %s' => '', + // 'Swimlane: %s' => '', 'I want to receive notifications for:' => 'Wysyłaj powiadomienia dla:', 'All tasks' => 'Wszystkich zadań', 'Only for tasks assigned to me' => 'Tylko zadań przypisanych do mnie', @@ -911,7 +911,7 @@ return array( 'Current column: %s' => 'Bieżąca kolumna: %s', 'Current category: %s' => 'Bieżąca kategoria: %s', 'no category' => 'brak kategorii', - //'Current assignee: %s' => '', + // 'Current assignee: %s' => '', 'not assigned' => 'Brak osoby odpowiedzialnej', 'Author:' => 'Autor', 'contributors' => 'współautorzy', @@ -1005,7 +1005,7 @@ return array( 'Expand swimlane' => 'Rozwiń swimlane', 'Add a new filter' => 'Dodaj nowy filtr', 'Share with all project members' => 'Udostępnij wszystkim uczestnikom projektu', - //'Shared' => '', + // 'Shared' => '', 'Owner' => 'Właściciel', 'Unread notifications' => 'Nieprzeczytane powiadomienia', 'My filters' => 'Moje filtry', @@ -1099,4 +1099,18 @@ return array( 'Enter group name...' => 'Wprowadź nazwę grupy...', 'Role:' => 'Rola:', 'Project members' => 'Uczestnicy projektu', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index f3e988eb..0dbbfda7 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Membros de projeto', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index 0608bd4b..4dee0525 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1099,4 +1099,18 @@ return array( 'Enter group name...' => 'Escreva o nome do Grupo', 'Role:' => 'Função:', 'Project members' => 'Membros do projecto', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 06b3b9d1..508f5b21 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Участники проекта', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 8ad8d002..21dc3f4c 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 015fbfc0..e3bf966a 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 76606417..eab705e4 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 6d6f1dc9..15e5a253 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 0355b1fd..1fde04cb 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1099,4 +1099,18 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', + // 'Compare hours for "%s"' => '', + // '%s mentioned you in the task #%d' => '', + // '%s mentioned you in a comment on the task #%d' => '', + // 'You were mentioned in the task #%d' => '', + // 'You were mentioned in a comment on the task #%d' => '', + // 'Mentioned' => '', + // 'Compare Estimated Time vs Actual Time' => '', + // 'Estimated hours: ' => '', + // 'Actual hours: ' => '', + // 'Hours Spent' => '', + // 'Hours Estimated' => '', + // 'Estimated Time' => '', + // 'Actual Time' => '', + // 'Estimated vs actual time' => '', ); diff --git a/app/Model/Action.php b/app/Model/Action.php index 289471f4..d3d18edb 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -427,7 +427,7 @@ class Action extends Base return $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false; case 'user_id': case 'owner_id': - return $this->projectPermission->isMember($project_id, $param['value']) ? $param['value'] : false; + return $this->projectPermission->isAssignable($project_id, $param['value']) ? $param['value'] : false; default: return $param['value']; } diff --git a/app/Model/Comment.php b/app/Model/Comment.php index f60a96e3..71e964dc 100644 --- a/app/Model/Comment.php +++ b/app/Model/Comment.php @@ -26,8 +26,9 @@ class Comment extends Base * * @var string */ - const EVENT_UPDATE = 'comment.update'; - const EVENT_CREATE = 'comment.create'; + const EVENT_UPDATE = 'comment.update'; + const EVENT_CREATE = 'comment.create'; + const EVENT_USER_MENTION = 'comment.user.mention'; /** * Get all comments for a given task @@ -110,7 +111,9 @@ class Comment extends Base $comment_id = $this->persist(self::TABLE, $values); if ($comment_id) { - $this->container['dispatcher']->dispatch(self::EVENT_CREATE, new CommentEvent(array('id' => $comment_id) + $values)); + $event = new CommentEvent(array('id' => $comment_id) + $values); + $this->dispatcher->dispatch(self::EVENT_CREATE, $event); + $this->userMention->fireEvents($values['comment'], self::EVENT_USER_MENTION, $event); } return $comment_id; diff --git a/app/Model/Notification.php b/app/Model/Notification.php index f1122993..87c1a796 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -74,6 +74,10 @@ class Notification extends Base return e('%s commented on the task #%d', $event_author, $event_data['task']['id']); case File::EVENT_CREATE: return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']); + case Task::EVENT_USER_MENTION: + return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']); + case Comment::EVENT_USER_MENTION: + return e('%s mentioned you in a comment on the task #%d', $event_author, $event_data['task']['id']); default: return e('Notification'); } @@ -91,52 +95,40 @@ class Notification extends Base { switch ($event_name) { case File::EVENT_CREATE: - $title = e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); - break; + return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); case Comment::EVENT_CREATE: - $title = e('New comment on task #%d', $event_data['comment']['task_id']); - break; + return e('New comment on task #%d', $event_data['comment']['task_id']); case Comment::EVENT_UPDATE: - $title = e('Comment updated on task #%d', $event_data['comment']['task_id']); - break; + return e('Comment updated on task #%d', $event_data['comment']['task_id']); case Subtask::EVENT_CREATE: - $title = e('New subtask on task #%d', $event_data['subtask']['task_id']); - break; + return e('New subtask on task #%d', $event_data['subtask']['task_id']); case Subtask::EVENT_UPDATE: - $title = e('Subtask updated on task #%d', $event_data['subtask']['task_id']); - break; + return e('Subtask updated on task #%d', $event_data['subtask']['task_id']); case Task::EVENT_CREATE: - $title = e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); - break; + return e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); case Task::EVENT_UPDATE: - $title = e('Task updated #%d', $event_data['task']['id']); - break; + return e('Task updated #%d', $event_data['task']['id']); case Task::EVENT_CLOSE: - $title = e('Task #%d closed', $event_data['task']['id']); - break; + return e('Task #%d closed', $event_data['task']['id']); case Task::EVENT_OPEN: - $title = e('Task #%d opened', $event_data['task']['id']); - break; + return e('Task #%d opened', $event_data['task']['id']); case Task::EVENT_MOVE_COLUMN: - $title = e('Column changed for task #%d', $event_data['task']['id']); - break; + return e('Column changed for task #%d', $event_data['task']['id']); case Task::EVENT_MOVE_POSITION: - $title = e('New position for task #%d', $event_data['task']['id']); - break; + return e('New position for task #%d', $event_data['task']['id']); case Task::EVENT_MOVE_SWIMLANE: - $title = e('Swimlane changed for task #%d', $event_data['task']['id']); - break; + return e('Swimlane changed for task #%d', $event_data['task']['id']); case Task::EVENT_ASSIGNEE_CHANGE: - $title = e('Assignee changed on task #%d', $event_data['task']['id']); - break; + return e('Assignee changed on task #%d', $event_data['task']['id']); case Task::EVENT_OVERDUE: $nb = count($event_data['tasks']); - $title = $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']); - break; + return $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']); + case Task::EVENT_USER_MENTION: + return e('You were mentioned in the task #%d', $event_data['task']['id']); + case Comment::EVENT_USER_MENTION: + return e('You were mentioned in a comment on the task #%d', $event_data['task']['id']); default: - $title = e('Notification'); + return e('Notification'); } - - return $title; } } diff --git a/app/Model/ProjectGroupRoleFilter.php b/app/Model/ProjectGroupRoleFilter.php new file mode 100644 index 00000000..989d3073 --- /dev/null +++ b/app/Model/ProjectGroupRoleFilter.php @@ -0,0 +1,89 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project Group Role Filter + * + * @package model + * @author Frederic Guillot + */ +class ProjectGroupRoleFilter extends Base +{ + /** + * Query + * + * @access protected + * @var \PicoDb\Table + */ + protected $query; + + /** + * Initialize filter + * + * @access public + * @return UserFilter + */ + public function create() + { + $this->query = $this->db->table(ProjectGroupRole::TABLE); + return $this; + } + + /** + * Get all results of the filter + * + * @access public + * @param string $column + * @return array + */ + public function findAll($column = '') + { + if ($column !== '') { + return $this->query->asc($column)->findAllByColumn($column); + } + + return $this->query->findAll(); + } + + /** + * Get the PicoDb query + * + * @access public + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->query; + } + + /** + * Filter by project id + * + * @access public + * @param integer $project_id + * @return ProjectUserRoleFilter + */ + public function filterByProjectId($project_id) + { + $this->query->eq(ProjectGroupRole::TABLE.'.project_id', $project_id); + return $this; + } + + /** + * Filter by username + * + * @access public + * @param string $input + * @return ProjectUserRoleFilter + */ + public function startWithUsername($input) + { + $this->query + ->join(GroupMember::TABLE, 'group_id', 'group_id', ProjectGroupRole::TABLE) + ->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE) + ->ilike(User::TABLE.'.username', $input.'%'); + + return $this; + } +} diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index 4ad9bbf1..cea62e13 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -44,6 +44,25 @@ class ProjectPermission extends Base } /** + * Get all usernames (fetch users from groups) + * + * @access public + * @param integer $project_id + * @param string $input + * @return array + */ + public function findUsernames($project_id, $input) + { + $userMembers = $this->projectUserRoleFilter->create()->filterByProjectId($project_id)->startWithUsername($input)->findAll('username'); + $groupMembers = $this->projectGroupRoleFilter->create()->filterByProjectId($project_id)->startWithUsername($input)->findAll('username'); + $members = array_unique(array_merge($userMembers, $groupMembers)); + + sort($members); + + return $members; + } + + /** * Return true if everybody is allowed for the project * * @access public @@ -86,12 +105,25 @@ class ProjectPermission extends Base * @param integer $user_id * @return boolean */ - public function isMember($project_id, $user_id) + public function isAssignable($project_id, $user_id) { return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER)); } /** + * Return true if the user is member + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function isMember($project_id, $user_id) + { + return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER, Role::PROJECT_VIEWER)); + } + + /** * Get active project ids by user * * @access public diff --git a/app/Model/ProjectUserRoleFilter.php b/app/Model/ProjectUserRoleFilter.php new file mode 100644 index 00000000..64403643 --- /dev/null +++ b/app/Model/ProjectUserRoleFilter.php @@ -0,0 +1,88 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project User Role Filter + * + * @package model + * @author Frederic Guillot + */ +class ProjectUserRoleFilter extends Base +{ + /** + * Query + * + * @access protected + * @var \PicoDb\Table + */ + protected $query; + + /** + * Initialize filter + * + * @access public + * @return UserFilter + */ + public function create() + { + $this->query = $this->db->table(ProjectUserRole::TABLE); + return $this; + } + + /** + * Get all results of the filter + * + * @access public + * @param string $column + * @return array + */ + public function findAll($column = '') + { + if ($column !== '') { + return $this->query->asc($column)->findAllByColumn($column); + } + + return $this->query->findAll(); + } + + /** + * Get the PicoDb query + * + * @access public + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->query; + } + + /** + * Filter by project id + * + * @access public + * @param integer $project_id + * @return ProjectUserRoleFilter + */ + public function filterByProjectId($project_id) + { + $this->query->eq(ProjectUserRole::TABLE.'.project_id', $project_id); + return $this; + } + + /** + * Filter by username + * + * @access public + * @param string $input + * @return ProjectUserRoleFilter + */ + public function startWithUsername($input) + { + $this->query + ->join(User::TABLE, 'id', 'user_id') + ->ilike(User::TABLE.'.username', $input.'%'); + + return $this; + } +} diff --git a/app/Model/Task.php b/app/Model/Task.php index f1cd094f..7aa9e312 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -41,6 +41,7 @@ class Task extends Base const EVENT_CREATE_UPDATE = 'task.create_update'; const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change'; const EVENT_OVERDUE = 'task.overdue'; + const EVENT_USER_MENTION = 'task.user.mention'; /** * Recurrence: status diff --git a/app/Model/TaskCreation.php b/app/Model/TaskCreation.php index 5ef1a04b..88912d4d 100644 --- a/app/Model/TaskCreation.php +++ b/app/Model/TaskCreation.php @@ -86,8 +86,13 @@ class TaskCreation extends Base */ private function fireEvents($task_id, array $values) { - $values['task_id'] = $task_id; - $this->container['dispatcher']->dispatch(Task::EVENT_CREATE_UPDATE, new TaskEvent($values)); - $this->container['dispatcher']->dispatch(Task::EVENT_CREATE, new TaskEvent($values)); + $event = new TaskEvent(array('task_id' => $task_id) + $values); + + $this->dispatcher->dispatch(Task::EVENT_CREATE_UPDATE, $event); + $this->dispatcher->dispatch(Task::EVENT_CREATE, $event); + + if (! empty($values['description'])) { + $this->userMention->fireEvents($values['description'], Task::EVENT_USER_MENTION, $event); + } } } diff --git a/app/Model/TaskPosition.php b/app/Model/TaskPosition.php index da363cb3..53193c9b 100644 --- a/app/Model/TaskPosition.php +++ b/app/Model/TaskPosition.php @@ -26,28 +26,34 @@ class TaskPosition extends Base */ public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true) { + $result = false; + if ($position < 1) { - return false; + return $result; } $task = $this->taskFinder->getById($task_id); - // Ignore closed tasks if ($task['is_active'] == Task::STATUS_CLOSED) { return true; } - $result = false; + $this->db->startTransaction(); - if ($task['swimlane_id'] != $swimlane_id) { - $result = $this->saveSwimlaneChange($project_id, $task_id, $position, $task['column_id'], $column_id, $task['swimlane_id'], $swimlane_id); - } elseif ($task['column_id'] != $column_id) { - $result = $this->saveColumnChange($project_id, $task_id, $position, $swimlane_id, $task['column_id'], $column_id); + if ($task['swimlane_id'] != $swimlane_id || $task['column_id'] != $column_id) { + $result = $this->moveTaskToAnotherColumn($task, $swimlane_id, $column_id, $position); } elseif ($task['position'] != $position) { - $result = $this->savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id); + $result = $this->moveTaskWithinSameColumn($task, $position); } - if ($result && $fire_events) { + if (! $result) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + + if ($fire_events) { $this->fireEvents($task, $column_id, $position, $swimlane_id); } @@ -55,145 +61,87 @@ class TaskPosition extends Base } /** - * Move a task to another swimlane + * Move a task to another column/swimlane * * @access private - * @param integer $project_id - * @param integer $task_id - * @param integer $position - * @param integer $original_column_id - * @param integer $new_column_id - * @param integer $original_swimlane_id - * @param integer $new_swimlane_id + * @param array $task + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $position * @return boolean */ - private function saveSwimlaneChange($project_id, $task_id, $position, $original_column_id, $new_column_id, $original_swimlane_id, $new_swimlane_id) + private function moveTaskToAnotherColumn(array $task, $swimlane_id, $column_id, $position) { - $this->db->startTransaction(); - $r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $original_swimlane_id); - $r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $new_swimlane_id); - $this->db->closeTransaction(); + $results = array(); + $max = $this->getQuery($task['project_id'], $swimlane_id, $column_id)->count(); + $position = $max > 0 && $position > $max ? $max + 1 : $position; - return $r1 && $r2; - } + $results[] = $this->getQuery($task['project_id'], $task['swimlane_id'], $task['column_id'])->gt('position', $task['position'])->decrement('position', 1); + $results[] = $this->getQuery($task['project_id'], $swimlane_id, $column_id)->gte('position', $position)->increment('position', 1); + $results[] = $this->updateTaskPosition($task['id'], $swimlane_id, $column_id, $position); - /** - * Move a task to another column - * - * @access private - * @param integer $project_id - * @param integer $task_id - * @param integer $position - * @param integer $swimlane_id - * @param integer $original_column_id - * @param integer $new_column_id - * @return boolean - */ - private function saveColumnChange($project_id, $task_id, $position, $swimlane_id, $original_column_id, $new_column_id) - { - $this->db->startTransaction(); - $r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $swimlane_id); - $r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $swimlane_id); - $this->db->closeTransaction(); - - return $r1 && $r2; + return !in_array(false, $results, true); } /** - * Move a task to another position in the same column + * Move a task within the same column * * @access private - * @param integer $project_id - * @param integer $task_id - * @param integer $position - * @param integer $column_id - * @param integer $swimlane_id + * @param array $task + * @param integer $position * @return boolean */ - private function savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id) + private function moveTaskWithinSameColumn(array $task, $position) { - $this->db->startTransaction(); - $result = $this->saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id); - $this->db->closeTransaction(); + $results = array(); + $max = $this->getQuery($task['project_id'], $task['swimlane_id'], $task['column_id'])->count(); + $position = $max > 0 && $position > $max ? $max : $position; + + if ($position >= $max) { + $results[] = $this->getQuery($task['project_id'], $task['swimlane_id'], $task['column_id'])->lte('position', $position)->decrement('position', 1); + } else { + $results[] = $this->getQuery($task['project_id'], $task['swimlane_id'], $task['column_id'])->gte('position', $position)->increment('position', 1); + } - return $result; + $results[] = $this->updateTaskPosition($task['id'], $task['swimlane_id'], $task['column_id'], $position); + + return !in_array(false, $results, true); } /** - * Save all task positions for one column + * Update final task position * * @access private - * @param integer $project_id - * @param integer $task_id - * @param integer $position - * @param integer $column_id - * @param integer $swimlane_id + * @param integer $task_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $position * @return boolean */ - private function saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id) + private function updateTaskPosition($task_id, $swimlane_id, $column_id, $position) { - $tasks_ids = $this->db->table(Task::TABLE) + return $this->db->table(Task::TABLE) + ->eq('id', $task_id) ->eq('is_active', 1) - ->eq('swimlane_id', $swimlane_id) - ->eq('project_id', $project_id) - ->eq('column_id', $column_id) - ->neq('id', $task_id) - ->asc('position') - ->asc('id') - ->findAllByColumn('id'); - - $offset = 1; - - foreach ($tasks_ids as $current_task_id) { - - // Insert the new task - if ($position == $offset) { - if (! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) { - return false; - } - $offset++; - } - - // Rewrite other tasks position - if (! $this->saveTaskPosition($current_task_id, $offset, $column_id, $swimlane_id)) { - return false; - } - - $offset++; - } - - // Insert the new task at the bottom and normalize bad position - if ($position >= $offset && ! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) { - return false; - } - - return true; + ->update(array('position' => $position, 'column_id' => $column_id, 'swimlane_id' => $swimlane_id, 'date_modification' => time())); } /** - * Save new task position + * Get common query * * @access private - * @param integer $task_id - * @param integer $position - * @param integer $column_id - * @param integer $swimlane_id - * @return boolean + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @return \PicoDb\Table */ - private function saveTaskPosition($task_id, $position, $column_id, $swimlane_id) + private function getQuery($project_id, $swimlane_id, $column_id) { - $result = $this->db->table(Task::TABLE)->eq('id', $task_id)->update(array( - 'position' => $position, - 'column_id' => $column_id, - 'swimlane_id' => $swimlane_id, - )); - - if (! $result) { - $this->db->cancelTransaction(); - return false; - } - - return true; + return $this->db->table(Task::TABLE) + ->eq('project_id', $project_id) + ->eq('swimlane_id', $swimlane_id) + ->eq('column_id', $column_id) + ->eq('is_active', 1); } /** diff --git a/app/Model/UserMention.php b/app/Model/UserMention.php new file mode 100644 index 00000000..97a4e419 --- /dev/null +++ b/app/Model/UserMention.php @@ -0,0 +1,61 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Event\GenericEvent; + +/** + * User Mention + * + * @package model + * @author Frederic Guillot + */ +class UserMention extends Base +{ + /** + * Get list of mentioned users + * + * @access public + * @param string $content + * @return array + */ + public function getMentionedUsers($content) + { + $users = array(); + + if (preg_match_all('/@([^\s]+)/', $content, $matches)) { + $users = $this->db->table(User::TABLE) + ->columns('id', 'username', 'name', 'email', 'language') + ->eq('notifications_enabled', 1) + ->neq('id', $this->userSession->getId()) + ->in('username', array_unique($matches[1])) + ->findAll(); + } + + return $users; + } + + /** + * Fire events for user mentions + * + * @access public + * @param string $content + * @param string $eventName + * @param GenericEvent $event + */ + public function fireEvents($content, $eventName, GenericEvent $event) + { + if (empty($event['project_id'])) { + $event['project_id'] = $this->taskFinder->getProjectId($event['task_id']); + } + + $users = $this->getMentionedUsers($content); + + foreach ($users as $user) { + if ($this->projectPermission->isMember($event['project_id'], $user['id'])) { + $event['mention'] = $user; + $this->dispatcher->dispatch($eventName, $event); + } + } + } +} diff --git a/app/Model/UserNotification.php b/app/Model/UserNotification.php index e00f23c5..8161288c 100644 --- a/app/Model/UserNotification.php +++ b/app/Model/UserNotification.php @@ -21,18 +21,12 @@ class UserNotification extends Base */ public function sendNotifications($event_name, array $event_data) { - $logged_user_id = $this->userSession->isLogged() ? $this->userSession->getId() : 0; - $users = $this->getUsersWithNotificationEnabled($event_data['task']['project_id'], $logged_user_id); - - if (! empty($users)) { - foreach ($users as $user) { - if ($this->userNotificationFilter->shouldReceiveNotification($user, $event_data)) { - $this->sendUserNotification($user, $event_name, $event_data); - } - } + $users = $this->getUsersWithNotificationEnabled($event_data['task']['project_id'], $this->userSession->getId()); - // Restore locales - $this->config->setupTranslations(); + foreach ($users as $user) { + if ($this->userNotificationFilter->shouldReceiveNotification($user, $event_data)) { + $this->sendUserNotification($user, $event_name, $event_data); + } } } @@ -58,6 +52,9 @@ class UserNotification extends Base foreach ($this->userNotificationType->getSelectedTypes($user['id']) as $type) { $this->userNotificationType->getType($type)->notifyUser($user, $event_name, $event_data); } + + // Restore locales + $this->config->setupTranslations(); } /** diff --git a/app/Notification/Mail.php b/app/Notification/Mail.php index 98bffef2..d05dbdf2 100644 --- a/app/Notification/Mail.php +++ b/app/Notification/Mail.php @@ -121,6 +121,10 @@ class Mail extends Base implements NotificationInterface case Task::EVENT_ASSIGNEE_CHANGE: $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); break; + case Task::EVENT_USER_MENTION: + case Comment::EVENT_USER_MENTION: + $subject = $this->getStandardMailSubject(e('Mentioned'), $event_data); + break; case Task::EVENT_OVERDUE: $subject = e('[%s] Overdue tasks', $event_data['project_name']); break; diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 46ba5be8..6e7c49d9 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -106,6 +106,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Taskmodification', '*', Role::PROJECT_MEMBER); $acl->add('Taskstatus', '*', Role::PROJECT_MEMBER); $acl->add('Timer', '*', Role::PROJECT_MEMBER); + $acl->add('UserHelper', array('mention'), Role::PROJECT_MEMBER); return $acl; } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index fad73047..559a78c1 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -42,7 +42,9 @@ class ClassProvider implements ServiceProviderInterface 'ProjectNotification', 'ProjectMetadata', 'ProjectGroupRole', + 'ProjectGroupRoleFilter', 'ProjectUserRole', + 'ProjectUserRoleFilter', 'RememberMeSession', 'Subtask', 'SubtaskExport', @@ -67,6 +69,7 @@ class ClassProvider implements ServiceProviderInterface 'User', 'UserImport', 'UserLocking', + 'UserMention', 'UserNotification', 'UserNotificationFilter', 'UserUnreadNotification', diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index 394573e4..1bde24dd 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -2,6 +2,7 @@ namespace Kanboard\Subscriber; +use Kanboard\Core\Base; use Kanboard\Event\GenericEvent; use Kanboard\Model\Task; use Kanboard\Model\Comment; @@ -9,34 +10,40 @@ use Kanboard\Model\Subtask; use Kanboard\Model\File; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class NotificationSubscriber extends \Kanboard\Core\Base implements EventSubscriberInterface +class NotificationSubscriber extends Base implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( - Task::EVENT_CREATE => array('execute', 0), - Task::EVENT_UPDATE => array('execute', 0), - Task::EVENT_CLOSE => array('execute', 0), - Task::EVENT_OPEN => array('execute', 0), - Task::EVENT_MOVE_COLUMN => array('execute', 0), - Task::EVENT_MOVE_POSITION => array('execute', 0), - Task::EVENT_MOVE_SWIMLANE => array('execute', 0), - Task::EVENT_ASSIGNEE_CHANGE => array('execute', 0), - Subtask::EVENT_CREATE => array('execute', 0), - Subtask::EVENT_UPDATE => array('execute', 0), - Comment::EVENT_CREATE => array('execute', 0), - Comment::EVENT_UPDATE => array('execute', 0), - File::EVENT_CREATE => array('execute', 0), + Task::EVENT_USER_MENTION => 'handleEvent', + Task::EVENT_CREATE => 'handleEvent', + Task::EVENT_UPDATE => 'handleEvent', + Task::EVENT_CLOSE => 'handleEvent', + Task::EVENT_OPEN => 'handleEvent', + Task::EVENT_MOVE_COLUMN => 'handleEvent', + Task::EVENT_MOVE_POSITION => 'handleEvent', + Task::EVENT_MOVE_SWIMLANE => 'handleEvent', + Task::EVENT_ASSIGNEE_CHANGE => 'handleEvent', + Subtask::EVENT_CREATE => 'handleEvent', + Subtask::EVENT_UPDATE => 'handleEvent', + Comment::EVENT_CREATE => 'handleEvent', + Comment::EVENT_UPDATE => 'handleEvent', + Comment::EVENT_USER_MENTION => 'handleEvent', + File::EVENT_CREATE => 'handleEvent', ); } - public function execute(GenericEvent $event, $event_name) + public function handleEvent(GenericEvent $event, $event_name) { $event_data = $this->getEventData($event); if (! empty($event_data)) { - $this->userNotification->sendNotifications($event_name, $event_data); - $this->projectNotification->sendNotifications($event_data['task']['project_id'], $event_name, $event_data); + if (! empty($event['mention'])) { + $this->userNotification->sendUserNotification($event['mention'], $event_name, $event_data); + } else { + $this->userNotification->sendNotifications($event_name, $event_data); + $this->projectNotification->sendNotifications($event_data['task']['project_id'], $event_name, $event_data); + } } } diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php index 746fcebb..19eef8d5 100644 --- a/app/Template/analytic/sidebar.php +++ b/app/Template/analytic/sidebar.php @@ -1,26 +1,26 @@ <div class="sidebar"> <h2><?= t('Reportings') ?></h2> <ul> - <li <?= $this->app->getRouterAction() === 'tasks' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'tasks') ?>> <?= $this->url->link(t('Task distribution'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'users' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'users') ?>> <?= $this->url->link(t('User repartition'), 'analytic', 'users', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'cfd' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'cfd') ?>> <?= $this->url->link(t('Cumulative flow diagram'), 'analytic', 'cfd', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'burndown' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'burndown') ?>> <?= $this->url->link(t('Burndown chart'), 'analytic', 'burndown', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'averagetimebycolumn' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'averageTimeByColumn') ?>> <?= $this->url->link(t('Average time into each column'), 'analytic', 'averageTimeByColumn', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'leadandcycletime' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('analytic', 'leadAndCycleTime') ?>> <?= $this->url->link(t('Lead and cycle time'), 'analytic', 'leadAndCycleTime', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'comparehours' ? 'class="active"' : '' ?>> - <?= $this->url->link(t('Compare hours'), 'analytic', 'compareHours', array('project_id' => $project['id'])) ?> + <li <?= $this->app->checkMenuSelection('analytic', 'compareHours') ?>> + <?= $this->url->link(t('Estimated vs actual time'), 'analytic', 'compareHours', array('project_id' => $project['id'])) ?> </li> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php index 552a9c27..b5e14aaf 100644 --- a/app/Template/app/sidebar.php +++ b/app/Template/app/sidebar.php @@ -1,25 +1,25 @@ <div class="sidebar"> <h2><?= $this->e($user['name'] ?: $user['username']) ?></h2> <ul> - <li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'index') ?>> <?= $this->url->link(t('Overview'), 'app', 'index', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'projects' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'projects') ?>> <?= $this->url->link(t('My projects'), 'app', 'projects', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'tasks' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'tasks') ?>> <?= $this->url->link(t('My tasks'), 'app', 'tasks', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'subtasks' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'subtasks') ?>> <?= $this->url->link(t('My subtasks'), 'app', 'subtasks', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'calendar' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'calendar') ?>> <?= $this->url->link(t('My calendar'), 'app', 'calendar', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'activity') ?>> <?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('app', 'notifications') ?>> <?= $this->url->link(t('My notifications'), 'app', 'notifications', array('user_id' => $user['id'])) ?> </li> <?= $this->hook->render('template:dashboard:sidebar') ?> diff --git a/app/Template/comment/create.php b/app/Template/comment/create.php index 8bcbe0f7..e9a6404d 100644 --- a/app/Template/comment/create.php +++ b/app/Template/comment/create.php @@ -17,7 +17,18 @@ </li> </ul> <div class="write-area"> - <?= $this->form->textarea('comment', $values, $errors, array(! isset($skip_cancel) ? 'autofocus' : '', 'required', 'placeholder="'.t('Leave a comment').'"'), 'comment-textarea') ?> + <?= $this->form->textarea( + 'comment', + $values, + $errors, + array( + ! isset($skip_cancel) ? 'autofocus' : '', + 'required', + 'placeholder="'.t('Leave a comment').'"', + 'data-mention-search-url="'.$this->url->href('UserHelper', 'mention', array('project_id' => $task['project_id'])).'"', + ), + 'comment-textarea' + ) ?> </div> <div class="preview-area"> <div class="markdown"></div> diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 4195cde1..a8174505 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -1,37 +1,37 @@ <div class="sidebar"> <h2><?= t('Actions') ?></h2> <ul> - <li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'index') ?>> <?= $this->url->link(t('About'), 'config', 'index') ?> </li> - <li <?= $this->app->getRouterAction() === 'plugins' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'plugins') ?>> <?= $this->url->link(t('Plugins'), 'config', 'plugins') ?> </li> - <li <?= $this->app->getRouterAction() === 'application' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'application') ?>> <?= $this->url->link(t('Application settings'), 'config', 'application') ?> </li> - <li <?= $this->app->getRouterAction() === 'project' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'project') ?>> <?= $this->url->link(t('Project settings'), 'config', 'project') ?> </li> - <li <?= $this->app->getRouterAction() === 'board' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'board') ?>> <?= $this->url->link(t('Board settings'), 'config', 'board') ?> </li> - <li <?= $this->app->getRouterAction() === 'calendar' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'calendar') ?>> <?= $this->url->link(t('Calendar settings'), 'config', 'calendar') ?> </li> - <li <?= $this->app->getRouterController() === 'link' && $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('link') ?>> <?= $this->url->link(t('Link settings'), 'link', 'index') ?> </li> - <li <?= $this->app->getRouterController() === 'currency' && $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('currency', 'index') ?>> <?= $this->url->link(t('Currency rates'), 'currency', 'index') ?> </li> - <li <?= $this->app->getRouterAction() === 'integrations' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'integrations') ?>> <?= $this->url->link(t('Integrations'), 'config', 'integrations') ?> </li> - <li <?= $this->app->getRouterAction() === 'webhook' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'webhook') ?>> <?= $this->url->link(t('Webhooks'), 'config', 'webhook') ?> </li> - <li <?= $this->app->getRouterAction() === 'api' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('config', 'api') ?>> <?= $this->url->link(t('API'), 'config', 'api') ?> </li> <li> diff --git a/app/Template/notification/comment_user_mention.php b/app/Template/notification/comment_user_mention.php new file mode 100644 index 00000000..59f5127e --- /dev/null +++ b/app/Template/notification/comment_user_mention.php @@ -0,0 +1,7 @@ +<h2><?= t('You were mentioned in a comment on the task #%d', $task['id']) ?></h2> + +<p><?= $this->e($task['title']) ?></p> + +<?= $this->text->markdown($comment['comment']) ?> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/task_user_mention.php b/app/Template/notification/task_user_mention.php new file mode 100644 index 00000000..40ddddca --- /dev/null +++ b/app/Template/notification/task_user_mention.php @@ -0,0 +1,7 @@ +<h2><?= t('You were mentioned in the task #%d', $task['id']) ?></h2> +<p><?= $this->e($task['title']) ?></p> + +<h2><?= t('Description') ?></h2> +<?= $this->text->markdown($task['description']) ?> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index 92794038..025c410d 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -1,60 +1,61 @@ <div class="sidebar"> <h2><?= t('Actions') ?></h2> <ul> - <li <?= $this->app->getRouterAction() === 'show' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'show') ?>> <?= $this->url->link(t('Summary'), 'project', 'show', array('project_id' => $project['id'])) ?> </li> <?php if ($this->user->hasProjectAccess('customfilter', 'index', $project['id'])): ?> - <li <?= $this->app->getRouterController() === 'customfilter' && $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('customfilter') ?>> <?= $this->url->link(t('Custom filters'), 'customfilter', 'index', array('project_id' => $project['id'])) ?> </li> <?php endif ?> <?php if ($this->user->hasProjectAccess('project', 'edit', $project['id'])): ?> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'share' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'share') ?>> <?= $this->url->link(t('Public access'), 'project', 'share', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'notifications') ?>> <?= $this->url->link(t('Notifications'), 'project', 'notifications', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'integrations' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'integrations') ?>> <?= $this->url->link(t('Integrations'), 'project', 'integrations', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'edit' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'edit') ?>> <?= $this->url->link(t('Edit project'), 'project', 'edit', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'column' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('column') ?>> <?= $this->url->link(t('Columns'), 'column', 'index', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'swimlane' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('swimlane') ?>> <?= $this->url->link(t('Swimlanes'), 'swimlane', 'index', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'category' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('category') ?>> <?= $this->url->link(t('Categories'), 'category', 'index', array('project_id' => $project['id'])) ?> </li> <?php if ($project['is_private'] == 0): ?> - <li <?= $this->app->getRouterController() === 'ProjectPermission' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('ProjectPermission') ?>> <?= $this->url->link(t('Permissions'), 'ProjectPermission', 'index', array('project_id' => $project['id'])) ?> </li> <?php endif ?> - <li <?= $this->app->getRouterController() === 'action' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('action') ?>> <?= $this->url->link(t('Automatic actions'), 'action', 'index', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'duplicate' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'duplicate') ?>> <?= $this->url->link(t('Duplicate'), 'project', 'duplicate', array('project_id' => $project['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'project' && ($this->app->getRouterAction() === 'disable' || $this->app->getRouterAction() === 'enable') ? 'class="active"' : '' ?>> <?php if ($project['is_active']): ?> + <li <?= $this->app->checkMenuSelection('project', 'disable') ?>> <?= $this->url->link(t('Disable'), 'project', 'disable', array('project_id' => $project['id']), true) ?> <?php else: ?> + <li <?= $this->app->checkMenuSelection('project', 'enable') ?>> <?= $this->url->link(t('Enable'), 'project', 'enable', array('project_id' => $project['id']), true) ?> <?php endif ?> </li> - <li <?= $this->app->getRouterController() === 'taskImport' && $this->app->getRouterAction() === 'step1' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskImport') ?>> <?= $this->url->link(t('Import'), 'taskImport', 'step1', array('project_id' => $project['id'])) ?> </li> <?php if ($this->user->hasProjectAccess('project', 'remove', $project['id'])): ?> - <li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('project', 'remove') ?>> <?= $this->url->link(t('Remove'), 'project', 'remove', array('project_id' => $project['id'])) ?> </li> <?php endif ?> diff --git a/app/Template/project_user/sidebar.php b/app/Template/project_user/sidebar.php index b81ba14a..27f1094c 100644 --- a/app/Template/project_user/sidebar.php +++ b/app/Template/project_user/sidebar.php @@ -12,16 +12,16 @@ <br/><br/> <ul> - <li <?= $this->app->getRouterAction() === 'managers' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('projectuser', 'managers') ?>> <?= $this->url->link(t('Project managers'), 'projectuser', 'managers', $filter) ?> </li> - <li <?= $this->app->getRouterAction() === 'members' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('projectuser', 'members') ?>> <?= $this->url->link(t('Project members'), 'projectuser', 'members', $filter) ?> </li> - <li <?= $this->app->getRouterAction() === 'opens' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('projectuser', 'opens') ?>> <?= $this->url->link(t('Open tasks'), 'projectuser', 'opens', $filter) ?> </li> - <li <?= $this->app->getRouterAction() === 'closed' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('projectuser', 'closed') ?>> <?= $this->url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?> </li> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index d994aad3..f522c1c4 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -1,20 +1,20 @@ <div class="sidebar"> <h2><?= t('Information') ?></h2> <ul> - <li <?= $this->app->getRouterController() === 'task' && $this->app->getRouterAction() === 'show' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('task', 'show') ?>> <?= $this->url->link(t('Summary'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'activity' && $this->app->getRouterAction() === 'task' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('activity', 'task') ?>> <?= $this->url->link(t('Activity stream'), 'activity', 'task', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'task' && $this->app->getRouterAction() === 'transitions' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('task', 'transitions') ?>> <?= $this->url->link(t('Transitions'), 'task', 'transitions', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'task' && $this->app->getRouterAction() === 'analytics' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('task', 'analytics') ?>> <?= $this->url->link(t('Analytics'), 'task', 'analytics', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> <?php if ($task['time_estimated'] > 0 || $task['time_spent'] > 0): ?> - <li <?= $this->app->getRouterController() === 'task' && $this->app->getRouterAction() === 'timetracking' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('task', 'timetracking') ?>> <?= $this->url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> <?php endif ?> @@ -24,40 +24,40 @@ <?php if ($this->user->hasProjectAccess('taskmodification', 'edit', $task['project_id'])): ?> <h2><?= t('Actions') ?></h2> <ul> - <li <?= $this->app->getRouterController() === 'taskmodification' && $this->app->getRouterAction() === 'edit' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskmodification', 'edit') ?>> <?= $this->url->link(t('Edit the task'), 'taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskmodification' && $this->app->getRouterAction() === 'description' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskmodification', 'description') ?>> <?= $this->url->link(t('Edit the description'), 'taskmodification', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskmodification' && $this->app->getRouterAction() === 'recurrence' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskmodification', 'recurrence') ?>> <?= $this->url->link(t('Edit recurrence'), 'taskmodification', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'subtask' && $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('subtask', 'create') ?>> <?= $this->url->link(t('Add a sub-task'), 'subtask', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'tasklink' && $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('tasklink', 'create') ?>> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'comment' && $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('comment', 'create') ?>> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'file' && $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('file', 'create') ?>> <?= $this->url->link(t('Attach a document'), 'file', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'file' && $this->app->getRouterAction() === 'screenshot' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('file', 'screenshot') ?>> <?= $this->url->link(t('Add a screenshot'), 'file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskduplication' && $this->app->getRouterAction() === 'duplicate' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskduplication', 'duplicate') ?>> <?= $this->url->link(t('Duplicate'), 'taskduplication', 'duplicate', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskduplication' && $this->app->getRouterAction() === 'copy' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskduplication', 'copy') ?>> <?= $this->url->link(t('Duplicate to another project'), 'taskduplication', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskduplication' && $this->app->getRouterAction() === 'move' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskduplication', 'move') ?>> <?= $this->url->link(t('Move to another project'), 'taskduplication', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'taskstatus' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('taskstatus') ?>> <?php if ($task['is_active'] == 1): ?> <?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> <?php else: ?> @@ -65,7 +65,7 @@ <?php endif ?> </li> <?php if ($this->task->canRemove($task)): ?> - <li <?= $this->app->getRouterController() === 'task' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('task', 'remove') ?>> <?= $this->url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> <?php endif ?> diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php index 325ca1c8..c9f367a0 100644 --- a/app/Template/task_creation/form.php +++ b/app/Template/task_creation/form.php @@ -22,7 +22,16 @@ <div class="form-tabs"> <div class="write-area"> - <?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?> + <?= $this->form->textarea( + 'description', + $values, + $errors, + array( + 'placeholder="'.t('Leave a description').'"', + 'tabindex="2"', + 'data-mention-search-url="'.$this->url->href('UserHelper', 'mention', array('project_id' => $values['project_id'])).'"' + ) + ) ?> </div> <div class="preview-area"> <div class="markdown"></div> diff --git a/app/Template/task_modification/edit_description.php b/app/Template/task_modification/edit_description.php index 4cae939c..c38e885d 100644 --- a/app/Template/task_modification/edit_description.php +++ b/app/Template/task_modification/edit_description.php @@ -17,7 +17,17 @@ </li> </ul> <div class="write-area"> - <?= $this->form->textarea('description', $values, $errors, array('autofocus', 'placeholder="'.t('Leave a description').'"'), 'task-show-description-textarea') ?> + <?= $this->form->textarea( + 'description', + $values, + $errors, + array( + 'autofocus', + 'placeholder="'.t('Leave a description').'"', + 'data-mention-search-url="'.$this->url->href('UserHelper', 'mention', array('project_id' => $task['project_id'])).'"' + ), + 'task-show-description-textarea' + ) ?> </div> <div class="preview-area"> <div class="markdown"></div> diff --git a/app/Template/task_modification/edit_task.php b/app/Template/task_modification/edit_task.php index f4d7449a..6fdb77b5 100644 --- a/app/Template/task_modification/edit_task.php +++ b/app/Template/task_modification/edit_task.php @@ -13,7 +13,16 @@ <?= $this->form->label(t('Description'), 'description') ?> <div class="form-tabs"> <div class="write-area"> - <?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?> + <?= $this->form->textarea( + 'description', + $values, + $errors, + array( + 'placeholder="'.t('Leave a description').'"', + 'tabindex="2"', + 'data-mention-search-url="'.$this->url->href('UserHelper', 'mention', array('project_id' => $task['project_id'])).'"' + ) + ) ?> </div> <div class="preview-area"> <div class="markdown"></div> diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index 011994b9..7756126e 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -1,7 +1,7 @@ <div class="sidebar"> <h2><?= t('Information') ?></h2> <ul> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'show' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'show') ?>> <?= $this->url->link(t('Summary'), 'user', 'show', array('user_id' => $user['id'])) ?> </li> <?php if ($this->user->isAdmin()): ?> @@ -10,13 +10,13 @@ </li> <?php endif ?> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'timesheet' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'timesheet') ?>> <?= $this->url->link(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'last' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'last') ?>> <?= $this->url->link(t('Last logins'), 'user', 'last', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'sessions' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'sessions') ?>> <?= $this->url->link(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?> </li> <?php endif ?> @@ -27,42 +27,42 @@ <h2><?= t('Actions') ?></h2> <ul> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'edit' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'edit') ?>> <?= $this->url->link(t('Edit profile'), 'user', 'edit', array('user_id' => $user['id'])) ?> </li> <?php if ($user['is_ldap_user'] == 0): ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'password' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'password') ?>> <?= $this->url->link(t('Change password'), 'user', 'password', array('user_id' => $user['id'])) ?> </li> <?php endif ?> <?php if ($this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->getRouterController() === 'twofactor' && $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('twofactor', 'index') ?>> <?= $this->url->link(t('Two factor authentication'), 'twofactor', 'index', array('user_id' => $user['id'])) ?> </li> <?php elseif ($this->user->hasAccess('twofactor', 'disable') && $user['twofactor_activated'] == 1): ?> - <li <?= $this->app->getRouterController() === 'twofactor' && $this->app->getRouterAction() === 'disable' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('twofactor', 'disable') ?>> <?= $this->url->link(t('Two factor authentication'), 'twofactor', 'disable', array('user_id' => $user['id'])) ?> </li> <?php endif ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'share' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'share') ?>> <?= $this->url->link(t('Public access'), 'user', 'share', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'notifications') ?>> <?= $this->url->link(t('Notifications'), 'user', 'notifications', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'external' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'external') ?>> <?= $this->url->link(t('External accounts'), 'user', 'external', array('user_id' => $user['id'])) ?> </li> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'integrations' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'integrations') ?>> <?= $this->url->link(t('Integrations'), 'user', 'integrations', array('user_id' => $user['id'])) ?> </li> <?php endif ?> <?php if ($this->user->hasAccess('user', 'authentication')): ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'authentication' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'authentication') ?>> <?= $this->url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?> </li> <?php endif ?> @@ -70,7 +70,7 @@ <?= $this->hook->render('template:user:sidebar:actions', array('user' => $user)) ?> <?php if ($this->user->hasAccess('user', 'remove') && ! $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>> + <li <?= $this->app->checkMenuSelection('user', 'remove') ?>> <?= $this->url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?> </li> <?php endif ?> diff --git a/assets/css/app.css b/assets/css/app.css index 6a05dda3..ae4d85a1 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -18,4 +18,4 @@ * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} -.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}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:20px;color:#333;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}.page{clear:both}ul.no-bullet li{list-style-type:none;margin-left:0}.pull-right{text-align:right}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)}.chosen-select{min-height:27px}.avatar{float:left;margin-right:10px}#ui-datepicker-div{font-size:.8em}#app-loading-icon{position:fixed;right:3px;bottom:3px}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h2{font-size:1.3em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}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"]{color:#888;border:1px solid #ccc;width:300px;max-width:95%;font-size:100%;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;appearance:none}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus,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)}input.form-numeric,input[type="number"]{width:70px}textarea{border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-size:100%;font-family:sans-serif}select{max-width:95%}select:focus{outline:0}::-webkit-input-placeholder{color:#ddd;padding-top:2px}::-ms-input-placeholder{color:#ddd;padding-top:2px}::-moz-placeholder{color:#ddd;padding-top:2px}.form-actions{padding-top:20px;clear:both}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-required{color:red;padding-left:5px;font-weight:bold}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:0}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0;margin-right:15px}.form-inline .form-required{display:none}.form-inline-group{display:inline}input.form-datetime,input.form-date{width:150px}input.form-input-large{width:400px}.form-row{margin-top:10px;margin-bottom:20px}.form-column{float:left;margin-right:3%;max-width:47%}.form-column ul{margin-top:15px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}label+.form-tabs{margin-top:10px}.form-tabs{width:100%;max-width:800px}ul.form-tabs-nav{margin-bottom:8px;margin-top:0}.form-tabs-nav li{margin-left:0;display:inline}.form-tab{margin-right:20px}.form-tab a{color:#ccc;font-weight:bold;text-decoration:none}.form-tab a:focus,.form-tab a:hover{color:#000}.form-tab-selected a{color:#333}.preview-area{border:1px dashed #000;padding-top:5px;padding-left:5px;padding-right:5px;margin-bottom:5px;display:none;overflow:auto}.btn{-webkit-appearance:none;appearance:none;display:inline-block;color:#333;border:1px solid #ccc;background:#efefef;padding:5px;padding-left:15px;padding-right:15px;font-size:.9em;cursor:pointer;border-radius:2px}a.btn{text-decoration:none;font-weight:bold}.btn-small{padding:2px;padding-left:5px;padding-right:5px}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}a.btn-red:hover,.btn-red:hover,.btn-red:focus{color:#fff;background:#c53727}a.btn-blue,.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}a.btn-blue:hover,.btn-blue:hover,a.btn-blue:focus,.btn-blue:focus{border-color:#2f5bb7;background:#357ae8}.btn-blue:disabled{color:#ccc;border:1px solid #ccc;background:#f7f7f7}#main .alert,.page .alert{margin-top:10px}.alert{padding:8px 35px 8px 14px;margin-bottom:10px;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}.tooltip-arrow:after{background:#fff;border:1px solid #aaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px;font-size:.85em}.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:550px}.ui-tooltip-content .markdown p{margin-bottom:0}.tooltip .fa-info-circle{color:#999;font-size:.95em}.ui-tooltip ul{margin-left:20px}header{margin-top:10px;padding-bottom:10px;border-bottom:1px solid #dedede}header h1{margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%;float:left}header ul{text-align:right;font-size:.9em}header li{display:inline;padding-left:30px}header a{color:#777;text-decoration:none}nav .active a{color:#333;font-weight:bold}.username a{color:#000}.username a:hover{color:#df5353;text-decoration:underline}.logo{opacity:.3;color:#d40000}.logo span{color:#333}.logo:hover{opacity:.8}.logo:focus span,.logo:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header h2{margin:0;padding:0;font-size:1.4em;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#ddd}.page-header h2 a:focus,.page-header h2 a:hover{color:#333}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:10px;font-size:.95em}.menu-inline{margin-bottom:5px}@media only screen and (max-width:640px){.page-header-mobile li{display:block;margin-bottom:5px}}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed}#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{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;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}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;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;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.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:#444}.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;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fefefe;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:20px}.activity-datetime{color:#999;font-size:.85em}.activity-content{margin-top:10px;margin-left:20px;padding-left:20px;border-left:2px solid #666}.activity-title{font-weight:bold;color:#000}.activity-description{font-size:.9em;color:#aaa;padding-top:5px}.activity-description ul{margin-top:10px}.activity-description li{margin-left:40px;list-style-type:circle;color:#555}.activity-description .markdown{margin-top:10px;color:#555}.activity-changes{margin-top:10px;font-size:.85em}.activity-changes ul{margin-left:25px}.dashboard-project-stats span{font-size:.75em;margin-right:10px;color:#999}.dashboard-project-stats strong{font-size:1.2em}.dashboard-table-link{font-weight:bold;color:#444;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);overflow:auto;z-index:100}#popover-content{position:absolute;width:70%;margin:0 0 0 -35%;left:50%;top:1%;padding:15px;background:#fff;overflow:auto;max-height:85%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;position:relative;clear:both}.sidebar-content{margin-left:23%;width:76%;position:absolute}.sidebar{width:20%;float:left;padding:10px;padding-top:0;border:1px solid #ddd;background:#fdfdfd;border-radius:5px}.sidebar li{list-style-type:square;margin-left:30px;line-height:1.8em}.sidebar li.active a{color:#000;font-weight:bold;text-decoration:none}.sidebar li.active a:focus,.sidebar li.active a:hover{text-decoration:underline}.sidebar-collapsed .sidebar{width:10px;padding-bottom:0;float:none}.sidebar-collapsed .sidebar-content{margin:0;margin-top:15px;width:100%}.sidebar-collapse{text-align:right}.sidebar-collapse a,.sidebar-expand a{color:#333;text-decoration:none}.sidebar-collapse a:hover,.sidebar-expand a:hover{color:#df5353}@media only screen and (max-width:1024px){.sidebar{width:25%}.sidebar-content{margin-left:30%;width:70%}}@media only screen and (max-width:767px){.sidebar{width:95%;float:none}.sidebar-content{margin:0;margin-top:15px;width:100%}}@media only screen and (max-width:1080px){div.filter-dropdowns .filters{margin-left:0}div.filter-dropdowns{display:block;margin-top:5px}}@media only screen and (max-width:1024px){li.hide-tablet,.hide-tablet{display:none}body{font-size:.85em}.form-tab{max-width:404px}.form-inline-group input[type="submit"],.form-inline-group label{display:block}.form-inline-group input[type="submit"]{margin-top:20px}td>input[type="text"]{max-width:150px}.task-time-form label{display:block}.task-time-form input[type="submit"]{margin-top:10px;display:block}.page-header .form-input-large{width:300px}}@media only screen and (max-width:1024px) and (orientation:landscape){header{padding-bottom:4px}div.chosen-container{font-size:.9em}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{height:18px}.page-header .form-input-large{width:300px}}@media only screen and (max-width:640px){.hide-mobile{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:0;padding-left:10px;padding-right:10px;padding-top:8px;padding-bottom:8px;font-size:.85em;border-bottom:1px solid #f8f8f8;cursor:pointer}.dropdown-submenu-open li:last-child{border:0}.dropdown-submenu-open li: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}.page-header .dropdown{padding-right:10px}#screenshot-zone{position:relative;border:2px dashed #ccc;width:90%;height:250px;overflow:auto}#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center}#screenshot-zone.screenshot-pasted{border:2px solid #333}.toolbar{font-size:.9em;padding-top:5px}.views{display:inline-block;margin-right:10px;font-size:.9em}.views li{border:1px solid #eee;padding-left:8px;padding-right:8px;padding-top:5px;padding-bottom:5px;display:inline}.menu-inline li.active a,.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-right:0;border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-left:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.filters{display:inline-block;border:1px solid #eee;border-radius:5px;padding:5px;padding-right:10px;margin-left:8px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{font-size:.9em;display:inline-block}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:0}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:#777}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#666}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;font-size:.9em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#666;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:#666}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 silver;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:.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}
\ No newline at end of file +.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}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}.page{clear:both}ul.no-bullet li{list-style-type:none;margin-left:0}.pull-right{text-align:right}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)}.chosen-select{min-height:27px}.avatar{float:left;margin-right:10px}#ui-datepicker-div{font-size:.8em}#app-loading-icon{position:fixed;right:3px;bottom:3px}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h2{font-size:1.3em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}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"]{color:#888;border:1px solid #ccc;width:300px;max-width:95%;font-size:100%;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;appearance:none}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus,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)}input.form-numeric,input[type="number"]{width:70px}textarea{border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-size:100%;font-family:sans-serif}select{max-width:95%}select:focus{outline:0}::-webkit-input-placeholder{color:#ddd;padding-top:2px}::-ms-input-placeholder{color:#ddd;padding-top:2px}::-moz-placeholder{color:#ddd;padding-top:2px}.form-actions{padding-top:20px;clear:both}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-required{color:red;padding-left:5px;font-weight:bold}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:0}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0;margin-right:15px}.form-inline .form-required{display:none}.form-inline-group{display:inline}input.form-datetime,input.form-date{width:150px}input.form-input-large{width:400px}.form-row{margin-top:10px;margin-bottom:20px}.form-column{float:left;margin-right:3%;max-width:47%}.form-column ul{margin-top:15px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}label+.form-tabs{margin-top:10px}.form-tabs{width:100%;max-width:800px}ul.form-tabs-nav{margin-bottom:8px;margin-top:0}.form-tabs-nav li{margin-left:0;display:inline}.form-tab{margin-right:20px}.form-tab a{color:#ccc;font-weight:bold;text-decoration:none}.form-tab a:focus,.form-tab a:hover{color:#000}.form-tab-selected a{color:#333}.preview-area{border:1px dashed #000;padding-top:5px;padding-left:5px;padding-right:5px;margin-bottom:5px;display:none;overflow:auto}.btn{-webkit-appearance:none;appearance:none;display:inline-block;color:#333;border:1px solid #ccc;background:#efefef;padding:5px;padding-left:15px;padding-right:15px;font-size:.9em;cursor:pointer;border-radius:2px}a.btn{text-decoration:none;font-weight:bold}.btn-small{padding:2px;padding-left:5px;padding-right:5px}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}a.btn-red:hover,.btn-red:hover,.btn-red:focus{color:#fff;background:#c53727}a.btn-blue,.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}a.btn-blue:hover,.btn-blue:hover,a.btn-blue:focus,.btn-blue:focus{border-color:#2f5bb7;background:#357ae8}.btn-blue:disabled{color:#ccc;border:1px solid #ccc;background:#f7f7f7}#main .alert,.page .alert{margin-top:10px}.alert{padding:8px 35px 8px 14px;margin-bottom:10px;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}.tooltip-arrow:after{background:#fff;border:1px solid #aaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px;font-size:.85em}.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:550px}.ui-tooltip-content .markdown p{margin-bottom:0}.tooltip .fa-info-circle{color:#999;font-size:.95em}.ui-tooltip ul{margin-left:20px}header{margin-top:10px;padding-bottom:10px;border-bottom:1px solid #dedede}header h1{margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%;float:left}header ul{text-align:right;font-size:.9em}header li{display:inline;padding-left:30px}header a{color:#777;text-decoration:none}nav .active a{color:#333;font-weight:bold}.username a{color:#000}.username a:hover{color:#df5353;text-decoration:underline}.logo{opacity:.3;color:#d40000}.logo span{color:#333}.logo:hover{opacity:.8}.logo:focus span,.logo:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header h2{margin:0;padding:0;font-size:1.4em;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#ddd}.page-header h2 a:focus,.page-header h2 a:hover{color:#333}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:10px;font-size:.95em}.menu-inline{margin-bottom:5px}@media only screen and (max-width:640px){.page-header-mobile li{display:block;margin-bottom:5px}}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#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{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;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}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;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;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.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:#444}.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;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fefefe;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:20px}.activity-datetime{color:#999;font-size:.85em}.activity-content{margin-top:10px;margin-left:20px;padding-left:20px;border-left:2px solid #666}.activity-title{font-weight:bold;color:#000}.activity-description{font-size:.9em;color:#aaa;padding-top:5px}.activity-description ul{margin-top:10px}.activity-description li{margin-left:40px;list-style-type:circle;color:#555}.activity-description .markdown{margin-top:10px;color:#555}.activity-changes{margin-top:10px;font-size:.85em}.activity-changes ul{margin-left:25px}.dashboard-project-stats span{font-size:.75em;margin-right:10px;color:#999}.dashboard-project-stats strong{font-size:1.2em}.dashboard-table-link{font-weight:bold;color:#444;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);overflow:auto;z-index:100}#popover-content{position:absolute;width:70%;margin:0 0 0 -35%;left:50%;top:1%;padding:15px;background:#fff;overflow:auto;max-height:85%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;position:relative;clear:both}.sidebar-content{margin-left:23%;width:76%;position:absolute}.sidebar{width:20%;float:left;padding:10px;padding-top:0;border:1px solid #ddd;background:#fdfdfd;border-radius:5px}.sidebar li{list-style-type:square;margin-left:30px;line-height:1.8em}.sidebar li.active a{color:#000;font-weight:bold;text-decoration:none}.sidebar li.active a:focus,.sidebar li.active a:hover{text-decoration:underline}.sidebar-collapsed .sidebar{width:10px;padding-bottom:0;float:none}.sidebar-collapsed .sidebar-content{margin:0;margin-top:15px;width:100%}.sidebar-collapse{text-align:right}.sidebar-collapse a,.sidebar-expand a{color:#333;text-decoration:none}.sidebar-collapse a:hover,.sidebar-expand a:hover{color:#df5353}@media only screen and (max-width:1024px){.sidebar{width:25%}.sidebar-content{margin-left:30%;width:70%}}@media only screen and (max-width:767px){.sidebar{width:95%;float:none}.sidebar-content{margin:0;margin-top:15px;width:100%}}@media only screen and (max-width:1080px){div.filter-dropdowns .filters{margin-left:0}div.filter-dropdowns{display:block;margin-top:5px}}@media only screen and (max-width:1024px){li.hide-tablet,.hide-tablet{display:none}body{font-size:.85em}.form-tab{max-width:404px}.form-inline-group input[type="submit"],.form-inline-group label{display:block}.form-inline-group input[type="submit"]{margin-top:20px}td>input[type="text"]{max-width:150px}.task-time-form label{display:block}.task-time-form input[type="submit"]{margin-top:10px;display:block}.page-header .form-input-large{width:300px}}@media only screen and (max-width:1024px) and (orientation:landscape){header{padding-bottom:4px}div.chosen-container{font-size:.9em}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{height:18px}.page-header .form-input-large{width:300px}}@media only screen and (max-width:640px){.hide-mobile{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)}.textarea-dropdown li,.dropdown-submenu-open li{display:block;margin:0;padding:0;padding-left:10px;padding-right:10px;padding-top:8px;padding-bottom:8px;font-size:.85em;border-bottom:1px solid #f8f8f8;cursor:pointer}.textarea-dropdown li:last-child,.dropdown-submenu-open li:last-child{border:0}.textarea-dropdown .active,.textarea-dropdown li:hover,.dropdown-submenu-open li:hover{background:#4078c0;color:#fff}.textarea-dropdown .active a,.textarea-dropdown li:hover a,.dropdown-submenu-open li:hover a{color:#fff}.textarea-dropdown a,.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.page-header .dropdown{padding-right:10px}.textarea-dropdown{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)}#screenshot-zone{position:relative;border:2px dashed #ccc;width:90%;height:250px;overflow:auto}#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center}#screenshot-zone.screenshot-pasted{border:2px solid #333}.toolbar{font-size:.9em;padding-top:5px}.views{display:inline-block;margin-right:10px;font-size:.9em}.views li{border:1px solid #eee;padding-left:8px;padding-right:8px;padding-top:5px;padding-bottom:5px;display:inline}.menu-inline li.active a,.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-right:0;border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-left:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.filters{display:inline-block;border:1px solid #eee;border-radius:5px;padding:5px;padding-right:10px;margin-left:8px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{font-size:.9em;display:inline-block}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:0}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:#777}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#666}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;font-size:.9em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#666;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:#666}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 silver;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:.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}
\ No newline at end of file diff --git a/assets/css/print.css b/assets/css/print.css index 14b76511..57f05772 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -18,4 +18,4 @@ * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} -.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}header,.sidebar,.form-comment,.page-header{display:none}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed}#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{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;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}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;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;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.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:#444}.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;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}
\ No newline at end of file +.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}header,.sidebar,.form-comment,.page-header{display:none}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#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{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;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}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;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;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.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:#444}.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;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}
\ No newline at end of file diff --git a/assets/css/src/base.css b/assets/css/src/base.css index 8b15ac16..8c5ba097 100644 --- a/assets/css/src/base.css +++ b/assets/css/src/base.css @@ -17,7 +17,7 @@ body { body { margin-left: 10px; margin-right: 10px; - padding-bottom: 20px; + padding-bottom: 10px; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-rendering: optimizeLegibility; diff --git a/assets/css/src/board.css b/assets/css/src/board.css index c59ccdf0..cbe67c73 100644 --- a/assets/css/src/board.css +++ b/assets/css/src/board.css @@ -16,6 +16,7 @@ #board { table-layout: fixed; + margin-bottom: 0; } #board th.board-column-header { diff --git a/assets/css/src/dropdown.css b/assets/css/src/dropdown.css index 7839dbcd..d123dad1 100644 --- a/assets/css/src/dropdown.css +++ b/assets/css/src/dropdown.css @@ -21,6 +21,7 @@ ul.dropdown-submenu-open { box-shadow: 0px 1px 3px rgba(0,0,0,0.15); } +.textarea-dropdown li, .dropdown-submenu-open li { display: block; margin: 0; @@ -34,19 +35,25 @@ ul.dropdown-submenu-open { cursor: pointer; } +.textarea-dropdown li:last-child, .dropdown-submenu-open li:last-child { border: none; } +.textarea-dropdown .active, +.textarea-dropdown li:hover, .dropdown-submenu-open li:hover { background: #4078C0; color: #fff; } +.textarea-dropdown .active a, +.textarea-dropdown li:hover a, .dropdown-submenu-open li:hover a { color: #fff; } +.textarea-dropdown a, .dropdown-submenu-open a { text-decoration: none; color: #333; @@ -59,3 +66,14 @@ ul.dropdown-submenu-open { .page-header .dropdown { padding-right: 10px; } + +/* textarea dropdown */ +.textarea-dropdown { + list-style: none; + margin: 3px 0 0 1px; + padding: 6px 0; + background-color: #fff; + border: 1px solid #b2b2b2; + border-radius: 3px; + box-shadow: 0px 1px 3px rgba(0,0,0,0.15); +} diff --git a/assets/js/app.js b/assets/js/app.js index 35f77950..c80dc8e6 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -51,4 +51,1231 @@ c,a,e),l[d.key][c?"unshift":"push"]({callback:b,modifiers:d.modifiers,action:d.a unbind:function(a,b){return m.bind(a,function(){},b)},trigger:function(a,b){if(q[a+":"+b])q[a+":"+b]({},a);return this},reset:function(){l={};q={};return this},stopCallback:function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:function(a,b,d){var c=C(a,b,d),e;b={};var f=0,g=!1;for(e=0;e<c.length;++e)c[e].seq&&(f=Math.max(f,c[e].level));for(e=0;e<c.length;++e)c[e].seq?c[e].level==f&&(g=!0, b[c[e].seq]=1,x(c[e].callback,d,c[e].combo,c[e].seq)):g||x(c[e].callback,d,c[e].combo);c="keypress"==d.type&&I;d.type!=u||w(a)||c||t(b);I=g&&"keydown"==d.type}};J.Mousetrap=m;"function"===typeof define&&define.amd&&define(m)})(window,document); Mousetrap=function(a){var d={},e=a.stopCallback;a.stopCallback=function(b,c,a){return d[a]?!1:e(b,c,a)};a.bindGlobal=function(b,c,e){a.bind(b,c,e);if(b instanceof Array)for(c=0;c<b.length;c++)d[b[c]]=!0;else d[b]=!0};return a}(Mousetrap); -!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(x){this.app=x;this.router=new v();this.router.addRoute("screenshot-zone",e)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(y){var x=this;x.app.dropdown.close();$.get(y,function(z){$("body").append('<div id="popover-container"><div id="popover-content">'+z+"</div></div>");x.app.refresh();x.router.dispatch(this.app);x.afterOpen()})};t.prototype.close=function(x){if(this.isOpen()){if(x){x.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(y){y.preventDefault();y.stopPropagation();var x=y.target.getAttribute("href");if(!x){x=y.target.getAttribute("data-href")}if(x){this.open(x)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(x){x.stopPropagation()})};t.prototype.afterOpen=function(){var x=this;var y=$("#task-form");if(y){y.on("submit",function(z){z.preventDefault();$.ajax({type:"POST",url:y.attr("action"),data:y.serialize(),success:function(B,C,A){if(A.getResponseHeader("X-Ajax-Redirect")){window.location=A.getResponseHeader("X-Ajax-Redirect")}else{$("#popover-content").html(B);x.afterOpen()}}})})}};function r(){}r.prototype.listen=function(){var x=this;$(document).on("click",function(){x.close()});$(document).on("click",".dropdown-menu",function(B){B.preventDefault();B.stopImmediatePropagation();x.close();var z=$(this).next("ul");var A=240;var C=$(this).offset();var y=$(this).height();$("body").append(jQuery("<div>",{id:"dropdown"}));z.clone().appendTo("#dropdown");var D=$("#dropdown ul");D.css("left",C.left);if(C.top+A-$(window).scrollTop()>$(window).height()){D.css("top",C.top-A-y)}else{D.css("top",C.top+y)}D.addClass("dropdown-submenu-open")});$(document).on("click",".dropdown-submenu-open li",function(y){if($(y.target).is("li")){$(this).find("a:visible")[0].click()}})};r.prototype.close=function(){$("#dropdown").remove()};function q(x){this.app=x}q.prototype.listen=function(){var x=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(y,z){$(this).css(y);var A=z.target.left+z.target.width/2-z.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(z.vertical).addClass(A<1?"align-left":"align-right").appendTo(this)}},content:function(){var A=this;var y=$(this).attr("data-href");if(!y){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(y,function z(D){var C=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(D);C.css({top:"",left:""});C.children(".tooltip-arrow").remove();var B=$(A).tooltip("option","position");B.of=$(A);C.position(B);$("#tooltip-subtasks a").not(".popover").click(function(E){E.preventDefault();E.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){x.app.popover.open($(this).attr("href"));$(A).tooltip("close")}else{$.get($(this).attr("href"),z)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var y=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(y).tooltip("close")})}).on("mouseleave focusout",function(y){y.stopImmediatePropagation();var z=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(z).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(B){B.preventDefault();var y=$(".write-area");var A=$(".preview-area");var x=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var z=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:x.val()})});z.done(function(C){A.find(".markdown").html(C);A.css("height",x.css("height"));A.css("width",x.css("width"));y.hide();A.show()})};l.prototype.showWriter=function(x){x.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(x){x.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(x){x.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(x){this.app=x;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var x=this;$(document).on("click",".filter-helper",function(A){A.preventDefault();var z=$(this).data("filter");var y=$(this).data("append-filter");if(y){z=$("#form-search").val()+" "+y}$("#form-search").val(z);if($("#board").length){x.app.board.reloadFilters(z)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("v b",function(z){var y=$(".view-board");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v c",function(z){var y=$(".view-calendar");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v l",function(z){var y=$(".view-listing");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v g",function(z){var y=$(".view-gantt");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("f",function(z){z.preventDefault();var y=document.getElementById("form-search");if(y){y.focus()}});Mousetrap.bind("r",function(z){z.preventDefault();var y=$(".filter-reset").data("filter");$("#form-search").val(y);if($("#board").length){x.app.board.reloadFilters(y)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var x=false;$("select.task-reload-project-destination").change(function(){if(!x){$(".loading-icon").show();x=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(x,y){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(x){x.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(y){y.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){x.popover.close();x.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var y=$(this);var z=y.data("dst-field");var x=y.data("dst-extra-field");if($("#form-"+z).val()==""){y.parent().find("input[type=submit]").attr("disabled","disabled")}y.autocomplete({source:y.data("search-url"),minLength:1,select:function(A,B){$("input[name="+z+"]").val(B.item.id);if(x){$("input[name="+x+"]").val(B.item[x])}y.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").chosen({width:"180px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$(".select-auto-redirect").change(function(){var x=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(x,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var x="";if(typeof document.hidden!=="undefined"){x="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){x="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){x="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){x="webkitVisibilityState"}}}}if(x!=""){return document[x]=="visible"}return true};m.prototype.formatDuration=function(x){if(x>=86400){return Math.round(x/86400)+"d"}else{if(x>=3600){return Math.round(x/3600)+"h"}else{if(x>=60){return Math.round(x/60)+"m"}}}return x+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};e.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(C){if(C.clipboardData&&C.clipboardData.items){var A=C.clipboardData.items;if(A){for(var B=0;B<A.length;B++){if(A[B].type.indexOf("image")!==-1){var z=A[B].getAsFile();var x=new FileReader();var y=this;x.onload=function(D){y.createImage(D.target.result)};x.readAsDataURL(z)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var x=this.pasteCatcher.childNodes[0];if(x){if(x.tagName==="IMG"){this.createImage(x.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(z){var y=new Image();y.src=z;y.onload=function(){var A=z.split("base64,");var B=A[1];$("input[name=screenshot]").val(B)};var x=document.getElementById("screenshot-zone");x.innerHTML="";x.className="screenshot-pasted";x.appendChild(y);this.destroy();this.initialize()};function j(){}j.prototype.execute=function(){var x=$("#calendar");x.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(y){$.ajax({cache:false,url:x.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:y.id,date_due:y.start.format()})})},viewRender:function(){var y=x.data("check-url");var A={start:x.fullCalendar("getView").start.format(),end:x.fullCalendar("getView").end.format()};for(var z in A){y+="&"+z+"="+A[z]}$.getJSON(y,function(B){x.fullCalendar("removeEvents");x.fullCalendar("addEventSource",B);x.fullCalendar("rerenderEvents")})}})};function k(x){this.app=x;this.checkInterval=null}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.columnScrolling();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var x=parseInt($("#board").attr("data-check-interval"));if(x>0){this.checkInterval=window.setInterval(this.check.bind(this),x*1000)}};k.prototype.reloadFilters=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(y){x.refresh(y)},304:function(){x.app.hideLoadingIcon()}}})}};k.prototype.save=function(z,A,x,y){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:z,column_id:A,swimlane_id:y,position:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.refresh=function(x){$("#board-container").replaceWith(x);this.app.refresh();this.app.swimlane.refresh();this.columnScrolling();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode()};k.prototype.dragAndDrop=function(){var x=this;var y={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(z,A){A.item.removeClass("draggable-item-selected");x.save(A.item.attr("data-task-id"),A.item.parent().attr("data-column-id"),A.item.index()+1,A.item.parent().attr("data-swimlane-id"))},start:function(z,A){A.item.addClass("draggable-item-selected");A.placeholder.height(A.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");y.handle=".task-board-sort-handle"}$(".board-task-list").sortable(y)};k.prototype.listen=function(){var x=this;$(document).on("click",".task-board",function(y){if(y.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(y){y.preventDefault();x.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(y){y.preventDefault();x.toggleColumnScrolling()});$(document).on("click",".board-column-title",function(){x.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var x=localStorage.getItem("column_scroll")||1;localStorage.setItem("column_scroll",x==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-task-list").each(function(){$(this).css("min-height",80);$(this).css("height","");$(".board-rotation-wrapper").css("min-height","")})}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var x=$(window).height()-145;$(".board-task-list").css("height",x);$(".board-rotation-wrapper").css("min-height",x)}}};k.prototype.toggleCompactView=function(){var x=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",x==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(y){$(".filter-display-mode").toggle();x.refresh(y)}})};k.prototype.restoreColumnViewMode=function(){var x=this;$(".board-column-header").each(function(){var y=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+y)){x.hideColumn(y)}})};k.prototype.toggleColumnViewMode=function(x){if(localStorage.getItem("hidden_column_"+x)){this.showColumn(x)}else{this.hideColumn(x)}};k.prototype.hideColumn=function(x){$(".board-column-"+x+" .board-column-expanded").hide();$(".board-column-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x+" .board-column-expanded").hide();$(".board-column-header-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+x).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+x+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+x+"").height())});localStorage.setItem("hidden_column_"+x,1)};k.prototype.showColumn=function(x){$(".board-column-"+x+" .board-column-expanded").show();$(".board-column-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x+" .board-column-expanded").show();$(".board-column-header-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x).removeClass("board-column-header-collapsed");$(".board-column-"+x).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+x).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+x)};k.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("c",function(){x.toggleCompactView()});Mousetrap.bind("s",function(){x.toggleCollapsedMode()});Mousetrap.bind("n",function(){x.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(y){var z=this.getAllCollapsed();var x=z.indexOf(y);if(x>-1){z.splice(x,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(z));$(".board-swimlane-columns-"+y).css("display","table-row");$(".board-swimlane-tasks-"+y).css("display","table-row");$(".hide-icon-swimlane-"+y).css("display","inline");$(".show-icon-swimlane-"+y).css("display","none")};g.prototype.collapse=function(x){var y=this.getAllCollapsed();if(y.indexOf(x)<0){y.push(x);localStorage.setItem(this.getStorageKey(),JSON.stringify(y))}$(".board-swimlane-columns-"+x+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+x).css("display","none");$(".hide-icon-swimlane-"+x).css("display","none");$(".show-icon-swimlane-"+x).css("display","inline")};g.prototype.isCollapsed=function(x){return this.getAllCollapsed().indexOf(x)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var y=this.getAllCollapsed();for(var x=0;x<y.length;x++){this.collapse(y[x])}};g.prototype.listen=function(){var x=this;$(document).on("click",".board-swimlane-toggle",function(z){z.preventDefault();var y=$(this).data("swimlane-id");if(x.isCollapsed(y)){x.expand(y)}else{x.collapse(y)}})};function c(x){this.app=x;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(x),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var A=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var z=this.getDateRange(A);var x=z[0];var C=z[1];var y=$(this.options.container);var B=jQuery("<div>",{"class":"ganttview"});B.append(this.renderVerticalHeader());B.append(this.renderSlider(x,C));y.append(B);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",y).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(x);this.listenForBlockMove(x)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var B=jQuery("<div>",{"class":"ganttview-vtheader"});var y=jQuery("<div>",{"class":"ganttview-vtheader-item"});var A=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var x=0;x<this.data.length;x++){var z=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[x])})).append(" ");if(this.data[x].type=="task"){z.append(jQuery("<a>",{href:this.data[x].link,target:"_blank",title:this.data[x].title}).append(this.data[x].title))}else{z.append(jQuery("<a>",{href:this.data[x].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].link,target:"_blank"}).append(this.data[x].title))}A.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(z))}y.append(A);B.append(y);return B};c.prototype.renderSlider=function(y,A){var x=jQuery("<div>",{"class":"ganttview-slide-container"});var z=this.getDates(y,A);x.append(this.renderHorizontalHeader(z));x.append(this.renderGrid(z));x.append(this.addBlockContainers());this.addBlocks(x,y);return x};c.prototype.renderHorizontalHeader=function(x){var E=jQuery("<div>",{"class":"ganttview-hzheader"});var C=jQuery("<div>",{"class":"ganttview-hzheader-months"});var B=jQuery("<div>",{"class":"ganttview-hzheader-days"});var A=0;for(var F in x){for(var z in x[F]){var G=x[F][z].length*this.options.cellWidth;A=A+G;C.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(G-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[z]+" "+F));for(var D in x[F][z]){B.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(x[F][z][D].getDate()))}}}C.css("width",A+"px");B.css("width",A+"px");E.append(C).append(B);return E};c.prototype.renderGrid=function(x){var G=jQuery("<div>",{"class":"ganttview-grid"});var B=jQuery("<div>",{"class":"ganttview-grid-row"});for(var E in x){for(var z in x[E]){for(var D in x[E][z]){var A=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(x[E][z][D])){A.addClass("ganttview-weekend")}B.append(A)}}}var F=jQuery("div.ganttview-grid-row-cell",B).length*this.options.cellWidth;B.css("width",F+"px");G.css("width",F+"px");for(var C=0;C<this.data.length;C++){G.append(B.clone())}return G};c.prototype.addBlockContainers=function(){var y=jQuery("<div>",{"class":"ganttview-blocks"});for(var x=0;x<this.data.length;x++){y.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return y};c.prototype.addBlocks=function(y,x){var F=jQuery("div.ganttview-blocks div.ganttview-block-container",y);var z=0;for(var C=0;C<this.data.length;C++){var D=this.data[C];var G=this.daysBetween(D.start,D.end)+1;var B=this.daysBetween(x,D.start);var E=jQuery("<div>",{"class":"ganttview-block-text"});var A=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(D),css:{width:((G*this.options.cellWidth)-9)+"px","margin-left":(B*this.options.cellWidth)+"px"}}).append(E);if(G>=2){E.append(D.progress)}A.data("record",D);this.setBarColor(A,D);if(D.progress!="0%"){A.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":D.color.border,width:D.progress,opacity:0.4}}))}jQuery(F[z]).append(A);z=z+1}};c.prototype.getVerticalHeaderTooltip=function(y){var D="";if(y.type=="task"){D="<strong>"+y.column_title+"</strong> ("+y.progress+")<br/>"+y.title}else{var A=["managers","members"];for(var z in A){var B=A[z];if(!jQuery.isEmptyObject(y.users[B])){var C=jQuery("<ul>");for(var x in y.users[B]){C.append(jQuery("<li>").append(y.users[B][x]))}D+="<p><strong>"+$(this.options.container).data("label-"+B)+"</strong></p>"+C[0].outerHTML}}}return D};c.prototype.getBarTooltip=function(x){var y="";if(x.not_defined){y=$(this.options.container).data("label-not-defined")}else{if(x.type=="task"){y="<strong>"+x.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(x.assignee?x.assignee:"")+"<br/>"}y+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.start)+"<br/>";y+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.end)}return y};c.prototype.setBarColor=function(y,x){if(x.not_defined){y.addClass("ganttview-block-not-defined")}else{y.css("background-color",x.color.background);y.css("border-color",x.color.border)}};c.prototype.listenForBlockResize=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.listenForBlockMove=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.updateDataAndPosition=function(C,A){var x=jQuery("div.ganttview-slide-container",this.options.container);var G=x.scrollLeft();var D=C.offset().left-x.offset().left-1+G;var F=C.data("record");F.not_defined=false;this.setBarColor(C,F);var z=Math.round(D/this.options.cellWidth);var E=this.addDays(this.cloneDate(A),z);F.start=E;var y=C.outerWidth();var B=Math.round(y/this.options.cellWidth)-1;F.end=this.addDays(this.cloneDate(E),B);if(F.type==="task"&&B>0){jQuery("div.ganttview-block-text",C).text(F.progress)}C.attr("title",this.getBarTooltip(F));C.data("record",F);C.css("top","").css("left","").css("position","relative").css("margin-left",D+"px")};c.prototype.getDates=function(B,x){var A=[];A[B.getFullYear()]=[];A[B.getFullYear()][B.getMonth()]=[B];var z=B;while(this.compareDate(z,x)==-1){var y=this.addDays(this.cloneDate(z),1);if(!A[y.getFullYear()]){A[y.getFullYear()]=[]}if(!A[y.getFullYear()][y.getMonth()]){A[y.getFullYear()][y.getMonth()]=[]}A[y.getFullYear()][y.getMonth()].push(y);z=y}return A};c.prototype.prepareData=function(z){for(var y=0;y<z.length;y++){var A=new Date(z[y].start[0],z[y].start[1]-1,z[y].start[2],0,0,0,0);z[y].start=A;var x=new Date(z[y].end[0],z[y].end[1]-1,z[y].end[2],0,0,0,0);z[y].end=x}return z};c.prototype.getDateRange=function(z){var C=new Date();var y=new Date();for(var A=0;A<this.data.length;A++){var B=new Date();B.setTime(Date.parse(this.data[A].start));var x=new Date();x.setTime(Date.parse(this.data[A].end));if(A==0){C=B;y=x}if(this.compareDate(C,B)==1){C=B}if(this.compareDate(y,x)==-1){y=x}}if(this.daysBetween(C,y)<z){y=this.addDays(this.cloneDate(C),z)}C.setDate(C.getDate()-1);return[C,y]};c.prototype.daysBetween=function(A,x){if(!A||!x){return 0}var z=0,y=this.cloneDate(A);while(this.compareDate(y,x)==-1){z=z+1;this.addDays(y,1)}return z};c.prototype.isWeekend=function(x){return x.getDay()%6==0};c.prototype.cloneDate=function(x){return new Date(x.getTime())};c.prototype.addDays=function(x,y){x.setDate(x.getDate()+y*1);return x};c.prototype.compareDate=function(y,x){if(isNaN(y)||isNaN(x)){throw new Error(y+" - "+x)}else{if(y instanceof Date&&x instanceof Date){return(y<x)?-1:(y>x)?1:0}else{throw new TypeError(y+" - "+x)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})};function s(){}s.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].column_title,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function p(){}p.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].user,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function d(){}d.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[];var x=[];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]]);if(z>0){x.push(D[B][z])}}else{C[z].push(D[B][z]);if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C,type:"area-spline",groups:[x]},axis:{x:{type:"category",categories:y}}})};function o(){}o.prototype.execute=function(){var C=$("#chart").data("metrics");var B=[[$("#chart").data("label-total")]];var x=[];var z=d3.time.format("%Y-%m-%d");var D=d3.time.format($("#chart").data("date-format"));for(var A=0;A<C.length;A++){for(var y=0;y<C[A].length;y++){if(A==0){B.push([C[A][y]])}else{B[y+1].push(C[A][y]);if(y>0){if(B[0][A]==undefined){B[0].push(0)}B[0][A]+=C[A][y]}if(y==0){x.push(D(z.parse(C[A][y])))}}}}c3.generate({data:{columns:B},axis:{x:{type:"category",categories:x}}})};function h(x){this.app=x}h.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y in z){A.push(z[y].average);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function w(x){this.app=x}w.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y=0;y<z.length;y++){A.push(z[y].time_spent);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(x){this.app=x}u.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[$("#chart").data("label-cycle")];var z=[$("#chart").data("label-lead")];var y=[];var B={};B[$("#chart").data("label-cycle")]="area";B[$("#chart").data("label-lead")]="area-spline";var x={};x[$("#chart").data("label-lead")]="#afb42b";x[$("#chart").data("label-cycle")]="#4e342e";for(var A=0;A<D.length;A++){C.push(parseInt(D[A].avg_cycle_time));z.push(parseInt(D[A].avg_lead_time));y.push(D[A].day)}c3.generate({data:{columns:[z,C],types:B,colors:x},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}}})};function i(x){this.app=x}i.prototype.execute=function(){var A=$("#chart").data("metrics");var B=[$("#chart").data("label-spent")];var z=[$("#chart").data("label-estimated")];var y=[];for(var x in A){B.push(parseInt(A[x].time_spent));z.push(parseInt(A[x].time_estimated));y.push(x)}c3.generate({data:{columns:[B,z],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:y}},legend:{show:true}})};function v(){this.routes={}}v.prototype.addRoute=function(y,x){this.routes[y]=x};v.prototype.dispatch=function(y){for(var z in this.routes){if(document.getElementById(z)){var x=Object.create(this.routes[z].prototype);this.routes[z].apply(x,[y]);x.execute();break}}};jQuery(document).ready(function(){var y=new m();var x=new v();x.addRoute("board",k);x.addRoute("calendar",j);x.addRoute("screenshot-zone",e);x.addRoute("analytic-task-repartition",s);x.addRoute("analytic-user-repartition",p);x.addRoute("analytic-cfd",d);x.addRoute("analytic-burndown",o);x.addRoute("analytic-avg-time-column",h);x.addRoute("analytic-task-time-column",w);x.addRoute("analytic-lead-cycle-time",u);x.addRoute("analytic-compare-hours",i);x.addRoute("gantt-chart",c);x.dispatch(y);y.listen()})})();
\ No newline at end of file +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === "object" && module.exports) { + var $ = require('jquery'); + module.exports = factory($); + } else { + // Browser globals + factory(jQuery); + } +}(function (jQuery) { + +/*! + * jQuery.textcomplete + * + * Repository: https://github.com/yuku-t/jquery-textcomplete + * License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE) + * Author: Yuku Takahashi + */ + +if (typeof jQuery === 'undefined') { + throw new Error('jQuery.textcomplete requires jQuery'); +} + ++function ($) { + 'use strict'; + + var warn = function (message) { + if (console.warn) { console.warn(message); } + }; + + var id = 1; + + $.fn.textcomplete = function (strategies, option) { + var args = Array.prototype.slice.call(arguments); + return this.each(function () { + var self = this; + var $this = $(this); + var completer = $this.data('textComplete'); + if (!completer) { + option || (option = {}); + option._oid = id++; // unique object id + completer = new $.fn.textcomplete.Completer(this, option); + $this.data('textComplete', completer); + } + if (typeof strategies === 'string') { + if (!completer) return; + args.shift() + completer[strategies].apply(completer, args); + if (strategies === 'destroy') { + $this.removeData('textComplete'); + } + } else { + // For backward compatibility. + // TODO: Remove at v0.4 + $.each(strategies, function (obj) { + $.each(['header', 'footer', 'placement', 'maxCount'], function (name) { + if (obj[name]) { + completer.option[name] = obj[name]; + warn(name + 'as a strategy param is deprecated. Use option.'); + delete obj[name]; + } + }); + }); + completer.register($.fn.textcomplete.Strategy.parse(strategies, { + el: self, + $el: $this + })); + } + }); + }; + +}(jQuery); + ++function ($) { + 'use strict'; + + // Exclusive execution control utility. + // + // func - The function to be locked. It is executed with a function named + // `free` as the first argument. Once it is called, additional + // execution are ignored until the free is invoked. Then the last + // ignored execution will be replayed immediately. + // + // Examples + // + // var lockedFunc = lock(function (free) { + // setTimeout(function { free(); }, 1000); // It will be free in 1 sec. + // console.log('Hello, world'); + // }); + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // lockedFunc(); // none + // // 1 sec past then + // // => 'Hello, world' + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // + // Returns a wrapped function. + var lock = function (func) { + var locked, queuedArgsToReplay; + + return function () { + // Convert arguments into a real array. + var args = Array.prototype.slice.call(arguments); + if (locked) { + // Keep a copy of this argument list to replay later. + // OK to overwrite a previous value because we only replay + // the last one. + queuedArgsToReplay = args; + return; + } + locked = true; + var self = this; + args.unshift(function replayOrFree() { + if (queuedArgsToReplay) { + // Other request(s) arrived while we were locked. + // Now that the lock is becoming available, replay + // the latest such request, then call back here to + // unlock (or replay another request that arrived + // while this one was in flight). + var replayArgs = queuedArgsToReplay; + queuedArgsToReplay = undefined; + replayArgs.unshift(replayOrFree); + func.apply(self, replayArgs); + } else { + locked = false; + } + }); + func.apply(this, args); + }; + }; + + var isString = function (obj) { + return Object.prototype.toString.call(obj) === '[object String]'; + }; + + var isFunction = function (obj) { + return Object.prototype.toString.call(obj) === '[object Function]'; + }; + + var uniqueId = 0; + + function Completer(element, option) { + this.$el = $(element); + this.id = 'textcomplete' + uniqueId++; + this.strategies = []; + this.views = []; + this.option = $.extend({}, Completer._getDefaults(), option); + + if (!this.$el.is('input[type=text]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') { + throw new Error('textcomplete must be called on a Textarea or a ContentEditable.'); + } + + if (element === document.activeElement) { + // element has already been focused. Initialize view objects immediately. + this.initialize() + } else { + // Initialize view objects lazily. + var self = this; + this.$el.one('focus.' + this.id, function () { self.initialize(); }); + } + } + + Completer._getDefaults = function () { + if (!Completer.DEFAULTS) { + Completer.DEFAULTS = { + appendTo: $('body'), + zIndex: '100' + }; + } + + return Completer.DEFAULTS; + } + + $.extend(Completer.prototype, { + // Public properties + // ----------------- + + id: null, + option: null, + strategies: null, + adapter: null, + dropdown: null, + $el: null, + + // Public methods + // -------------- + + initialize: function () { + var element = this.$el.get(0); + // Initialize view objects. + this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); + var Adapter, viewName; + if (this.option.adapter) { + Adapter = this.option.adapter; + } else { + if (this.$el.is('textarea') || this.$el.is('input[type=text]')) { + viewName = typeof element.selectionEnd === 'number' ? 'Textarea' : 'IETextarea'; + } else { + viewName = 'ContentEditable'; + } + Adapter = $.fn.textcomplete[viewName]; + } + this.adapter = new Adapter(element, this, this.option); + }, + + destroy: function () { + this.$el.off('.' + this.id); + if (this.adapter) { + this.adapter.destroy(); + } + if (this.dropdown) { + this.dropdown.destroy(); + } + this.$el = this.adapter = this.dropdown = null; + }, + + // Invoke textcomplete. + trigger: function (text, skipUnchangedTerm) { + if (!this.dropdown) { this.initialize(); } + text != null || (text = this.adapter.getTextFromHeadToCaret()); + var searchQuery = this._extractSearchQuery(text); + if (searchQuery.length) { + var term = searchQuery[1]; + // Ignore shift-key, ctrl-key and so on. + if (skipUnchangedTerm && this._term === term) { return; } + this._term = term; + this._search.apply(this, searchQuery); + } else { + this._term = null; + this.dropdown.deactivate(); + } + }, + + fire: function (eventName) { + var args = Array.prototype.slice.call(arguments, 1); + this.$el.trigger(eventName, args); + return this; + }, + + register: function (strategies) { + Array.prototype.push.apply(this.strategies, strategies); + }, + + // Insert the value into adapter view. It is called when the dropdown is clicked + // or selected. + // + // value - The selected element of the array callbacked from search func. + // strategy - The Strategy object. + // e - Click or keydown event object. + select: function (value, strategy, e) { + this._term = null; + this.adapter.select(value, strategy, e); + this.fire('change').fire('textComplete:select', value, strategy); + this.adapter.focus(); + }, + + // Private properties + // ------------------ + + _clearAtNext: true, + _term: null, + + // Private methods + // --------------- + + // Parse the given text and extract the first matching strategy. + // + // Returns an array including the strategy, the query term and the match + // object if the text matches an strategy; otherwise returns an empty array. + _extractSearchQuery: function (text) { + for (var i = 0; i < this.strategies.length; i++) { + var strategy = this.strategies[i]; + var context = strategy.context(text); + if (context || context === '') { + var matchRegexp = isFunction(strategy.match) ? strategy.match(text) : strategy.match; + if (isString(context)) { text = context; } + var match = text.match(matchRegexp); + if (match) { return [strategy, match[strategy.index], match]; } + } + } + return [] + }, + + // Call the search method of selected strategy.. + _search: lock(function (free, strategy, term, match) { + var self = this; + strategy.search(term, function (data, stillSearching) { + if (!self.dropdown.shown) { + self.dropdown.activate(); + } + if (self._clearAtNext) { + // The first callback in the current lock. + self.dropdown.clear(); + self._clearAtNext = false; + } + self.dropdown.setPosition(self.adapter.getCaretPosition()); + self.dropdown.render(self._zip(data, strategy, term)); + if (!stillSearching) { + // The last callback in the current lock. + free(); + self._clearAtNext = true; // Call dropdown.clear at the next time. + } + }, match); + }), + + // Build a parameter for Dropdown#render. + // + // Examples + // + // this._zip(['a', 'b'], 's'); + // //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }] + _zip: function (data, strategy, term) { + return $.map(data, function (value) { + return { value: value, strategy: strategy, term: term }; + }); + } + }); + + $.fn.textcomplete.Completer = Completer; +}(jQuery); + ++function ($) { + 'use strict'; + + var $window = $(window); + + var include = function (zippedData, datum) { + var i, elem; + var idProperty = datum.strategy.idProperty + for (i = 0; i < zippedData.length; i++) { + elem = zippedData[i]; + if (elem.strategy !== datum.strategy) continue; + if (idProperty) { + if (elem.value[idProperty] === datum.value[idProperty]) return true; + } else { + if (elem.value === datum.value) return true; + } + } + return false; + }; + + var dropdownViews = {}; + $(document).on('click', function (e) { + var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown; + $.each(dropdownViews, function (key, view) { + if (key !== id) { view.deactivate(); } + }); + }); + + var commands = { + SKIP_DEFAULT: 0, + KEY_UP: 1, + KEY_DOWN: 2, + KEY_ENTER: 3, + KEY_PAGEUP: 4, + KEY_PAGEDOWN: 5, + KEY_ESCAPE: 6 + }; + + // Dropdown view + // ============= + + // Construct Dropdown object. + // + // element - Textarea or contenteditable element. + function Dropdown(element, completer, option) { + this.$el = Dropdown.createElement(option); + this.completer = completer; + this.id = completer.id + 'dropdown'; + this._data = []; // zipped data. + this.$inputEl = $(element); + this.option = option; + + // Override setPosition method. + if (option.listPosition) { this.setPosition = option.listPosition; } + if (option.height) { this.$el.height(option.height); } + var self = this; + $.each(['maxCount', 'placement', 'footer', 'header', 'noResultsMessage', 'className'], function (_i, name) { + if (option[name] != null) { self[name] = option[name]; } + }); + this._bindEvents(element); + dropdownViews[this.id] = this; + } + + $.extend(Dropdown, { + // Class methods + // ------------- + + createElement: function (option) { + var $parent = option.appendTo; + if (!($parent instanceof $)) { $parent = $($parent); } + var $el = $('<ul></ul>') + .addClass('textcomplete-dropdown') + .attr('id', 'textcomplete-dropdown-' + option._oid) + .css({ + display: 'none', + left: 0, + position: 'absolute', + zIndex: option.zIndex + }) + .appendTo($parent); + return $el; + } + }); + + $.extend(Dropdown.prototype, { + // Public properties + // ----------------- + + $el: null, // jQuery object of ul.dropdown-menu element. + $inputEl: null, // jQuery object of target textarea. + completer: null, + footer: null, + header: null, + id: null, + maxCount: 10, + placement: '', + shown: false, + data: [], // Shown zipped data. + className: '', + + // Public methods + // -------------- + + destroy: function () { + // Don't remove $el because it may be shared by several textcompletes. + this.deactivate(); + + this.$el.off('.' + this.id); + this.$inputEl.off('.' + this.id); + this.clear(); + this.$el = this.$inputEl = this.completer = null; + delete dropdownViews[this.id] + }, + + render: function (zippedData) { + var contentsHtml = this._buildContents(zippedData); + var unzippedData = $.map(this.data, function (d) { return d.value; }); + if (this.data.length) { + this._renderHeader(unzippedData); + this._renderFooter(unzippedData); + if (contentsHtml) { + this._renderContents(contentsHtml); + this._fitToBottom(); + this._activateIndexedItem(); + } + this._setScroll(); + } else if (this.noResultsMessage) { + this._renderNoResultsMessage(unzippedData); + } else if (this.shown) { + this.deactivate(); + } + }, + + setPosition: function (pos) { + this.$el.css(this._applyPlacement(pos)); + + // Make the dropdown fixed if the input is also fixed + // This can't be done during init, as textcomplete may be used on multiple elements on the same page + // Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed + var position = 'absolute'; + // Check if input or one of its parents has positioning we need to care about + this.$inputEl.add(this.$inputEl.parents()).each(function() { + if($(this).css('position') === 'absolute') // The element has absolute positioning, so it's all OK + return false; + if($(this).css('position') === 'fixed') { + position = 'fixed'; + return false; + } + }); + this.$el.css({ position: position }); // Update positioning + + return this; + }, + + clear: function () { + this.$el.html(''); + this.data = []; + this._index = 0; + this._$header = this._$footer = this._$noResultsMessage = null; + }, + + activate: function () { + if (!this.shown) { + this.clear(); + this.$el.show(); + if (this.className) { this.$el.addClass(this.className); } + this.completer.fire('textComplete:show'); + this.shown = true; + } + return this; + }, + + deactivate: function () { + if (this.shown) { + this.$el.hide(); + if (this.className) { this.$el.removeClass(this.className); } + this.completer.fire('textComplete:hide'); + this.shown = false; + } + return this; + }, + + isUp: function (e) { + return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P + }, + + isDown: function (e) { + return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N + }, + + isEnter: function (e) { + var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey; + return !modifiers && (e.keyCode === 13 || e.keyCode === 9 || (this.option.completeOnSpace === true && e.keyCode === 32)) // ENTER, TAB + }, + + isPageup: function (e) { + return e.keyCode === 33; // PAGEUP + }, + + isPagedown: function (e) { + return e.keyCode === 34; // PAGEDOWN + }, + + isEscape: function (e) { + return e.keyCode === 27; // ESCAPE + }, + + // Private properties + // ------------------ + + _data: null, // Currently shown zipped data. + _index: null, + _$header: null, + _$noResultsMessage: null, + _$footer: null, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); + this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); + this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); + this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); + }, + + _onClick: function (e) { + var $el = $(e.target); + e.preventDefault(); + e.originalEvent.keepTextCompleteDropdown = this.id; + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + var datum = this.data[parseInt($el.data('index'), 10)]; + this.completer.select(datum.value, datum.strategy, e); + var self = this; + // Deactive at next tick to allow other event handlers to know whether + // the dropdown has been shown or not. + setTimeout(function () { + self.deactivate(); + if (e.type === 'touchstart') { + self.$inputEl.focus(); + } + }, 0); + }, + + // Activate hovered item. + _onMouseover: function (e) { + var $el = $(e.target); + e.preventDefault(); + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + this._index = parseInt($el.data('index'), 10); + this._activateIndexedItem(); + }, + + _onKeydown: function (e) { + if (!this.shown) { return; } + + var command; + + if ($.isFunction(this.option.onKeydown)) { + command = this.option.onKeydown(e, commands); + } + + if (command == null) { + command = this._defaultKeydown(e); + } + + switch (command) { + case commands.KEY_UP: + e.preventDefault(); + this._up(); + break; + case commands.KEY_DOWN: + e.preventDefault(); + this._down(); + break; + case commands.KEY_ENTER: + e.preventDefault(); + this._enter(e); + break; + case commands.KEY_PAGEUP: + e.preventDefault(); + this._pageup(); + break; + case commands.KEY_PAGEDOWN: + e.preventDefault(); + this._pagedown(); + break; + case commands.KEY_ESCAPE: + e.preventDefault(); + this.deactivate(); + break; + } + }, + + _defaultKeydown: function (e) { + if (this.isUp(e)) { + return commands.KEY_UP; + } else if (this.isDown(e)) { + return commands.KEY_DOWN; + } else if (this.isEnter(e)) { + return commands.KEY_ENTER; + } else if (this.isPageup(e)) { + return commands.KEY_PAGEUP; + } else if (this.isPagedown(e)) { + return commands.KEY_PAGEDOWN; + } else if (this.isEscape(e)) { + return commands.KEY_ESCAPE; + } + }, + + _up: function () { + if (this._index === 0) { + this._index = this.data.length - 1; + } else { + this._index -= 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _down: function () { + if (this._index === this.data.length - 1) { + this._index = 0; + } else { + this._index += 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _enter: function (e) { + var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)]; + this.completer.select(datum.value, datum.strategy, e); + this.deactivate(); + }, + + _pageup: function () { + var target = 0; + var threshold = this._getActiveElement().position().top - this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top + $(this).outerHeight() > threshold) { + target = i; + return false; + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _pagedown: function () { + var target = this.data.length - 1; + var threshold = this._getActiveElement().position().top + this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top > threshold) { + target = i; + return false + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _activateIndexedItem: function () { + this.$el.find('.textcomplete-item.active').removeClass('active'); + this._getActiveElement().addClass('active'); + }, + + _getActiveElement: function () { + return this.$el.children('.textcomplete-item:nth(' + this._index + ')'); + }, + + _setScroll: function () { + var $activeEl = this._getActiveElement(); + var itemTop = $activeEl.position().top; + var itemHeight = $activeEl.outerHeight(); + var visibleHeight = this.$el.innerHeight(); + var visibleTop = this.$el.scrollTop(); + if (this._index === 0 || this._index == this.data.length - 1 || itemTop < 0) { + this.$el.scrollTop(itemTop + visibleTop); + } else if (itemTop + itemHeight > visibleHeight) { + this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight); + } + }, + + _buildContents: function (zippedData) { + var datum, i, index; + var html = ''; + for (i = 0; i < zippedData.length; i++) { + if (this.data.length === this.maxCount) break; + datum = zippedData[i]; + if (include(this.data, datum)) { continue; } + index = this.data.length; + this.data.push(datum); + html += '<li class="textcomplete-item" data-index="' + index + '"><a>'; + html += datum.strategy.template(datum.value, datum.term); + html += '</a></li>'; + } + return html; + }, + + _renderHeader: function (unzippedData) { + if (this.header) { + if (!this._$header) { + this._$header = $('<li class="textcomplete-header"></li>').prependTo(this.$el); + } + var html = $.isFunction(this.header) ? this.header(unzippedData) : this.header; + this._$header.html(html); + } + }, + + _renderFooter: function (unzippedData) { + if (this.footer) { + if (!this._$footer) { + this._$footer = $('<li class="textcomplete-footer"></li>').appendTo(this.$el); + } + var html = $.isFunction(this.footer) ? this.footer(unzippedData) : this.footer; + this._$footer.html(html); + } + }, + + _renderNoResultsMessage: function (unzippedData) { + if (this.noResultsMessage) { + if (!this._$noResultsMessage) { + this._$noResultsMessage = $('<li class="textcomplete-no-results-message"></li>').appendTo(this.$el); + } + var html = $.isFunction(this.noResultsMessage) ? this.noResultsMessage(unzippedData) : this.noResultsMessage; + this._$noResultsMessage.html(html); + } + }, + + _renderContents: function (html) { + if (this._$footer) { + this._$footer.before(html); + } else { + this.$el.append(html); + } + }, + + _fitToBottom: function() { + var windowScrollBottom = $window.scrollTop() + $window.height(); + var height = this.$el.height(); + if ((this.$el.position().top + height) > windowScrollBottom) { + this.$el.offset({top: windowScrollBottom - height}); + } + }, + + _applyPlacement: function (position) { + // If the 'placement' option set to 'top', move the position above the element. + if (this.placement.indexOf('top') !== -1) { + // Overwrite the position object to set the 'bottom' property instead of the top. + position = { + top: 'auto', + bottom: this.$el.parent().height() - position.top + position.lineHeight, + left: position.left + }; + } else { + position.bottom = 'auto'; + delete position.lineHeight; + } + if (this.placement.indexOf('absleft') !== -1) { + position.left = 0; + } else if (this.placement.indexOf('absright') !== -1) { + position.right = 0; + position.left = 'auto'; + } + return position; + } + }); + + $.fn.textcomplete.Dropdown = Dropdown; + $.extend($.fn.textcomplete, commands); +}(jQuery); + ++function ($) { + 'use strict'; + + // Memoize a search function. + var memoize = function (func) { + var memo = {}; + return function (term, callback) { + if (memo[term]) { + callback(memo[term]); + } else { + func.call(this, term, function (data) { + memo[term] = (memo[term] || []).concat(data); + callback.apply(null, arguments); + }); + } + }; + }; + + function Strategy(options) { + $.extend(this, options); + if (this.cache) { this.search = memoize(this.search); } + } + + Strategy.parse = function (strategiesArray, params) { + return $.map(strategiesArray, function (strategy) { + var strategyObj = new Strategy(strategy); + strategyObj.el = params.el; + strategyObj.$el = params.$el; + return strategyObj; + }); + }; + + $.extend(Strategy.prototype, { + // Public properties + // ----------------- + + // Required + match: null, + replace: null, + search: null, + + // Optional + cache: false, + context: function () { return true; }, + index: 2, + template: function (obj) { return obj; }, + idProperty: null + }); + + $.fn.textcomplete.Strategy = Strategy; + +}(jQuery); + ++function ($) { + 'use strict'; + + var now = Date.now || function () { return new Date().getTime(); }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // `wait` msec. + // + // This utility function was originally implemented at Underscore.js. + var debounce = function (func, wait) { + var timeout, args, context, timestamp, result; + var later = function () { + var last = now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + result = func.apply(context, args); + context = args = null; + } + }; + + return function () { + context = this; + args = arguments; + timestamp = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + } + return result; + }; + }; + + function Adapter () {} + + $.extend(Adapter.prototype, { + // Public properties + // ----------------- + + id: null, // Identity. + completer: null, // Completer object which creates it. + el: null, // Textarea element. + $el: null, // jQuery object of the textarea. + option: null, + + // Public methods + // -------------- + + initialize: function (element, completer, option) { + this.el = element; + this.$el = $(element); + this.id = completer.id + this.constructor.name; + this.completer = completer; + this.option = option; + + if (this.option.debounce) { + this._onKeyup = debounce(this._onKeyup, this.option.debounce); + } + + this._bindEvents(); + }, + + destroy: function () { + this.$el.off('.' + this.id); // Remove all event handlers. + this.$el = this.el = this.completer = null; + }, + + // Update the element with the given value and strategy. + // + // value - The selected object. It is one of the item of the array + // which was callbacked from the search function. + // strategy - The Strategy associated with the selected value. + select: function (/* value, strategy */) { + throw new Error('Not implemented'); + }, + + // Returns the caret's relative coordinates from body's left top corner. + // + // FIXME: Calculate the left top corner of `this.option.appendTo` element. + getCaretPosition: function () { + var position = this._getCaretRelativePosition(); + var offset = this.$el.offset(); + position.top += offset.top; + position.left += offset.left; + return position; + }, + + // Focus on the element. + focus: function () { + this.$el.focus(); + }, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this)); + }, + + _onKeyup: function (e) { + if (this._skipSearch(e)) { return; } + this.completer.trigger(this.getTextFromHeadToCaret(), true); + }, + + // Suppress searching if it returns true. + _skipSearch: function (clickEvent) { + switch (clickEvent.keyCode) { + case 13: // ENTER + case 40: // DOWN + case 38: // UP + return true; + } + if (clickEvent.ctrlKey) switch (clickEvent.keyCode) { + case 78: // Ctrl-N + case 80: // Ctrl-P + return true; + } + } + }); + + $.fn.textcomplete.Adapter = Adapter; +}(jQuery); + ++function ($) { + 'use strict'; + + // Textarea adapter + // ================ + // + // Managing a textarea. It doesn't know a Dropdown. + function Textarea(element, completer, option) { + this.initialize(element, completer, option); + } + + Textarea.DIV_PROPERTIES = { + left: -9999, + position: 'absolute', + top: 0, + whiteSpace: 'pre-wrap' + } + + Textarea.COPY_PROPERTIES = [ + 'border-width', 'font-family', 'font-size', 'font-style', 'font-variant', + 'font-weight', 'height', 'letter-spacing', 'word-spacing', 'line-height', + 'text-decoration', 'text-align', 'width', 'padding-top', 'padding-right', + 'padding-bottom', 'padding-left', 'margin-top', 'margin-right', + 'margin-bottom', 'margin-left', 'border-style', 'box-sizing', 'tab-size' + ]; + + $.extend(Textarea.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the textarea with the given value and strategy. + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(this.el.selectionEnd); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.selectionStart = this.el.selectionEnd = pre.length; + } + }, + + // Private methods + // --------------- + + // Returns the caret's relative coordinates from textarea's left top corner. + // + // Browser native API does not provide the way to know the position of + // caret in pixels, so that here we use a kind of hack to accomplish + // the aim. First of all it puts a dummy div element and completely copies + // the textarea's style to the element, then it inserts the text and a + // span element into the textarea. + // Consequently, the span element's position is the thing what we want. + _getCaretRelativePosition: function () { + var dummyDiv = $('<div></div>').css(this._copyCss()) + .text(this.getTextFromHeadToCaret()); + var span = $('<span></span>').text('.').appendTo(dummyDiv); + this.$el.before(dummyDiv); + var position = span.position(); + position.top += span.height() - this.$el.scrollTop(); + position.lineHeight = span.height(); + dummyDiv.remove(); + return position; + }, + + _copyCss: function () { + return $.extend({ + // Set 'scroll' if a scrollbar is being shown; otherwise 'auto'. + overflow: this.el.scrollHeight > this.el.offsetHeight ? 'scroll' : 'auto' + }, Textarea.DIV_PROPERTIES, this._getStyles()); + }, + + _getStyles: (function ($) { + var color = $('<div></div>').css(['color']).color; + if (typeof color !== 'undefined') { + return function () { + return this.$el.css(Textarea.COPY_PROPERTIES); + }; + } else { // jQuery < 1.8 + return function () { + var $el = this.$el; + var styles = {}; + $.each(Textarea.COPY_PROPERTIES, function (i, property) { + styles[property] = $el.css(property); + }); + return styles; + }; + } + })($), + + getTextFromHeadToCaret: function () { + return this.el.value.substring(0, this.el.selectionEnd); + } + }); + + $.fn.textcomplete.Textarea = Textarea; +}(jQuery); + ++function ($) { + 'use strict'; + + var sentinelChar = '吶'; + + function IETextarea(element, completer, option) { + this.initialize(element, completer, option); + $('<span>' + sentinelChar + '</span>').css({ + position: 'absolute', + top: -9999, + left: -9999 + }).insertBefore(element); + } + + $.extend(IETextarea.prototype, $.fn.textcomplete.Textarea.prototype, { + // Public methods + // -------------- + + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(pre.length); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.focus(); + var range = this.el.createTextRange(); + range.collapse(true); + range.moveEnd('character', pre.length); + range.moveStart('character', pre.length); + range.select(); + } + }, + + getTextFromHeadToCaret: function () { + this.el.focus(); + var range = document.selection.createRange(); + range.moveStart('character', -this.el.value.length); + var arr = range.text.split(sentinelChar) + return arr.length === 1 ? arr[0] : arr[1]; + } + }); + + $.fn.textcomplete.IETextarea = IETextarea; +}(jQuery); + +// NOTE: TextComplete plugin has contenteditable support but it does not work +// fine especially on old IEs. +// Any pull requests are REALLY welcome. + ++function ($) { + 'use strict'; + + // ContentEditable adapter + // ======================= + // + // Adapter for contenteditable elements. + function ContentEditable (element, completer, option) { + this.initialize(element, completer, option); + } + + $.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the content with the given value and strategy. + // When an dropdown item is selected, it is executed. + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var sel = window.getSelection() + var range = sel.getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + var content = selection.toString(); + var post = content.substring(range.startOffset); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + range.selectNodeContents(range.startContainer); + range.deleteContents(); + var node = document.createTextNode(pre + post); + range.insertNode(node); + range.setStart(node, pre.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + }, + + // Private methods + // --------------- + + // Returns the caret's relative position from the contenteditable's + // left top corner. + // + // Examples + // + // this._getCaretRelativePosition() + // //=> { top: 18, left: 200, lineHeight: 16 } + // + // Dropdown's position will be decided using the result. + _getCaretRelativePosition: function () { + var range = window.getSelection().getRangeAt(0).cloneRange(); + var node = document.createElement('span'); + range.insertNode(node); + range.selectNodeContents(node); + range.deleteContents(); + var $node = $(node); + var position = $node.offset(); + position.left -= this.$el.offset().left; + position.top += $node.height() - this.$el.offset().top; + position.lineHeight = $node.height(); + $node.remove(); + return position; + }, + + // Returns the string between the first character and the caret. + // Completer will be triggered with the result for start autocompleting. + // + // Example + // + // // Suppose the html is '<b>hello</b> wor|ld' and | is the caret. + // this.getTextFromHeadToCaret() + // // => ' wor' // not '<b>hello</b> wor' + getTextFromHeadToCaret: function () { + var range = window.getSelection().getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + return selection.toString().substring(0, range.startOffset); + } + }); + + $.fn.textcomplete.ContentEditable = ContentEditable; +}(jQuery); + +return jQuery; +})); +!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(x){this.app=x;this.router=new v();this.router.addRoute("screenshot-zone",e)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(y){var x=this;x.app.dropdown.close();$.get(y,function(z){$("body").append('<div id="popover-container"><div id="popover-content">'+z+"</div></div>");x.app.refresh();x.router.dispatch(this.app);x.afterOpen()})};t.prototype.close=function(x){if(this.isOpen()){if(x){x.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(y){y.preventDefault();y.stopPropagation();var x=y.target.getAttribute("href");if(!x){x=y.target.getAttribute("data-href")}if(x){this.open(x)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(x){x.stopPropagation()})};t.prototype.afterOpen=function(){var x=this;var y=$("#task-form");if(y){y.on("submit",function(z){z.preventDefault();$.ajax({type:"POST",url:y.attr("action"),data:y.serialize(),success:function(B,C,A){if(A.getResponseHeader("X-Ajax-Redirect")){window.location=A.getResponseHeader("X-Ajax-Redirect")}else{$("#popover-content").html(B);x.afterOpen()}}})})}};function r(){}r.prototype.listen=function(){var x=this;$(document).on("click",function(){x.close()});$(document).on("click",".dropdown-menu",function(B){B.preventDefault();B.stopImmediatePropagation();x.close();var z=$(this).next("ul");var A=240;var C=$(this).offset();var y=$(this).height();$("body").append(jQuery("<div>",{id:"dropdown"}));z.clone().appendTo("#dropdown");var D=$("#dropdown ul");D.css("left",C.left);if(C.top+A-$(window).scrollTop()>$(window).height()){D.css("top",C.top-A-y)}else{D.css("top",C.top+y)}D.addClass("dropdown-submenu-open")});$(document).on("click",".dropdown-submenu-open li",function(y){if($(y.target).is("li")){$(this).find("a:visible")[0].click()}});$("textarea[data-mention-search-url]").textcomplete([{match:/(^|\s)@(\w*)$/,search:function(z,A){var y=$("textarea[data-mention-search-url]").data("mention-search-url");$.getJSON(y,{q:z}).done(function(B){A(B)}).fail(function(){A([])})},replace:function(y){return"$1@"+y+" "},cache:true}],{className:"textarea-dropdown"})};r.prototype.close=function(){$("#dropdown").remove()};function q(x){this.app=x}q.prototype.listen=function(){var x=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(y,z){$(this).css(y);var A=z.target.left+z.target.width/2-z.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(z.vertical).addClass(A<1?"align-left":"align-right").appendTo(this)}},content:function(){var A=this;var y=$(this).attr("data-href");if(!y){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(y,function z(D){var C=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(D);C.css({top:"",left:""});C.children(".tooltip-arrow").remove();var B=$(A).tooltip("option","position");B.of=$(A);C.position(B);$("#tooltip-subtasks a").not(".popover").click(function(E){E.preventDefault();E.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){x.app.popover.open($(this).attr("href"));$(A).tooltip("close")}else{$.get($(this).attr("href"),z)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var y=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(y).tooltip("close")})}).on("mouseleave focusout",function(y){y.stopImmediatePropagation();var z=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(z).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(B){B.preventDefault();var y=$(".write-area");var A=$(".preview-area");var x=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var z=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:x.val()})});z.done(function(C){A.find(".markdown").html(C);A.css("height",x.css("height"));A.css("width",x.css("width"));y.hide();A.show()})};l.prototype.showWriter=function(x){x.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(x){x.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(x){x.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(x){this.app=x;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var x=this;$(document).on("click",".filter-helper",function(A){A.preventDefault();var z=$(this).data("filter");var y=$(this).data("append-filter");if(y){z=$("#form-search").val()+" "+y}$("#form-search").val(z);if($("#board").length){x.app.board.reloadFilters(z)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("v b",function(z){var y=$(".view-board");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v c",function(z){var y=$(".view-calendar");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v l",function(z){var y=$(".view-listing");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v g",function(z){var y=$(".view-gantt");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("f",function(z){z.preventDefault();var y=document.getElementById("form-search");if(y){y.focus()}});Mousetrap.bind("r",function(z){z.preventDefault();var y=$(".filter-reset").data("filter");$("#form-search").val(y);if($("#board").length){x.app.board.reloadFilters(y)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var x=false;$("select.task-reload-project-destination").change(function(){if(!x){$(".loading-icon").show();x=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(x,y){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(x){x.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(y){y.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){x.popover.close();x.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var y=$(this);var z=y.data("dst-field");var x=y.data("dst-extra-field");if($("#form-"+z).val()==""){y.parent().find("input[type=submit]").attr("disabled","disabled")}y.autocomplete({source:y.data("search-url"),minLength:1,select:function(A,B){$("input[name="+z+"]").val(B.item.id);if(x){$("input[name="+x+"]").val(B.item[x])}y.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").chosen({width:"180px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$(".select-auto-redirect").change(function(){var x=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(x,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var x="";if(typeof document.hidden!=="undefined"){x="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){x="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){x="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){x="webkitVisibilityState"}}}}if(x!=""){return document[x]=="visible"}return true};m.prototype.formatDuration=function(x){if(x>=86400){return Math.round(x/86400)+"d"}else{if(x>=3600){return Math.round(x/3600)+"h"}else{if(x>=60){return Math.round(x/60)+"m"}}}return x+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};e.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(C){if(C.clipboardData&&C.clipboardData.items){var A=C.clipboardData.items;if(A){for(var B=0;B<A.length;B++){if(A[B].type.indexOf("image")!==-1){var z=A[B].getAsFile();var x=new FileReader();var y=this;x.onload=function(D){y.createImage(D.target.result)};x.readAsDataURL(z)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var x=this.pasteCatcher.childNodes[0];if(x){if(x.tagName==="IMG"){this.createImage(x.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(z){var y=new Image();y.src=z;y.onload=function(){var A=z.split("base64,");var B=A[1];$("input[name=screenshot]").val(B)};var x=document.getElementById("screenshot-zone");x.innerHTML="";x.className="screenshot-pasted";x.appendChild(y);this.destroy();this.initialize()};function j(){}j.prototype.execute=function(){var x=$("#calendar");x.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(y){$.ajax({cache:false,url:x.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:y.id,date_due:y.start.format()})})},viewRender:function(){var y=x.data("check-url");var A={start:x.fullCalendar("getView").start.format(),end:x.fullCalendar("getView").end.format()};for(var z in A){y+="&"+z+"="+A[z]}$.getJSON(y,function(B){x.fullCalendar("removeEvents");x.fullCalendar("addEventSource",B);x.fullCalendar("rerenderEvents")})}})};function k(x){this.app=x;this.checkInterval=null}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.columnScrolling();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var x=parseInt($("#board").attr("data-check-interval"));if(x>0){this.checkInterval=window.setInterval(this.check.bind(this),x*1000)}};k.prototype.reloadFilters=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(y){x.refresh(y)},304:function(){x.app.hideLoadingIcon()}}})}};k.prototype.save=function(z,A,x,y){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:z,column_id:A,swimlane_id:y,position:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.refresh=function(x){$("#board-container").replaceWith(x);this.app.refresh();this.app.swimlane.refresh();this.columnScrolling();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode()};k.prototype.dragAndDrop=function(){var x=this;var y={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(z,A){A.item.removeClass("draggable-item-selected");x.save(A.item.attr("data-task-id"),A.item.parent().attr("data-column-id"),A.item.index()+1,A.item.parent().attr("data-swimlane-id"))},start:function(z,A){A.item.addClass("draggable-item-selected");A.placeholder.height(A.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");y.handle=".task-board-sort-handle"}$(".board-task-list").sortable(y)};k.prototype.listen=function(){var x=this;$(document).on("click",".task-board",function(y){if(y.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(y){y.preventDefault();x.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(y){y.preventDefault();x.toggleColumnScrolling()});$(document).on("click",".board-column-title",function(){x.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var x=localStorage.getItem("column_scroll")||1;localStorage.setItem("column_scroll",x==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-task-list").each(function(){$(this).css("min-height",80);$(this).css("height","");$(".board-rotation-wrapper").css("min-height","")})}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var x=$(window).height()-170;$(".board-task-list").css("height",x);$(".board-rotation-wrapper").css("min-height",x)}}};k.prototype.toggleCompactView=function(){var x=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",x==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(y){$(".filter-display-mode").toggle();x.refresh(y)}})};k.prototype.restoreColumnViewMode=function(){var x=this;$(".board-column-header").each(function(){var y=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+y)){x.hideColumn(y)}})};k.prototype.toggleColumnViewMode=function(x){if(localStorage.getItem("hidden_column_"+x)){this.showColumn(x)}else{this.hideColumn(x)}};k.prototype.hideColumn=function(x){$(".board-column-"+x+" .board-column-expanded").hide();$(".board-column-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x+" .board-column-expanded").hide();$(".board-column-header-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+x).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+x+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+x+"").height())});localStorage.setItem("hidden_column_"+x,1)};k.prototype.showColumn=function(x){$(".board-column-"+x+" .board-column-expanded").show();$(".board-column-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x+" .board-column-expanded").show();$(".board-column-header-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x).removeClass("board-column-header-collapsed");$(".board-column-"+x).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+x).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+x)};k.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("c",function(){x.toggleCompactView()});Mousetrap.bind("s",function(){x.toggleCollapsedMode()});Mousetrap.bind("n",function(){x.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(y){var z=this.getAllCollapsed();var x=z.indexOf(y);if(x>-1){z.splice(x,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(z));$(".board-swimlane-columns-"+y).css("display","table-row");$(".board-swimlane-tasks-"+y).css("display","table-row");$(".hide-icon-swimlane-"+y).css("display","inline");$(".show-icon-swimlane-"+y).css("display","none")};g.prototype.collapse=function(x){var y=this.getAllCollapsed();if(y.indexOf(x)<0){y.push(x);localStorage.setItem(this.getStorageKey(),JSON.stringify(y))}$(".board-swimlane-columns-"+x+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+x).css("display","none");$(".hide-icon-swimlane-"+x).css("display","none");$(".show-icon-swimlane-"+x).css("display","inline")};g.prototype.isCollapsed=function(x){return this.getAllCollapsed().indexOf(x)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var y=this.getAllCollapsed();for(var x=0;x<y.length;x++){this.collapse(y[x])}};g.prototype.listen=function(){var x=this;$(document).on("click",".board-swimlane-toggle",function(z){z.preventDefault();var y=$(this).data("swimlane-id");if(x.isCollapsed(y)){x.expand(y)}else{x.collapse(y)}})};function c(x){this.app=x;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(x),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var A=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var z=this.getDateRange(A);var x=z[0];var C=z[1];var y=$(this.options.container);var B=jQuery("<div>",{"class":"ganttview"});B.append(this.renderVerticalHeader());B.append(this.renderSlider(x,C));y.append(B);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",y).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(x);this.listenForBlockMove(x)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var B=jQuery("<div>",{"class":"ganttview-vtheader"});var y=jQuery("<div>",{"class":"ganttview-vtheader-item"});var A=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var x=0;x<this.data.length;x++){var z=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[x])})).append(" ");if(this.data[x].type=="task"){z.append(jQuery("<a>",{href:this.data[x].link,target:"_blank",title:this.data[x].title}).append(this.data[x].title))}else{z.append(jQuery("<a>",{href:this.data[x].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].link,target:"_blank"}).append(this.data[x].title))}A.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(z))}y.append(A);B.append(y);return B};c.prototype.renderSlider=function(y,A){var x=jQuery("<div>",{"class":"ganttview-slide-container"});var z=this.getDates(y,A);x.append(this.renderHorizontalHeader(z));x.append(this.renderGrid(z));x.append(this.addBlockContainers());this.addBlocks(x,y);return x};c.prototype.renderHorizontalHeader=function(x){var E=jQuery("<div>",{"class":"ganttview-hzheader"});var C=jQuery("<div>",{"class":"ganttview-hzheader-months"});var B=jQuery("<div>",{"class":"ganttview-hzheader-days"});var A=0;for(var F in x){for(var z in x[F]){var G=x[F][z].length*this.options.cellWidth;A=A+G;C.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(G-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[z]+" "+F));for(var D in x[F][z]){B.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(x[F][z][D].getDate()))}}}C.css("width",A+"px");B.css("width",A+"px");E.append(C).append(B);return E};c.prototype.renderGrid=function(x){var G=jQuery("<div>",{"class":"ganttview-grid"});var B=jQuery("<div>",{"class":"ganttview-grid-row"});for(var E in x){for(var z in x[E]){for(var D in x[E][z]){var A=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(x[E][z][D])){A.addClass("ganttview-weekend")}B.append(A)}}}var F=jQuery("div.ganttview-grid-row-cell",B).length*this.options.cellWidth;B.css("width",F+"px");G.css("width",F+"px");for(var C=0;C<this.data.length;C++){G.append(B.clone())}return G};c.prototype.addBlockContainers=function(){var y=jQuery("<div>",{"class":"ganttview-blocks"});for(var x=0;x<this.data.length;x++){y.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return y};c.prototype.addBlocks=function(y,x){var F=jQuery("div.ganttview-blocks div.ganttview-block-container",y);var z=0;for(var C=0;C<this.data.length;C++){var D=this.data[C];var G=this.daysBetween(D.start,D.end)+1;var B=this.daysBetween(x,D.start);var E=jQuery("<div>",{"class":"ganttview-block-text"});var A=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(D),css:{width:((G*this.options.cellWidth)-9)+"px","margin-left":(B*this.options.cellWidth)+"px"}}).append(E);if(G>=2){E.append(D.progress)}A.data("record",D);this.setBarColor(A,D);if(D.progress!="0%"){A.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":D.color.border,width:D.progress,opacity:0.4}}))}jQuery(F[z]).append(A);z=z+1}};c.prototype.getVerticalHeaderTooltip=function(y){var D="";if(y.type=="task"){D="<strong>"+y.column_title+"</strong> ("+y.progress+")<br/>"+y.title}else{var A=["managers","members"];for(var z in A){var B=A[z];if(!jQuery.isEmptyObject(y.users[B])){var C=jQuery("<ul>");for(var x in y.users[B]){C.append(jQuery("<li>").append(y.users[B][x]))}D+="<p><strong>"+$(this.options.container).data("label-"+B)+"</strong></p>"+C[0].outerHTML}}}return D};c.prototype.getBarTooltip=function(x){var y="";if(x.not_defined){y=$(this.options.container).data("label-not-defined")}else{if(x.type=="task"){y="<strong>"+x.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(x.assignee?x.assignee:"")+"<br/>"}y+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.start)+"<br/>";y+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.end)}return y};c.prototype.setBarColor=function(y,x){if(x.not_defined){y.addClass("ganttview-block-not-defined")}else{y.css("background-color",x.color.background);y.css("border-color",x.color.border)}};c.prototype.listenForBlockResize=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.listenForBlockMove=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.updateDataAndPosition=function(C,A){var x=jQuery("div.ganttview-slide-container",this.options.container);var G=x.scrollLeft();var D=C.offset().left-x.offset().left-1+G;var F=C.data("record");F.not_defined=false;this.setBarColor(C,F);var z=Math.round(D/this.options.cellWidth);var E=this.addDays(this.cloneDate(A),z);F.start=E;var y=C.outerWidth();var B=Math.round(y/this.options.cellWidth)-1;F.end=this.addDays(this.cloneDate(E),B);if(F.type==="task"&&B>0){jQuery("div.ganttview-block-text",C).text(F.progress)}C.attr("title",this.getBarTooltip(F));C.data("record",F);C.css("top","").css("left","").css("position","relative").css("margin-left",D+"px")};c.prototype.getDates=function(B,x){var A=[];A[B.getFullYear()]=[];A[B.getFullYear()][B.getMonth()]=[B];var z=B;while(this.compareDate(z,x)==-1){var y=this.addDays(this.cloneDate(z),1);if(!A[y.getFullYear()]){A[y.getFullYear()]=[]}if(!A[y.getFullYear()][y.getMonth()]){A[y.getFullYear()][y.getMonth()]=[]}A[y.getFullYear()][y.getMonth()].push(y);z=y}return A};c.prototype.prepareData=function(z){for(var y=0;y<z.length;y++){var A=new Date(z[y].start[0],z[y].start[1]-1,z[y].start[2],0,0,0,0);z[y].start=A;var x=new Date(z[y].end[0],z[y].end[1]-1,z[y].end[2],0,0,0,0);z[y].end=x}return z};c.prototype.getDateRange=function(z){var C=new Date();var y=new Date();for(var A=0;A<this.data.length;A++){var B=new Date();B.setTime(Date.parse(this.data[A].start));var x=new Date();x.setTime(Date.parse(this.data[A].end));if(A==0){C=B;y=x}if(this.compareDate(C,B)==1){C=B}if(this.compareDate(y,x)==-1){y=x}}if(this.daysBetween(C,y)<z){y=this.addDays(this.cloneDate(C),z)}C.setDate(C.getDate()-1);return[C,y]};c.prototype.daysBetween=function(A,x){if(!A||!x){return 0}var z=0,y=this.cloneDate(A);while(this.compareDate(y,x)==-1){z=z+1;this.addDays(y,1)}return z};c.prototype.isWeekend=function(x){return x.getDay()%6==0};c.prototype.cloneDate=function(x){return new Date(x.getTime())};c.prototype.addDays=function(x,y){x.setDate(x.getDate()+y*1);return x};c.prototype.compareDate=function(y,x){if(isNaN(y)||isNaN(x)){throw new Error(y+" - "+x)}else{if(y instanceof Date&&x instanceof Date){return(y<x)?-1:(y>x)?1:0}else{throw new TypeError(y+" - "+x)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})};function s(){}s.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].column_title,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function p(){}p.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].user,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function d(){}d.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[];var x=[];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]]);if(z>0){x.push(D[B][z])}}else{C[z].push(D[B][z]);if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C,type:"area-spline",groups:[x]},axis:{x:{type:"category",categories:y}}})};function o(){}o.prototype.execute=function(){var C=$("#chart").data("metrics");var B=[[$("#chart").data("label-total")]];var x=[];var z=d3.time.format("%Y-%m-%d");var D=d3.time.format($("#chart").data("date-format"));for(var A=0;A<C.length;A++){for(var y=0;y<C[A].length;y++){if(A==0){B.push([C[A][y]])}else{B[y+1].push(C[A][y]);if(y>0){if(B[0][A]==undefined){B[0].push(0)}B[0][A]+=C[A][y]}if(y==0){x.push(D(z.parse(C[A][y])))}}}}c3.generate({data:{columns:B},axis:{x:{type:"category",categories:x}}})};function h(x){this.app=x}h.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y in z){A.push(z[y].average);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function w(x){this.app=x}w.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y=0;y<z.length;y++){A.push(z[y].time_spent);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(x){this.app=x}u.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[$("#chart").data("label-cycle")];var z=[$("#chart").data("label-lead")];var y=[];var B={};B[$("#chart").data("label-cycle")]="area";B[$("#chart").data("label-lead")]="area-spline";var x={};x[$("#chart").data("label-lead")]="#afb42b";x[$("#chart").data("label-cycle")]="#4e342e";for(var A=0;A<D.length;A++){C.push(parseInt(D[A].avg_cycle_time));z.push(parseInt(D[A].avg_lead_time));y.push(D[A].day)}c3.generate({data:{columns:[z,C],types:B,colors:x},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}}})};function i(x){this.app=x}i.prototype.execute=function(){var A=$("#chart").data("metrics");var B=[$("#chart").data("label-spent")];var z=[$("#chart").data("label-estimated")];var y=[];for(var x in A){B.push(parseInt(A[x].time_spent));z.push(parseInt(A[x].time_estimated));y.push(x)}c3.generate({data:{columns:[B,z],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:y}},legend:{show:true}})};function v(){this.routes={}}v.prototype.addRoute=function(y,x){this.routes[y]=x};v.prototype.dispatch=function(y){for(var z in this.routes){if(document.getElementById(z)){var x=Object.create(this.routes[z].prototype);this.routes[z].apply(x,[y]);x.execute();break}}};jQuery(document).ready(function(){var y=new m();var x=new v();x.addRoute("board",k);x.addRoute("calendar",j);x.addRoute("screenshot-zone",e);x.addRoute("analytic-task-repartition",s);x.addRoute("analytic-user-repartition",p);x.addRoute("analytic-cfd",d);x.addRoute("analytic-burndown",o);x.addRoute("analytic-avg-time-column",h);x.addRoute("analytic-task-time-column",w);x.addRoute("analytic-lead-cycle-time",u);x.addRoute("analytic-compare-hours",i);x.addRoute("gantt-chart",c);x.dispatch(y);y.listen()})})();
\ No newline at end of file diff --git a/assets/js/src/Board.js b/assets/js/src/Board.js index b98aa366..fe5fc687 100644 --- a/assets/js/src/Board.js +++ b/assets/js/src/Board.js @@ -180,7 +180,7 @@ Board.prototype.columnScrolling = function() { }); } else { - var height = $(window).height() - 145; + var height = $(window).height() - 170; $(".board-task-list").css("height", height); $(".board-rotation-wrapper").css("min-height", height); diff --git a/assets/js/src/Dropdown.js b/assets/js/src/Dropdown.js index 20fca8a7..81ce2509 100644 --- a/assets/js/src/Dropdown.js +++ b/assets/js/src/Dropdown.js @@ -40,6 +40,21 @@ Dropdown.prototype.listen = function() { $(this).find('a:visible')[0].click(); // Calling native click() not the jQuery one } }); + + // User mention autocomplete + $('textarea[data-mention-search-url]').textcomplete([{ + match: /(^|\s)@(\w*)$/, + search: function (term, callback) { + var url = $('textarea[data-mention-search-url]').data('mention-search-url'); + $.getJSON(url, { q: term }) + .done(function (resp) { callback(resp); }) + .fail(function () { callback([]); }); + }, + replace: function (value) { + return '$1@' + value + ' '; + }, + cache: true + }], {className: "textarea-dropdown"}); }; Dropdown.prototype.close = function() { diff --git a/assets/js/vendor/jquery.textcomplete.js b/assets/js/vendor/jquery.textcomplete.js new file mode 100644 index 00000000..0e548a65 --- /dev/null +++ b/assets/js/vendor/jquery.textcomplete.js @@ -0,0 +1,1227 @@ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === "object" && module.exports) { + var $ = require('jquery'); + module.exports = factory($); + } else { + // Browser globals + factory(jQuery); + } +}(function (jQuery) { + +/*! + * jQuery.textcomplete + * + * Repository: https://github.com/yuku-t/jquery-textcomplete + * License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE) + * Author: Yuku Takahashi + */ + +if (typeof jQuery === 'undefined') { + throw new Error('jQuery.textcomplete requires jQuery'); +} + ++function ($) { + 'use strict'; + + var warn = function (message) { + if (console.warn) { console.warn(message); } + }; + + var id = 1; + + $.fn.textcomplete = function (strategies, option) { + var args = Array.prototype.slice.call(arguments); + return this.each(function () { + var self = this; + var $this = $(this); + var completer = $this.data('textComplete'); + if (!completer) { + option || (option = {}); + option._oid = id++; // unique object id + completer = new $.fn.textcomplete.Completer(this, option); + $this.data('textComplete', completer); + } + if (typeof strategies === 'string') { + if (!completer) return; + args.shift() + completer[strategies].apply(completer, args); + if (strategies === 'destroy') { + $this.removeData('textComplete'); + } + } else { + // For backward compatibility. + // TODO: Remove at v0.4 + $.each(strategies, function (obj) { + $.each(['header', 'footer', 'placement', 'maxCount'], function (name) { + if (obj[name]) { + completer.option[name] = obj[name]; + warn(name + 'as a strategy param is deprecated. Use option.'); + delete obj[name]; + } + }); + }); + completer.register($.fn.textcomplete.Strategy.parse(strategies, { + el: self, + $el: $this + })); + } + }); + }; + +}(jQuery); + ++function ($) { + 'use strict'; + + // Exclusive execution control utility. + // + // func - The function to be locked. It is executed with a function named + // `free` as the first argument. Once it is called, additional + // execution are ignored until the free is invoked. Then the last + // ignored execution will be replayed immediately. + // + // Examples + // + // var lockedFunc = lock(function (free) { + // setTimeout(function { free(); }, 1000); // It will be free in 1 sec. + // console.log('Hello, world'); + // }); + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // lockedFunc(); // none + // // 1 sec past then + // // => 'Hello, world' + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // + // Returns a wrapped function. + var lock = function (func) { + var locked, queuedArgsToReplay; + + return function () { + // Convert arguments into a real array. + var args = Array.prototype.slice.call(arguments); + if (locked) { + // Keep a copy of this argument list to replay later. + // OK to overwrite a previous value because we only replay + // the last one. + queuedArgsToReplay = args; + return; + } + locked = true; + var self = this; + args.unshift(function replayOrFree() { + if (queuedArgsToReplay) { + // Other request(s) arrived while we were locked. + // Now that the lock is becoming available, replay + // the latest such request, then call back here to + // unlock (or replay another request that arrived + // while this one was in flight). + var replayArgs = queuedArgsToReplay; + queuedArgsToReplay = undefined; + replayArgs.unshift(replayOrFree); + func.apply(self, replayArgs); + } else { + locked = false; + } + }); + func.apply(this, args); + }; + }; + + var isString = function (obj) { + return Object.prototype.toString.call(obj) === '[object String]'; + }; + + var isFunction = function (obj) { + return Object.prototype.toString.call(obj) === '[object Function]'; + }; + + var uniqueId = 0; + + function Completer(element, option) { + this.$el = $(element); + this.id = 'textcomplete' + uniqueId++; + this.strategies = []; + this.views = []; + this.option = $.extend({}, Completer._getDefaults(), option); + + if (!this.$el.is('input[type=text]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') { + throw new Error('textcomplete must be called on a Textarea or a ContentEditable.'); + } + + if (element === document.activeElement) { + // element has already been focused. Initialize view objects immediately. + this.initialize() + } else { + // Initialize view objects lazily. + var self = this; + this.$el.one('focus.' + this.id, function () { self.initialize(); }); + } + } + + Completer._getDefaults = function () { + if (!Completer.DEFAULTS) { + Completer.DEFAULTS = { + appendTo: $('body'), + zIndex: '100' + }; + } + + return Completer.DEFAULTS; + } + + $.extend(Completer.prototype, { + // Public properties + // ----------------- + + id: null, + option: null, + strategies: null, + adapter: null, + dropdown: null, + $el: null, + + // Public methods + // -------------- + + initialize: function () { + var element = this.$el.get(0); + // Initialize view objects. + this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); + var Adapter, viewName; + if (this.option.adapter) { + Adapter = this.option.adapter; + } else { + if (this.$el.is('textarea') || this.$el.is('input[type=text]')) { + viewName = typeof element.selectionEnd === 'number' ? 'Textarea' : 'IETextarea'; + } else { + viewName = 'ContentEditable'; + } + Adapter = $.fn.textcomplete[viewName]; + } + this.adapter = new Adapter(element, this, this.option); + }, + + destroy: function () { + this.$el.off('.' + this.id); + if (this.adapter) { + this.adapter.destroy(); + } + if (this.dropdown) { + this.dropdown.destroy(); + } + this.$el = this.adapter = this.dropdown = null; + }, + + // Invoke textcomplete. + trigger: function (text, skipUnchangedTerm) { + if (!this.dropdown) { this.initialize(); } + text != null || (text = this.adapter.getTextFromHeadToCaret()); + var searchQuery = this._extractSearchQuery(text); + if (searchQuery.length) { + var term = searchQuery[1]; + // Ignore shift-key, ctrl-key and so on. + if (skipUnchangedTerm && this._term === term) { return; } + this._term = term; + this._search.apply(this, searchQuery); + } else { + this._term = null; + this.dropdown.deactivate(); + } + }, + + fire: function (eventName) { + var args = Array.prototype.slice.call(arguments, 1); + this.$el.trigger(eventName, args); + return this; + }, + + register: function (strategies) { + Array.prototype.push.apply(this.strategies, strategies); + }, + + // Insert the value into adapter view. It is called when the dropdown is clicked + // or selected. + // + // value - The selected element of the array callbacked from search func. + // strategy - The Strategy object. + // e - Click or keydown event object. + select: function (value, strategy, e) { + this._term = null; + this.adapter.select(value, strategy, e); + this.fire('change').fire('textComplete:select', value, strategy); + this.adapter.focus(); + }, + + // Private properties + // ------------------ + + _clearAtNext: true, + _term: null, + + // Private methods + // --------------- + + // Parse the given text and extract the first matching strategy. + // + // Returns an array including the strategy, the query term and the match + // object if the text matches an strategy; otherwise returns an empty array. + _extractSearchQuery: function (text) { + for (var i = 0; i < this.strategies.length; i++) { + var strategy = this.strategies[i]; + var context = strategy.context(text); + if (context || context === '') { + var matchRegexp = isFunction(strategy.match) ? strategy.match(text) : strategy.match; + if (isString(context)) { text = context; } + var match = text.match(matchRegexp); + if (match) { return [strategy, match[strategy.index], match]; } + } + } + return [] + }, + + // Call the search method of selected strategy.. + _search: lock(function (free, strategy, term, match) { + var self = this; + strategy.search(term, function (data, stillSearching) { + if (!self.dropdown.shown) { + self.dropdown.activate(); + } + if (self._clearAtNext) { + // The first callback in the current lock. + self.dropdown.clear(); + self._clearAtNext = false; + } + self.dropdown.setPosition(self.adapter.getCaretPosition()); + self.dropdown.render(self._zip(data, strategy, term)); + if (!stillSearching) { + // The last callback in the current lock. + free(); + self._clearAtNext = true; // Call dropdown.clear at the next time. + } + }, match); + }), + + // Build a parameter for Dropdown#render. + // + // Examples + // + // this._zip(['a', 'b'], 's'); + // //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }] + _zip: function (data, strategy, term) { + return $.map(data, function (value) { + return { value: value, strategy: strategy, term: term }; + }); + } + }); + + $.fn.textcomplete.Completer = Completer; +}(jQuery); + ++function ($) { + 'use strict'; + + var $window = $(window); + + var include = function (zippedData, datum) { + var i, elem; + var idProperty = datum.strategy.idProperty + for (i = 0; i < zippedData.length; i++) { + elem = zippedData[i]; + if (elem.strategy !== datum.strategy) continue; + if (idProperty) { + if (elem.value[idProperty] === datum.value[idProperty]) return true; + } else { + if (elem.value === datum.value) return true; + } + } + return false; + }; + + var dropdownViews = {}; + $(document).on('click', function (e) { + var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown; + $.each(dropdownViews, function (key, view) { + if (key !== id) { view.deactivate(); } + }); + }); + + var commands = { + SKIP_DEFAULT: 0, + KEY_UP: 1, + KEY_DOWN: 2, + KEY_ENTER: 3, + KEY_PAGEUP: 4, + KEY_PAGEDOWN: 5, + KEY_ESCAPE: 6 + }; + + // Dropdown view + // ============= + + // Construct Dropdown object. + // + // element - Textarea or contenteditable element. + function Dropdown(element, completer, option) { + this.$el = Dropdown.createElement(option); + this.completer = completer; + this.id = completer.id + 'dropdown'; + this._data = []; // zipped data. + this.$inputEl = $(element); + this.option = option; + + // Override setPosition method. + if (option.listPosition) { this.setPosition = option.listPosition; } + if (option.height) { this.$el.height(option.height); } + var self = this; + $.each(['maxCount', 'placement', 'footer', 'header', 'noResultsMessage', 'className'], function (_i, name) { + if (option[name] != null) { self[name] = option[name]; } + }); + this._bindEvents(element); + dropdownViews[this.id] = this; + } + + $.extend(Dropdown, { + // Class methods + // ------------- + + createElement: function (option) { + var $parent = option.appendTo; + if (!($parent instanceof $)) { $parent = $($parent); } + var $el = $('<ul></ul>') + .addClass('textcomplete-dropdown') + .attr('id', 'textcomplete-dropdown-' + option._oid) + .css({ + display: 'none', + left: 0, + position: 'absolute', + zIndex: option.zIndex + }) + .appendTo($parent); + return $el; + } + }); + + $.extend(Dropdown.prototype, { + // Public properties + // ----------------- + + $el: null, // jQuery object of ul.dropdown-menu element. + $inputEl: null, // jQuery object of target textarea. + completer: null, + footer: null, + header: null, + id: null, + maxCount: 10, + placement: '', + shown: false, + data: [], // Shown zipped data. + className: '', + + // Public methods + // -------------- + + destroy: function () { + // Don't remove $el because it may be shared by several textcompletes. + this.deactivate(); + + this.$el.off('.' + this.id); + this.$inputEl.off('.' + this.id); + this.clear(); + this.$el = this.$inputEl = this.completer = null; + delete dropdownViews[this.id] + }, + + render: function (zippedData) { + var contentsHtml = this._buildContents(zippedData); + var unzippedData = $.map(this.data, function (d) { return d.value; }); + if (this.data.length) { + this._renderHeader(unzippedData); + this._renderFooter(unzippedData); + if (contentsHtml) { + this._renderContents(contentsHtml); + this._fitToBottom(); + this._activateIndexedItem(); + } + this._setScroll(); + } else if (this.noResultsMessage) { + this._renderNoResultsMessage(unzippedData); + } else if (this.shown) { + this.deactivate(); + } + }, + + setPosition: function (pos) { + this.$el.css(this._applyPlacement(pos)); + + // Make the dropdown fixed if the input is also fixed + // This can't be done during init, as textcomplete may be used on multiple elements on the same page + // Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed + var position = 'absolute'; + // Check if input or one of its parents has positioning we need to care about + this.$inputEl.add(this.$inputEl.parents()).each(function() { + if($(this).css('position') === 'absolute') // The element has absolute positioning, so it's all OK + return false; + if($(this).css('position') === 'fixed') { + position = 'fixed'; + return false; + } + }); + this.$el.css({ position: position }); // Update positioning + + return this; + }, + + clear: function () { + this.$el.html(''); + this.data = []; + this._index = 0; + this._$header = this._$footer = this._$noResultsMessage = null; + }, + + activate: function () { + if (!this.shown) { + this.clear(); + this.$el.show(); + if (this.className) { this.$el.addClass(this.className); } + this.completer.fire('textComplete:show'); + this.shown = true; + } + return this; + }, + + deactivate: function () { + if (this.shown) { + this.$el.hide(); + if (this.className) { this.$el.removeClass(this.className); } + this.completer.fire('textComplete:hide'); + this.shown = false; + } + return this; + }, + + isUp: function (e) { + return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P + }, + + isDown: function (e) { + return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N + }, + + isEnter: function (e) { + var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey; + return !modifiers && (e.keyCode === 13 || e.keyCode === 9 || (this.option.completeOnSpace === true && e.keyCode === 32)) // ENTER, TAB + }, + + isPageup: function (e) { + return e.keyCode === 33; // PAGEUP + }, + + isPagedown: function (e) { + return e.keyCode === 34; // PAGEDOWN + }, + + isEscape: function (e) { + return e.keyCode === 27; // ESCAPE + }, + + // Private properties + // ------------------ + + _data: null, // Currently shown zipped data. + _index: null, + _$header: null, + _$noResultsMessage: null, + _$footer: null, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); + this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); + this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); + this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); + }, + + _onClick: function (e) { + var $el = $(e.target); + e.preventDefault(); + e.originalEvent.keepTextCompleteDropdown = this.id; + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + var datum = this.data[parseInt($el.data('index'), 10)]; + this.completer.select(datum.value, datum.strategy, e); + var self = this; + // Deactive at next tick to allow other event handlers to know whether + // the dropdown has been shown or not. + setTimeout(function () { + self.deactivate(); + if (e.type === 'touchstart') { + self.$inputEl.focus(); + } + }, 0); + }, + + // Activate hovered item. + _onMouseover: function (e) { + var $el = $(e.target); + e.preventDefault(); + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + this._index = parseInt($el.data('index'), 10); + this._activateIndexedItem(); + }, + + _onKeydown: function (e) { + if (!this.shown) { return; } + + var command; + + if ($.isFunction(this.option.onKeydown)) { + command = this.option.onKeydown(e, commands); + } + + if (command == null) { + command = this._defaultKeydown(e); + } + + switch (command) { + case commands.KEY_UP: + e.preventDefault(); + this._up(); + break; + case commands.KEY_DOWN: + e.preventDefault(); + this._down(); + break; + case commands.KEY_ENTER: + e.preventDefault(); + this._enter(e); + break; + case commands.KEY_PAGEUP: + e.preventDefault(); + this._pageup(); + break; + case commands.KEY_PAGEDOWN: + e.preventDefault(); + this._pagedown(); + break; + case commands.KEY_ESCAPE: + e.preventDefault(); + this.deactivate(); + break; + } + }, + + _defaultKeydown: function (e) { + if (this.isUp(e)) { + return commands.KEY_UP; + } else if (this.isDown(e)) { + return commands.KEY_DOWN; + } else if (this.isEnter(e)) { + return commands.KEY_ENTER; + } else if (this.isPageup(e)) { + return commands.KEY_PAGEUP; + } else if (this.isPagedown(e)) { + return commands.KEY_PAGEDOWN; + } else if (this.isEscape(e)) { + return commands.KEY_ESCAPE; + } + }, + + _up: function () { + if (this._index === 0) { + this._index = this.data.length - 1; + } else { + this._index -= 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _down: function () { + if (this._index === this.data.length - 1) { + this._index = 0; + } else { + this._index += 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _enter: function (e) { + var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)]; + this.completer.select(datum.value, datum.strategy, e); + this.deactivate(); + }, + + _pageup: function () { + var target = 0; + var threshold = this._getActiveElement().position().top - this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top + $(this).outerHeight() > threshold) { + target = i; + return false; + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _pagedown: function () { + var target = this.data.length - 1; + var threshold = this._getActiveElement().position().top + this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top > threshold) { + target = i; + return false + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _activateIndexedItem: function () { + this.$el.find('.textcomplete-item.active').removeClass('active'); + this._getActiveElement().addClass('active'); + }, + + _getActiveElement: function () { + return this.$el.children('.textcomplete-item:nth(' + this._index + ')'); + }, + + _setScroll: function () { + var $activeEl = this._getActiveElement(); + var itemTop = $activeEl.position().top; + var itemHeight = $activeEl.outerHeight(); + var visibleHeight = this.$el.innerHeight(); + var visibleTop = this.$el.scrollTop(); + if (this._index === 0 || this._index == this.data.length - 1 || itemTop < 0) { + this.$el.scrollTop(itemTop + visibleTop); + } else if (itemTop + itemHeight > visibleHeight) { + this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight); + } + }, + + _buildContents: function (zippedData) { + var datum, i, index; + var html = ''; + for (i = 0; i < zippedData.length; i++) { + if (this.data.length === this.maxCount) break; + datum = zippedData[i]; + if (include(this.data, datum)) { continue; } + index = this.data.length; + this.data.push(datum); + html += '<li class="textcomplete-item" data-index="' + index + '"><a>'; + html += datum.strategy.template(datum.value, datum.term); + html += '</a></li>'; + } + return html; + }, + + _renderHeader: function (unzippedData) { + if (this.header) { + if (!this._$header) { + this._$header = $('<li class="textcomplete-header"></li>').prependTo(this.$el); + } + var html = $.isFunction(this.header) ? this.header(unzippedData) : this.header; + this._$header.html(html); + } + }, + + _renderFooter: function (unzippedData) { + if (this.footer) { + if (!this._$footer) { + this._$footer = $('<li class="textcomplete-footer"></li>').appendTo(this.$el); + } + var html = $.isFunction(this.footer) ? this.footer(unzippedData) : this.footer; + this._$footer.html(html); + } + }, + + _renderNoResultsMessage: function (unzippedData) { + if (this.noResultsMessage) { + if (!this._$noResultsMessage) { + this._$noResultsMessage = $('<li class="textcomplete-no-results-message"></li>').appendTo(this.$el); + } + var html = $.isFunction(this.noResultsMessage) ? this.noResultsMessage(unzippedData) : this.noResultsMessage; + this._$noResultsMessage.html(html); + } + }, + + _renderContents: function (html) { + if (this._$footer) { + this._$footer.before(html); + } else { + this.$el.append(html); + } + }, + + _fitToBottom: function() { + var windowScrollBottom = $window.scrollTop() + $window.height(); + var height = this.$el.height(); + if ((this.$el.position().top + height) > windowScrollBottom) { + this.$el.offset({top: windowScrollBottom - height}); + } + }, + + _applyPlacement: function (position) { + // If the 'placement' option set to 'top', move the position above the element. + if (this.placement.indexOf('top') !== -1) { + // Overwrite the position object to set the 'bottom' property instead of the top. + position = { + top: 'auto', + bottom: this.$el.parent().height() - position.top + position.lineHeight, + left: position.left + }; + } else { + position.bottom = 'auto'; + delete position.lineHeight; + } + if (this.placement.indexOf('absleft') !== -1) { + position.left = 0; + } else if (this.placement.indexOf('absright') !== -1) { + position.right = 0; + position.left = 'auto'; + } + return position; + } + }); + + $.fn.textcomplete.Dropdown = Dropdown; + $.extend($.fn.textcomplete, commands); +}(jQuery); + ++function ($) { + 'use strict'; + + // Memoize a search function. + var memoize = function (func) { + var memo = {}; + return function (term, callback) { + if (memo[term]) { + callback(memo[term]); + } else { + func.call(this, term, function (data) { + memo[term] = (memo[term] || []).concat(data); + callback.apply(null, arguments); + }); + } + }; + }; + + function Strategy(options) { + $.extend(this, options); + if (this.cache) { this.search = memoize(this.search); } + } + + Strategy.parse = function (strategiesArray, params) { + return $.map(strategiesArray, function (strategy) { + var strategyObj = new Strategy(strategy); + strategyObj.el = params.el; + strategyObj.$el = params.$el; + return strategyObj; + }); + }; + + $.extend(Strategy.prototype, { + // Public properties + // ----------------- + + // Required + match: null, + replace: null, + search: null, + + // Optional + cache: false, + context: function () { return true; }, + index: 2, + template: function (obj) { return obj; }, + idProperty: null + }); + + $.fn.textcomplete.Strategy = Strategy; + +}(jQuery); + ++function ($) { + 'use strict'; + + var now = Date.now || function () { return new Date().getTime(); }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // `wait` msec. + // + // This utility function was originally implemented at Underscore.js. + var debounce = function (func, wait) { + var timeout, args, context, timestamp, result; + var later = function () { + var last = now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + result = func.apply(context, args); + context = args = null; + } + }; + + return function () { + context = this; + args = arguments; + timestamp = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + } + return result; + }; + }; + + function Adapter () {} + + $.extend(Adapter.prototype, { + // Public properties + // ----------------- + + id: null, // Identity. + completer: null, // Completer object which creates it. + el: null, // Textarea element. + $el: null, // jQuery object of the textarea. + option: null, + + // Public methods + // -------------- + + initialize: function (element, completer, option) { + this.el = element; + this.$el = $(element); + this.id = completer.id + this.constructor.name; + this.completer = completer; + this.option = option; + + if (this.option.debounce) { + this._onKeyup = debounce(this._onKeyup, this.option.debounce); + } + + this._bindEvents(); + }, + + destroy: function () { + this.$el.off('.' + this.id); // Remove all event handlers. + this.$el = this.el = this.completer = null; + }, + + // Update the element with the given value and strategy. + // + // value - The selected object. It is one of the item of the array + // which was callbacked from the search function. + // strategy - The Strategy associated with the selected value. + select: function (/* value, strategy */) { + throw new Error('Not implemented'); + }, + + // Returns the caret's relative coordinates from body's left top corner. + // + // FIXME: Calculate the left top corner of `this.option.appendTo` element. + getCaretPosition: function () { + var position = this._getCaretRelativePosition(); + var offset = this.$el.offset(); + position.top += offset.top; + position.left += offset.left; + return position; + }, + + // Focus on the element. + focus: function () { + this.$el.focus(); + }, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this)); + }, + + _onKeyup: function (e) { + if (this._skipSearch(e)) { return; } + this.completer.trigger(this.getTextFromHeadToCaret(), true); + }, + + // Suppress searching if it returns true. + _skipSearch: function (clickEvent) { + switch (clickEvent.keyCode) { + case 13: // ENTER + case 40: // DOWN + case 38: // UP + return true; + } + if (clickEvent.ctrlKey) switch (clickEvent.keyCode) { + case 78: // Ctrl-N + case 80: // Ctrl-P + return true; + } + } + }); + + $.fn.textcomplete.Adapter = Adapter; +}(jQuery); + ++function ($) { + 'use strict'; + + // Textarea adapter + // ================ + // + // Managing a textarea. It doesn't know a Dropdown. + function Textarea(element, completer, option) { + this.initialize(element, completer, option); + } + + Textarea.DIV_PROPERTIES = { + left: -9999, + position: 'absolute', + top: 0, + whiteSpace: 'pre-wrap' + } + + Textarea.COPY_PROPERTIES = [ + 'border-width', 'font-family', 'font-size', 'font-style', 'font-variant', + 'font-weight', 'height', 'letter-spacing', 'word-spacing', 'line-height', + 'text-decoration', 'text-align', 'width', 'padding-top', 'padding-right', + 'padding-bottom', 'padding-left', 'margin-top', 'margin-right', + 'margin-bottom', 'margin-left', 'border-style', 'box-sizing', 'tab-size' + ]; + + $.extend(Textarea.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the textarea with the given value and strategy. + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(this.el.selectionEnd); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.selectionStart = this.el.selectionEnd = pre.length; + } + }, + + // Private methods + // --------------- + + // Returns the caret's relative coordinates from textarea's left top corner. + // + // Browser native API does not provide the way to know the position of + // caret in pixels, so that here we use a kind of hack to accomplish + // the aim. First of all it puts a dummy div element and completely copies + // the textarea's style to the element, then it inserts the text and a + // span element into the textarea. + // Consequently, the span element's position is the thing what we want. + _getCaretRelativePosition: function () { + var dummyDiv = $('<div></div>').css(this._copyCss()) + .text(this.getTextFromHeadToCaret()); + var span = $('<span></span>').text('.').appendTo(dummyDiv); + this.$el.before(dummyDiv); + var position = span.position(); + position.top += span.height() - this.$el.scrollTop(); + position.lineHeight = span.height(); + dummyDiv.remove(); + return position; + }, + + _copyCss: function () { + return $.extend({ + // Set 'scroll' if a scrollbar is being shown; otherwise 'auto'. + overflow: this.el.scrollHeight > this.el.offsetHeight ? 'scroll' : 'auto' + }, Textarea.DIV_PROPERTIES, this._getStyles()); + }, + + _getStyles: (function ($) { + var color = $('<div></div>').css(['color']).color; + if (typeof color !== 'undefined') { + return function () { + return this.$el.css(Textarea.COPY_PROPERTIES); + }; + } else { // jQuery < 1.8 + return function () { + var $el = this.$el; + var styles = {}; + $.each(Textarea.COPY_PROPERTIES, function (i, property) { + styles[property] = $el.css(property); + }); + return styles; + }; + } + })($), + + getTextFromHeadToCaret: function () { + return this.el.value.substring(0, this.el.selectionEnd); + } + }); + + $.fn.textcomplete.Textarea = Textarea; +}(jQuery); + ++function ($) { + 'use strict'; + + var sentinelChar = '吶'; + + function IETextarea(element, completer, option) { + this.initialize(element, completer, option); + $('<span>' + sentinelChar + '</span>').css({ + position: 'absolute', + top: -9999, + left: -9999 + }).insertBefore(element); + } + + $.extend(IETextarea.prototype, $.fn.textcomplete.Textarea.prototype, { + // Public methods + // -------------- + + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(pre.length); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.focus(); + var range = this.el.createTextRange(); + range.collapse(true); + range.moveEnd('character', pre.length); + range.moveStart('character', pre.length); + range.select(); + } + }, + + getTextFromHeadToCaret: function () { + this.el.focus(); + var range = document.selection.createRange(); + range.moveStart('character', -this.el.value.length); + var arr = range.text.split(sentinelChar) + return arr.length === 1 ? arr[0] : arr[1]; + } + }); + + $.fn.textcomplete.IETextarea = IETextarea; +}(jQuery); + +// NOTE: TextComplete plugin has contenteditable support but it does not work +// fine especially on old IEs. +// Any pull requests are REALLY welcome. + ++function ($) { + 'use strict'; + + // ContentEditable adapter + // ======================= + // + // Adapter for contenteditable elements. + function ContentEditable (element, completer, option) { + this.initialize(element, completer, option); + } + + $.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the content with the given value and strategy. + // When an dropdown item is selected, it is executed. + select: function (value, strategy, e) { + var pre = this.getTextFromHeadToCaret(); + var sel = window.getSelection() + var range = sel.getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + var content = selection.toString(); + var post = content.substring(range.startOffset); + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + range.selectNodeContents(range.startContainer); + range.deleteContents(); + var node = document.createTextNode(pre + post); + range.insertNode(node); + range.setStart(node, pre.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + }, + + // Private methods + // --------------- + + // Returns the caret's relative position from the contenteditable's + // left top corner. + // + // Examples + // + // this._getCaretRelativePosition() + // //=> { top: 18, left: 200, lineHeight: 16 } + // + // Dropdown's position will be decided using the result. + _getCaretRelativePosition: function () { + var range = window.getSelection().getRangeAt(0).cloneRange(); + var node = document.createElement('span'); + range.insertNode(node); + range.selectNodeContents(node); + range.deleteContents(); + var $node = $(node); + var position = $node.offset(); + position.left -= this.$el.offset().left; + position.top += $node.height() - this.$el.offset().top; + position.lineHeight = $node.height(); + $node.remove(); + return position; + }, + + // Returns the string between the first character and the caret. + // Completer will be triggered with the result for start autocompleting. + // + // Example + // + // // Suppose the html is '<b>hello</b> wor|ld' and | is the caret. + // this.getTextFromHeadToCaret() + // // => ' wor' // not '<b>hello</b> wor' + getTextFromHeadToCaret: function () { + var range = window.getSelection().getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + return selection.toString().substring(0, range.startOffset); + } + }); + + $.fn.textcomplete.ContentEditable = ContentEditable; +}(jQuery); + +return jQuery; +})); diff --git a/composer.json b/composer.json index 06637abc..69840ec9 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "fguillot/kanboard", + "type": "project", "description": "Kanban project management software", "license": "MIT", "authors": [ @@ -7,6 +8,11 @@ "name": "Frédéric Guillot" } ], + "config": { + "preferred-install": "dist", + "optimize-autoloader": true, + "discard-changes": true + }, "require" : { "php" : ">=5.3", "ext-mbstring" : "*", @@ -14,9 +20,8 @@ "christian-riesen/otp" : "1.4", "eluceo/ical": "0.8.0", "erusev/parsedown" : "1.6.0", - "fabiang/xmpp" : "0.6.1", "fguillot/json-rpc" : "1.0.3", - "fguillot/picodb" : "1.0.3", + "fguillot/picodb" : "dev-master", "fguillot/simpleLogger" : "1.0.0", "fguillot/simple-validator" : "1.0.0", "league/html-to-markdown" : "~4.0", diff --git a/composer.lock b/composer.lock index 4af69a06..76bc3d11 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "a003844ed3fef854d64490d07064c66f", - "content-hash": "2189f3f4d11741a635dbd27a5750b71e", + "hash": "8230f229ff6260e337d500aea1b42429", + "content-hash": "87b8db514c808c0775bd52ad72c1a71d", "packages": [ { "name": "christian-riesen/base32", @@ -202,64 +202,6 @@ "time": "2015-10-04 16:44:32" }, { - "name": "fabiang/xmpp", - "version": "0.6.1", - "source": { - "type": "git", - "url": "https://github.com/fabiang/xmpp.git", - "reference": "47fdbe4a60ef0e726c4aaf39d6eb57afd42915c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fabiang/xmpp/zipball/47fdbe4a60ef0e726c4aaf39d6eb57afd42915c8", - "reference": "47fdbe4a60ef0e726c4aaf39d6eb57afd42915c8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "psr/log": "~1.0" - }, - "require-dev": { - "behat/behat": "~2.5", - "monolog/monolog": "~1.11", - "phpunit/phpunit": "~4.3", - "satooshi/php-coveralls": "~0.6" - }, - "suggest": { - "psr/log-implementation": "Allows more advanced logging of the xmpp connection" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Fabiang\\Xmpp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Fabian Grutschus", - "email": "f.grutschus@lubyte.de", - "homepage": "http://www.lubyte.de/", - "role": "developer" - } - ], - "description": "Library for XMPP protocol (Jabber) connections", - "homepage": "https://github.com/fabiang/xmpp", - "keywords": [ - "jabber", - "xmpp" - ], - "time": "2014-11-20 08:59:24" - }, - { "name": "fguillot/json-rpc", "version": "v1.0.3", "source": { @@ -297,16 +239,16 @@ }, { "name": "fguillot/picodb", - "version": "v1.0.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/fguillot/picoDb.git", - "reference": "0b4640ebe531a89d2faa36dfabe30a194f1fe903" + "reference": "9ed4ee0c412dc9259d45bbc52e55c74150f7fb99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fguillot/picoDb/zipball/0b4640ebe531a89d2faa36dfabe30a194f1fe903", - "reference": "0b4640ebe531a89d2faa36dfabe30a194f1fe903", + "url": "https://api.github.com/repos/fguillot/picoDb/zipball/9ed4ee0c412dc9259d45bbc52e55c74150f7fb99", + "reference": "9ed4ee0c412dc9259d45bbc52e55c74150f7fb99", "shasum": "" }, "require": { @@ -330,7 +272,7 @@ ], "description": "Minimalist database query builder", "homepage": "https://github.com/fguillot/picoDb", - "time": "2015-11-30 02:57:18" + "time": "2015-12-24 11:39:04" }, { "name": "fguillot/simple-validator", @@ -656,16 +598,16 @@ }, { "name": "symfony/console", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41" + "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", - "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", + "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a", + "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a", "shasum": "" }, "require": { @@ -712,11 +654,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-11-30 12:35:10" + "time": "2015-12-22 10:25:57" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -776,21 +718,24 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.0.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0b6a8940385311a24e060ec1fe35680e17c74497" + "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497", - "reference": "0b6a8940385311a24e060ec1fe35680e17c74497", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", + "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "branch-alias": { @@ -828,13 +773,13 @@ "portable", "shim" ], - "time": "2015-11-04 20:28:58" + "time": "2015-11-20 09:19:13" } ], "packages-dev": [ { "name": "symfony/stopwatch", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -884,7 +829,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "fguillot/picodb": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/doc/ubuntu-installation.markdown b/doc/ubuntu-installation.markdown index a08ccc20..cec3ebba 100644 --- a/doc/ubuntu-installation.markdown +++ b/doc/ubuntu-installation.markdown @@ -1,17 +1,17 @@ How to install Kanboard on Ubuntu? ================================== -Ubuntu 14.04 LTS ----------------- +Ubuntu Trusty 14.04 LTS +----------------------- Install Apache and PHP: ```bash sudo apt-get update -sudo apt-get install -y php5 php5-sqlite unzip +sudo apt-get install -y php5 php5-sqlite php5-gd php5-json php5-mcrypt unzip ``` -In case your web server was running restart to make sure the php modules are reloaded +In case your web server was running restart to make sure the php modules are reloaded: ```bash service apache2 restart diff --git a/tests/units/Model/ProjectPermissionTest.php b/tests/units/Model/ProjectPermissionTest.php index 77419bec..035a1246 100644 --- a/tests/units/Model/ProjectPermissionTest.php +++ b/tests/units/Model/ProjectPermissionTest.php @@ -13,6 +13,33 @@ use Kanboard\Core\Security\Role; class ProjectPermissionTest extends Base { + public function testFindByUsernames() + { + $userModel = new User($this->container); + $projectModel = new Project($this->container); + $groupModel = new Group($this->container); + $groupMemberModel = new GroupMember($this->container); + $groupRoleModel = new ProjectGroupRole($this->container); + $userRoleModel = new ProjectUserRole($this->container); + $projectPermissionModel = new ProjectPermission($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project 1'))); + + $this->assertEquals(2, $userModel->create(array('username' => 'user1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user2'))); + $this->assertEquals(4, $userModel->create(array('username' => 'user3'))); + + $this->assertEquals(1, $groupModel->create('Group A')); + $this->assertTrue($groupMemberModel->addUser(1, 2)); + + $this->assertTrue($groupRoleModel->addGroup(1, 1, Role::PROJECT_MEMBER)); + $this->assertTrue($userRoleModel->addUser(1, 3, Role::PROJECT_MANAGER)); + + $this->assertEquals(array('user1', 'user2'), $projectPermissionModel->findUsernames(1, 'us')); + $this->assertEmpty($projectPermissionModel->findUsernames(1, 'a')); + $this->assertEmpty($projectPermissionModel->findUsernames(2, 'user')); + } + public function testGetQueryByRole() { $userModel = new User($this->container); @@ -128,6 +155,43 @@ class ProjectPermissionTest extends Base $this->assertFalse($projectPermission->isUserAllowed(2, 5)); } + public function testIsAssignable() + { + $userModel = new User($this->container); + $projectModel = new Project($this->container); + $groupModel = new Group($this->container); + $groupRoleModel = new ProjectGroupRole($this->container); + $groupMemberModel = new GroupMember($this->container); + $userRoleModel = new ProjectUserRole($this->container); + $projectPermission = new ProjectPermission($this->container); + + $this->assertEquals(2, $userModel->create(array('username' => 'user 1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user 2'))); + $this->assertEquals(4, $userModel->create(array('username' => 'user 3'))); + $this->assertEquals(5, $userModel->create(array('username' => 'user 4'))); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project 1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project 2'))); + + $this->assertEquals(1, $groupModel->create('Group A')); + + $this->assertTrue($groupMemberModel->addUser(1, 2)); + $this->assertTrue($groupRoleModel->addGroup(1, 1, Role::PROJECT_VIEWER)); + + $this->assertTrue($userRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); + $this->assertTrue($userRoleModel->addUser(1, 4, Role::PROJECT_MANAGER)); + + $this->assertFalse($projectPermission->isAssignable(1, 2)); + $this->assertTrue($projectPermission->isAssignable(1, 3)); + $this->assertTrue($projectPermission->isAssignable(1, 4)); + $this->assertFalse($projectPermission->isAssignable(1, 5)); + + $this->assertFalse($projectPermission->isAssignable(2, 2)); + $this->assertFalse($projectPermission->isAssignable(2, 3)); + $this->assertFalse($projectPermission->isAssignable(2, 4)); + $this->assertFalse($projectPermission->isAssignable(2, 5)); + } + public function testIsMember() { $userModel = new User($this->container); @@ -154,7 +218,7 @@ class ProjectPermissionTest extends Base $this->assertTrue($userRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); $this->assertTrue($userRoleModel->addUser(1, 4, Role::PROJECT_MANAGER)); - $this->assertFalse($projectPermission->isMember(1, 2)); + $this->assertTrue($projectPermission->isMember(1, 2)); $this->assertTrue($projectPermission->isMember(1, 3)); $this->assertTrue($projectPermission->isMember(1, 4)); $this->assertFalse($projectPermission->isMember(1, 5)); diff --git a/tests/units/Model/TaskPositionTest.php b/tests/units/Model/TaskPositionTest.php index 42612f44..5f045768 100644 --- a/tests/units/Model/TaskPositionTest.php +++ b/tests/units/Model/TaskPositionTest.php @@ -106,7 +106,7 @@ class TaskPositionTest extends Base $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); $this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); - // We move the task 2 to the column 3 + // We move the task 1 to the column 3 $this->assertTrue($tp->movePosition(1, 1, 3, 1)); // Check tasks position @@ -235,7 +235,7 @@ class TaskPositionTest extends Base $this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); $this->assertEquals(4, $tc->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); - // Move the last task to the bottom + // Move the first task to the bottom $this->assertTrue($tp->movePosition(1, 1, 1, 4)); // Check tasks position diff --git a/tests/units/Model/UserMentionTest.php b/tests/units/Model/UserMentionTest.php new file mode 100644 index 00000000..d50c9285 --- /dev/null +++ b/tests/units/Model/UserMentionTest.php @@ -0,0 +1,114 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\Core\Security\Role; +use Kanboard\Event\GenericEvent; +use Kanboard\Model\User; +use Kanboard\Model\Task; +use Kanboard\Model\TaskCreation; +use Kanboard\Model\Project; +use Kanboard\Model\ProjectUserRole; +use Kanboard\Model\UserMention; + +class UserMentionTest extends Base +{ + public function testGetMentionedUsersWithNoMentions() + { + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + + $this->assertNotFalse($userModel->create(array('username' => 'user1'))); + $this->assertEmpty($userMentionModel->getMentionedUsers('test')); + } + + public function testGetMentionedUsersWithNotficationDisabled() + { + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + + $this->assertNotFalse($userModel->create(array('username' => 'user1'))); + $this->assertEmpty($userMentionModel->getMentionedUsers('test @user1')); + } + + public function testGetMentionedUsersWithNotficationEnabled() + { + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + + $this->assertNotFalse($userModel->create(array('username' => 'user1'))); + $this->assertNotFalse($userModel->create(array('username' => 'user2', 'name' => 'Foobar', 'notifications_enabled' => 1))); + + $users = $userMentionModel->getMentionedUsers('test @user2'); + $this->assertCount(1, $users); + $this->assertEquals('user2', $users[0]['username']); + $this->assertEquals('Foobar', $users[0]['name']); + $this->assertEquals('', $users[0]['email']); + $this->assertEquals('', $users[0]['language']); + } + + public function testGetMentionedUsersWithNotficationEnabledAndUserLoggedIn() + { + $this->container['sessionStorage']->user = array('id' => 3); + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + + $this->assertNotFalse($userModel->create(array('username' => 'user1'))); + $this->assertNotFalse($userModel->create(array('username' => 'user2', 'name' => 'Foobar', 'notifications_enabled' => 1))); + + $this->assertEmpty($userMentionModel->getMentionedUsers('test @user2')); + } + + public function testFireEventsWithMultipleMentions() + { + $projectUserRoleModel = new ProjectUserRole($this->container); + $projectModel = new Project($this->container); + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + $event = new GenericEvent(array('project_id' => 1)); + + $this->assertEquals(2, $userModel->create(array('username' => 'user1', 'name' => 'User 1', 'notifications_enabled' => 1))); + $this->assertEquals(3, $userModel->create(array('username' => 'user2', 'name' => 'User 2', 'notifications_enabled' => 1))); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertTrue($projectUserRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); + + $this->container['dispatcher']->addListener(Task::EVENT_USER_MENTION, array($this, 'onUserMention')); + + $userMentionModel->fireEvents('test @user1 @user2', Task::EVENT_USER_MENTION, $event); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(Task::EVENT_USER_MENTION.'.UserMentionTest::onUserMention', $called); + } + + public function testFireEventsWithNoProjectId() + { + $projectUserRoleModel = new ProjectUserRole($this->container); + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $userModel = new User($this->container); + $userMentionModel = new UserMention($this->container); + $event = new GenericEvent(array('task_id' => 1)); + + $this->assertEquals(2, $userModel->create(array('username' => 'user1', 'name' => 'User 1', 'notifications_enabled' => 1))); + $this->assertEquals(3, $userModel->create(array('username' => 'user2', 'name' => 'User 2', 'notifications_enabled' => 1))); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertTrue($projectUserRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); + + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'Task 1'))); + + $this->container['dispatcher']->addListener(Task::EVENT_USER_MENTION, array($this, 'onUserMention')); + + $userMentionModel->fireEvents('test @user1 @user2', Task::EVENT_USER_MENTION, $event); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(Task::EVENT_USER_MENTION.'.UserMentionTest::onUserMention', $called); + } + + public function onUserMention($event) + { + $this->assertInstanceOf('Kanboard\Event\GenericEvent', $event); + $this->assertEquals(array('id' => '3', 'username' => 'user2', 'name' => 'User 2', 'email' => null, 'language' => null), $event['mention']); + } +} |