Implement mention support for user groups
authorAlexander Ebert <ebert@woltlab.com>
Fri, 28 Apr 2023 12:24:55 +0000 (14:24 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 28 Apr 2023 12:24:55 +0000 (14:24 +0200)
Fixes #5439

ts/WoltLabSuite/Core/Component/Ckeditor/Mention.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Ckeditor/Mention.js
wcfsetup/install/files/lib/action/EditorGetMentionSuggestionsAction.class.php

index 6dd999b309fc828b66e680298a4c55965e507550..f3628fb20090c6431ab426dbbcc0a727a38afa9d 100644 (file)
@@ -13,21 +13,27 @@ import { createFragmentFromHtml } from "../../Dom/Util";
 import { listenToCkeditor } from "./Event";
 import { EditorConfig } from "./Types";
 
-type SearchResultItem = {
-  avatarTag: string;
-  username: string;
-  userID: number;
-  type: "user";
-};
+type SearchResultItem =
+  | {
+      avatarTag: string;
+      username: string;
+      userID: number;
+      type: "user";
+    }
+  | {
+      name: string;
+      groupID: string;
+      type: "group";
+    };
 type ResultGetSearchResultList = SearchResultItem[];
 
-type UserMention = {
+type Mention = {
   id: string;
   text: string;
   icon: string;
 };
 
-async function getPossibleMentions(query: string): Promise<UserMention[]> {
+async function getPossibleMentions(query: string): Promise<Mention[]> {
   // TODO: Provide the URL as a parameter.
   const url = new URL(window.WSC_API_URL + "index.php?editor-get-mention-suggestions/");
   url.searchParams.set("query", query);
@@ -39,13 +45,23 @@ async function getPossibleMentions(query: string): Promise<UserMention[]> {
     .fetchAsJson()) as ResultGetSearchResultList;
 
   return result.map((item) => {
-    return {
-      id: `@${item.username}`,
-      text: item.username,
-      icon: item.avatarTag,
-      objectId: item.userID,
-      type: item.type,
-    };
+    if (item.type === "user") {
+      return {
+        id: `@${item.username}`,
+        text: item.username,
+        icon: item.avatarTag,
+        objectId: item.userID,
+        type: item.type,
+      };
+    } else {
+      return {
+        id: `@${item.name}`,
+        text: item.name,
+        icon: '<fa-icon name="users"></fa-icon>',
+        objectId: item.groupID,
+        type: item.type,
+      };
+    }
   });
 }
 
index 0ee189c1f30c3aedcb6db1d0c1e68af5779d83f3..2213c9e72398e0d9424bdfbc5f0b79cf6a930f1d 100644 (file)
@@ -20,13 +20,24 @@ define(["require", "exports", "../../Ajax/Backend", "../../Dom/Util", "./Event"]
             .disableLoadingIndicator()
             .fetchAsJson());
         return result.map((item) => {
-            return {
-                id: `@${item.username}`,
-                text: item.username,
-                icon: item.avatarTag,
-                objectId: item.userID,
-                type: item.type,
-            };
+            if (item.type === "user") {
+                return {
+                    id: `@${item.username}`,
+                    text: item.username,
+                    icon: item.avatarTag,
+                    objectId: item.userID,
+                    type: item.type,
+                };
+            }
+            else {
+                return {
+                    id: `@${item.name}`,
+                    text: item.name,
+                    icon: '<fa-icon name="users"></fa-icon>',
+                    objectId: item.groupID,
+                    type: item.type,
+                };
+            }
         });
     }
     function getMentionConfiguration() {
index ed10bbe3c9675b41a3c6829e6cdb67db38a9ee71..45630b0bc7f2fcc6ffa45505e4aa940876891222 100644 (file)
@@ -6,6 +6,7 @@ use Laminas\Diactoros\Response\JsonResponse;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use wcf\data\user\group\UserGroup;
 use wcf\data\user\UserProfile;
 use wcf\data\user\UserProfileList;
 use wcf\http\Helper;
@@ -31,20 +32,28 @@ final class EditorGetMentionSuggestionsAction implements RequestHandlerInterface
                     EOT,
         );
 
-        $users = $this->getUsers($parameters);
+        $query = \mb_strtolower($parameters['query']);
+        $matches = [];
 
-        // TODO: Groups
+        foreach ($this->getGroups($query) as $userGroup) {
+            $matches[] = [
+                'name' => $userGroup->getName(),
+                'groupID' => $userGroup->groupID,
+                'type' => 'group',
+            ];
+        }
+
+        foreach ($this->getUsers($query) as $userProfile) {
+            $matches[] = [
+                'avatarTag' => $userProfile->getAvatar()->getImageTag(16),
+                'username' => $userProfile->getUsername(),
+                'userID' => $userProfile->getObjectID(),
+                'type' => 'user',
+            ];
+        }
 
         return new JsonResponse(
-            \array_map(
-                static fn (UserProfile $userProfile) => [
-                    'avatarTag' => $userProfile->getAvatar()->getImageTag(16),
-                    'username' => $userProfile->getUsername(),
-                    'userID' => $userProfile->getObjectID(),
-                    'type' => 'user',
-                ],
-                $users
-            ),
+            $matches,
             200,
             [
                 'cache-control' => [
@@ -57,14 +66,35 @@ final class EditorGetMentionSuggestionsAction implements RequestHandlerInterface
     /**
      * @return list<UserProfile>
      */
-    private function getUsers(array $parameters): array
+    private function getUsers(string $query): array
     {
         $userProfileList = new UserProfileList();
-        $userProfileList->getConditionBuilder()->add("username LIKE ?", [$parameters['query'] . '%']);
+        $userProfileList->getConditionBuilder()->add("username LIKE ?", [$query . '%']);
 
         $userProfileList->sqlLimit = 10;
         $userProfileList->readObjects();
 
         return \array_values($userProfileList->getObjects());
     }
+
+    /**
+     * @return list<UserGroup>
+     */
+    private function getGroups(string $query): array
+    {
+        $userGroups = UserGroup::getMentionableGroups();
+        if ($userGroups === []) {
+            return [];
+        }
+
+        $userGroups = \array_filter($userGroups, static function (UserGroup $userGroup) use ($query) {
+            return \str_starts_with(\mb_strtolower($userGroup->getName()), $query);
+        });
+
+        \usort($userGroups, static function (UserGroup $a, UserGroup $b) {
+            return \mb_strtolower($a->getName()) <=> \mb_strtolower($b->getName());
+        });
+
+        return $userGroups;
+    }
 }