From d8b0423d152ca27682b001f2c4d386d9c5dd361e Mon Sep 17 00:00:00 2001
From: Frederic Guillot <fred@kanboard.net>
Date: Sun, 27 Nov 2016 15:44:45 -0500
Subject: Add suggest menu for user mentions in text editor

---
 app/Controller/UserAjaxController.php       |  8 +++-
 app/Core/Helper.php                         |  1 +
 app/Formatter/BaseFormatter.php             | 14 -------
 app/Formatter/BaseTaskCalendarFormatter.php |  4 +-
 app/Formatter/UserMentionFormatter.php      | 60 +++++++++++++++++++++++++++++
 app/Helper/FormHelper.php                   |  4 ++
 app/Model/ProjectPermissionModel.php        | 24 ++++++++++--
 app/functions.php                           | 31 +++++++++++++++
 8 files changed, 124 insertions(+), 22 deletions(-)
 create mode 100644 app/Formatter/UserMentionFormatter.php

(limited to 'app')

diff --git a/app/Controller/UserAjaxController.php b/app/Controller/UserAjaxController.php
index ed180471..0e654333 100644
--- a/app/Controller/UserAjaxController.php
+++ b/app/Controller/UserAjaxController.php
@@ -4,6 +4,7 @@ namespace Kanboard\Controller;
 
 use Kanboard\Filter\UserNameFilter;
 use Kanboard\Formatter\UserAutoCompleteFormatter;
+use Kanboard\Formatter\UserMentionFormatter;
 use Kanboard\Model\UserModel;
 
 /**
@@ -37,7 +38,12 @@ class UserAjaxController extends BaseController
         $project_id = $this->request->getStringParam('project_id');
         $query = $this->request->getStringParam('q');
         $users = $this->projectPermissionModel->findUsernames($project_id, $query);
-        $this->response->json($users);
+
+        $this->response->json(
+            UserMentionFormatter::getInstance($this->container)
+                ->withUsers($users)
+                ->format()
+        );
     }
 
     /**
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
index b5c560af..9660c348 100644
--- a/app/Core/Helper.php
+++ b/app/Core/Helper.php
@@ -12,6 +12,7 @@ use Pimple\Container;
  *
  * @property \Kanboard\Helper\AppHelper               $app
  * @property \Kanboard\Helper\AssetHelper             $asset
+ * @property \Kanboard\Helper\AvatarHelper            $avatar
  * @property \Kanboard\Helper\BoardHelper             $board
  * @property \Kanboard\Helper\CalendarHelper          $calendar
  * @property \Kanboard\Helper\DateHelper              $dt
diff --git a/app/Formatter/BaseFormatter.php b/app/Formatter/BaseFormatter.php
index 89c48437..0d62628e 100644
--- a/app/Formatter/BaseFormatter.php
+++ b/app/Formatter/BaseFormatter.php
@@ -4,7 +4,6 @@ namespace Kanboard\Formatter;
 
 use Kanboard\Core\Base;
 use PicoDb\Table;
-use Pimple\Container;
 
 /**
  * Class BaseFormatter
@@ -22,19 +21,6 @@ abstract class BaseFormatter extends Base
      */
     protected $query;
 
-    /**
-     * Get object instance
-     *
-     * @static
-     * @access public
-     * @param  Container $container
-     * @return static
-     */
-    public static function getInstance(Container $container)
-    {
-        return new static($container);
-    }
-
     /**
      * Set query
      *
diff --git a/app/Formatter/BaseTaskCalendarFormatter.php b/app/Formatter/BaseTaskCalendarFormatter.php
index 8fab3e9a..3d9ead4d 100644
--- a/app/Formatter/BaseTaskCalendarFormatter.php
+++ b/app/Formatter/BaseTaskCalendarFormatter.php
@@ -2,8 +2,6 @@
 
 namespace Kanboard\Formatter;
 
-use Kanboard\Core\Filter\FormatterInterface;
-
 /**
  * Common class to handle calendar events
  *
@@ -34,7 +32,7 @@ abstract class BaseTaskCalendarFormatter extends BaseFormatter
      * @access public
      * @param  string  $start_column    Column name for the start date
      * @param  string  $end_column      Column name for the end date
-     * @return FormatterInterface
+     * @return $this
      */
     public function setColumns($start_column, $end_column = '')
     {
diff --git a/app/Formatter/UserMentionFormatter.php b/app/Formatter/UserMentionFormatter.php
new file mode 100644
index 00000000..395fc463
--- /dev/null
+++ b/app/Formatter/UserMentionFormatter.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Kanboard\Formatter;
+
+/**
+ * Class UserMentionFormatter
+ *
+ * @package Kanboard\Formatter
+ * @author  Frederic Guillot
+ */
+class UserMentionFormatter extends BaseFormatter
+{
+    protected $users = array();
+
+    /**
+     * Set users
+     *
+     * @param array $users
+     * @return $this
+     */
+    public function withUsers(array $users) {
+        $this->users = $users;
+        return $this;
+    }
+
+    /**
+     * Apply formatter
+     *
+     * @access public
+     * @return array
+     */
+    public function format()
+    {
+        $result = array();
+
+        foreach ($this->users as $user) {
+            $html = $this->helper->avatar->small(
+                $user['id'],
+                $user['username'],
+                $user['name'],
+                $user['email'],
+                $user['avatar_path'],
+                'avatar-inline'
+            );
+
+            $html .= ' '.$this->helper->text->e($user['username']);
+
+            if (! empty($user['name'])) {
+                $html .= ' <small>'.$this->helper->text->e($user['name']).'</small>';
+            }
+
+            $result[] = array(
+                'value' => $user['username'],
+                'html' => $html,
+            );
+        }
+
+        return $result;
+    }
+}
\ No newline at end of file
diff --git a/app/Helper/FormHelper.php b/app/Helper/FormHelper.php
index 629de9ff..e44c5d06 100644
--- a/app/Helper/FormHelper.php
+++ b/app/Helper/FormHelper.php
@@ -204,6 +204,10 @@ class FormHelper extends Base
             'placeholder' => t('Write your text in Markdown'),
         );
 
+        if (isset($values['project_id'])) {
+            $params['mentionUrl'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id']));
+        }
+
         $html = '<div class="js-text-editor" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>';
         $html .= $this->errorList($errors, $name);
 
diff --git a/app/Model/ProjectPermissionModel.php b/app/Model/ProjectPermissionModel.php
index 25b6a382..dabd406c 100644
--- a/app/Model/ProjectPermissionModel.php
+++ b/app/Model/ProjectPermissionModel.php
@@ -62,17 +62,33 @@ class ProjectPermissionModel extends Base
             ->withFilter(new ProjectUserRoleProjectFilter($project_id))
             ->withFilter(new ProjectUserRoleUsernameFilter($input))
             ->getQuery()
-            ->findAllByColumn('username');
+            ->columns(
+                UserModel::TABLE.'.id',
+                UserModel::TABLE.'.username',
+                UserModel::TABLE.'.name',
+                UserModel::TABLE.'.email',
+                UserModel::TABLE.'.avatar_path'
+            )
+            ->findAll();
 
         $groupMembers = $this->projectGroupRoleQuery
             ->withFilter(new ProjectGroupRoleProjectFilter($project_id))
             ->withFilter(new ProjectGroupRoleUsernameFilter($input))
             ->getQuery()
-            ->findAllByColumn('username');
+            ->columns(
+                UserModel::TABLE.'.id',
+                UserModel::TABLE.'.username',
+                UserModel::TABLE.'.name',
+                UserModel::TABLE.'.email',
+                UserModel::TABLE.'.avatar_path'
+            )
+            ->findAll();
 
-        $members = array_unique(array_merge($userMembers, $groupMembers));
+        $userMembers = array_column_index_unique($userMembers, 'username');
+        $groupMembers = array_column_index_unique($groupMembers, 'username');
+        $members = array_merge($userMembers, $groupMembers);
 
-        sort($members);
+        ksort($members);
 
         return $members;
     }
diff --git a/app/functions.php b/app/functions.php
index 8f0d482c..e732f308 100644
--- a/app/functions.php
+++ b/app/functions.php
@@ -52,6 +52,37 @@ function array_column_index(array &$input, $column)
     return $result;
 }
 
+/**
+ * Create indexed array from a list of dict with unique values
+ *
+ * $input = [
+ *   ['k1' => 1, 'k2' => 2], ['k1' => 3, 'k2' => 4], ['k1' => 1, 'k2' => 5]
+ * ]
+ *
+ * array_column_index_unique($input, 'k1') will returns:
+ *
+ * [
+ *   1 => ['k1' => 1, 'k2' => 2],
+ *   3 => ['k1' => 3, 'k2' => 4],
+ * ]
+ *
+ * @param  array   $input
+ * @param  string  $column
+ * @return array
+ */
+function array_column_index_unique(array &$input, $column)
+{
+    $result = array();
+
+    foreach ($input as &$row) {
+        if (isset($row[$column]) && ! isset($result[$row[$column]])) {
+            $result[$row[$column]] = $row;
+        }
+    }
+
+    return $result;
+}
+
 /**
  * Sum all values from a single column in the input array
  *
-- 
cgit v1.2.3