From: Cyperghost Date: Thu, 19 Dec 2024 10:56:38 +0000 (+0100) Subject: Store information about the message author X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=b828e9b6d83007e7243cae4c0da869f663fe4db6;p=GitHub%2FWoltLab%2FWCF.git Store information about the message author --- diff --git a/ts/WoltLabSuite/Core/Api/Messages/Author.ts b/ts/WoltLabSuite/Core/Api/Messages/Author.ts new file mode 100644 index 0000000000..8a850bc06f --- /dev/null +++ b/ts/WoltLabSuite/Core/Api/Messages/Author.ts @@ -0,0 +1,36 @@ +/** + * Requests render a full quote of a message. + * + * @author Olaf Braun + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 + * @woltlabExcludeBundle tiny + */ + +import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; +import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result"; + +type Response = { + objectID: number; + authorID: number; + author: string; + time: number; + link: string; + avatar: string; +}; + +export async function messageAuthor(className: string, objectID: number): Promise> { + const url = new URL(window.WSC_RPC_API_URL + "core/messages/messageauthor"); + url.searchParams.set("className", className); + url.searchParams.set("objectID", objectID.toString()); + + let response: Response; + try { + response = (await prepareRequest(url).get().allowCaching().fetchAsJson()) as Response; + } catch (e) { + return apiResultFromError(e); + } + + return apiResultFromValue(response); +} diff --git a/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts b/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts index 8f389ff930..11fda558e1 100644 --- a/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts +++ b/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts @@ -11,11 +11,25 @@ import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result"; -type Response = string; +type Response = { + objectID: number; + authorID: number; + author: string; + time: number; + link: string; + avatar: string; + message: string; +}; -export async function renderQuote(objectType: string, objectID: number): Promise> { +export async function renderQuote( + objectType: string, + className: string, + objectID: number, +): Promise> { const url = new URL(window.WSC_RPC_API_URL + "core/messages/renderquote"); url.searchParams.set("objectType", objectType); + url.searchParams.set("className", className); + url.searchParams.set("fullQuote", "true"); url.searchParams.set("objectID", objectID.toString()); let response: Response; diff --git a/ts/WoltLabSuite/Core/Component/Quote/Message.ts b/ts/WoltLabSuite/Core/Component/Quote/Message.ts index b0297ab22b..92fb91042c 100644 --- a/ts/WoltLabSuite/Core/Component/Quote/Message.ts +++ b/ts/WoltLabSuite/Core/Component/Quote/Message.ts @@ -20,6 +20,7 @@ interface Container { element: HTMLElement; messageBodySelector: string; objectType: string; + className: string; objectId: number; } @@ -37,13 +38,19 @@ let timerSelectionChange: number | undefined = undefined; let isMouseDown = false; const copyQuote = document.createElement("div"); -export function registerContainer(containerSelector: string, messageBodySelector: string, objectType: string): void { +export function registerContainer( + containerSelector: string, + messageBodySelector: string, + className: string, + objectType: string, +): void { wheneverFirstSeen(containerSelector, (container: HTMLElement) => { const id = DomUtil.identify(container); containers.set(id, { element: container, messageBodySelector: messageBodySelector, objectType: objectType, + className: className, objectId: ~~container.dataset.objectId!, }); @@ -59,7 +66,7 @@ export function registerContainer(containerSelector: string, messageBodySelector promiseMutex(async (event: MouseEvent) => { event.preventDefault(); - await saveFullQuote(objectType, ~~container.dataset.objectId!); + await saveFullQuote(objectType, className, ~~container.dataset.objectId!); //TODO insert into `activeEditor` }), ); @@ -79,23 +86,39 @@ function setup() { buttonSaveQuote.type = "button"; buttonSaveQuote.classList.add("jsQuoteManagerStore"); buttonSaveQuote.textContent = getPhrase("wcf.message.quote.quoteSelected"); - buttonSaveQuote.addEventListener("click", () => { - saveQuote(selectedMessage!.container.objectType, selectedMessage!.container.objectId, selectedMessage!.message); - - removeSelection(); - }); + buttonSaveQuote.addEventListener( + "click", + promiseMutex(async () => { + await saveQuote( + selectedMessage!.container.objectType, + selectedMessage!.container.objectId, + selectedMessage!.container.className, + selectedMessage!.message, + ); + + removeSelection(); + }), + ); copyQuote.appendChild(buttonSaveQuote); const buttonSaveAndInsertQuote = document.createElement("button"); buttonSaveAndInsertQuote.type = "button"; buttonSaveAndInsertQuote.hidden = true; buttonSaveAndInsertQuote.classList.add("jsQuoteManagerQuoteAndInsert"); buttonSaveAndInsertQuote.textContent = getPhrase("wcf.message.quote.quoteAndReply"); - buttonSaveAndInsertQuote.addEventListener("click", () => { - saveQuote(selectedMessage!.container.objectType, selectedMessage!.container.objectId, selectedMessage!.message); - //TODO insert into `activeEditor` - - removeSelection(); - }); + buttonSaveAndInsertQuote.addEventListener( + "click", + promiseMutex(async () => { + await saveQuote( + selectedMessage!.container.objectType, + selectedMessage!.container.objectId, + selectedMessage!.container.className, + selectedMessage!.message, + ); + //TODO insert into `activeEditor` + + removeSelection(); + }), + ); copyQuote.appendChild(buttonSaveAndInsertQuote); document.body.appendChild(copyQuote); diff --git a/ts/WoltLabSuite/Core/Component/Quote/Storage.ts b/ts/WoltLabSuite/Core/Component/Quote/Storage.ts index 61bd25b830..78b74ba854 100644 --- a/ts/WoltLabSuite/Core/Component/Quote/Storage.ts +++ b/ts/WoltLabSuite/Core/Component/Quote/Storage.ts @@ -10,34 +10,68 @@ import * as Core from "WoltLabSuite/Core/Core"; import { renderQuote } from "WoltLabSuite/Core/Api/Messages/RenderQuote"; +import { messageAuthor } from "WoltLabSuite/Core/Api/Messages/Author"; + +interface Message { + objectID: number; + time: number; + link: string; + authorID: number; + author: string; + avatar: string; +} interface StorageData { quotes: Map>; + messages: Map; } -export const STORAGE_KEY = Core.getStoragePrefix() + "quotes"; - -export function saveQuote(objectType: string, objectId: number, message: string) { - const storage = getStorage(); +const STORAGE_KEY = Core.getStoragePrefix() + "quotes"; - const key = getKey(objectType, objectId); - if (!storage.quotes.has(key)) { - storage.quotes.set(key, new Set()); +export async function saveQuote(objectType: string, objectId: number, objectClassName: string, message: string) { + const result = await messageAuthor(objectClassName, objectId); + if (!result.ok) { + // TODO error handling + return; } - storage.quotes.get(key)!.add(message); - - saveStorage(storage); + storeQuote(objectType, result.value, message); } -export async function saveFullQuote(objectType: string, objectId: number) { - const result = await renderQuote(objectType, objectId); +export async function saveFullQuote(objectType: string, objectClassName: string, objectId: number) { + const result = await renderQuote(objectType, objectClassName, objectId); if (!result.ok) { // TODO error handling return; } - saveQuote(objectType, objectId, result.value); + storeQuote( + objectType, + { + objectID: result.value.objectID, + time: result.value.time, + link: result.value.link, + authorID: result.value.authorID, + author: result.value.author, + avatar: result.value.avatar, + }, + result.value.message, + ); +} + +function storeQuote(objectType: string, message: Message, quote: string): void { + const storage = getStorage(); + + const key = getKey(objectType, message.objectID); + if (!storage.quotes.has(key)) { + storage.quotes.set(key, new Set()); + } + + storage.messages.set(key, message); + + storage.quotes.get(key)!.add(quote); + + saveStorage(storage); } export function getQuotes(): Map> { @@ -49,6 +83,7 @@ function getStorage(): StorageData { if (data === null) { return { quotes: new Map(), + messages: new Map(), }; } else { return JSON.parse(data, (key, value) => { @@ -58,6 +93,8 @@ function getStorage(): StorageData { result.set(key, new Set(setValue)); } return result; + } else if (key === "messages") { + return new Map(value); } return value; diff --git a/ts/WoltLabSuite/Core/Ui/Message/Quote.ts b/ts/WoltLabSuite/Core/Ui/Message/Quote.ts index 29b39a52d1..491e974740 100644 --- a/ts/WoltLabSuite/Core/Ui/Message/Quote.ts +++ b/ts/WoltLabSuite/Core/Ui/Message/Quote.ts @@ -25,7 +25,12 @@ export class UiMessageQuote { messageContentSelector: string, supportDirectInsert: boolean, ) { - registerContainer(containerSelector, messageBodySelector, objectType); + // remove "Action" from className + if (className.endsWith("Action")) { + className = className.substring(0, className.length - 6); + } + + registerContainer(containerSelector, messageBodySelector, className, objectType); } } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js index e213a4cf2f..72f49b9485 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js @@ -20,13 +20,14 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui let timerSelectionChange = undefined; let isMouseDown = false; const copyQuote = document.createElement("div"); - function registerContainer(containerSelector, messageBodySelector, objectType) { + function registerContainer(containerSelector, messageBodySelector, className, objectType) { (0, Selector_1.wheneverFirstSeen)(containerSelector, (container) => { const id = Util_1.default.identify(container); containers.set(id, { element: container, messageBodySelector: messageBodySelector, objectType: objectType, + className: className, objectId: ~~container.dataset.objectId, }); if (container.classList.contains("jsInvalidQuoteTarget")) { @@ -36,7 +37,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui container.classList.add("jsQuoteMessageContainer"); container.querySelector(".jsQuoteMessage")?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async (event) => { event.preventDefault(); - await (0, Storage_1.saveFullQuote)(objectType, ~~container.dataset.objectId); + await (0, Storage_1.saveFullQuote)(objectType, className, ~~container.dataset.objectId); //TODO insert into `activeEditor` })); }); @@ -51,21 +52,21 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui buttonSaveQuote.type = "button"; buttonSaveQuote.classList.add("jsQuoteManagerStore"); buttonSaveQuote.textContent = (0, Language_1.getPhrase)("wcf.message.quote.quoteSelected"); - buttonSaveQuote.addEventListener("click", () => { - (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.message); + buttonSaveQuote.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { + await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.container.className, selectedMessage.message); removeSelection(); - }); + })); copyQuote.appendChild(buttonSaveQuote); const buttonSaveAndInsertQuote = document.createElement("button"); buttonSaveAndInsertQuote.type = "button"; buttonSaveAndInsertQuote.hidden = true; buttonSaveAndInsertQuote.classList.add("jsQuoteManagerQuoteAndInsert"); buttonSaveAndInsertQuote.textContent = (0, Language_1.getPhrase)("wcf.message.quote.quoteAndReply"); - buttonSaveAndInsertQuote.addEventListener("click", () => { - (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.message); + buttonSaveAndInsertQuote.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { + await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.container.className, selectedMessage.message); //TODO insert into `activeEditor` removeSelection(); - }); + })); copyQuote.appendChild(buttonSaveAndInsertQuote); document.body.appendChild(copyQuote); document.addEventListener("mouseup", (event) => onMouseUp(event)); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js index 9399bb00c2..9829985755 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js @@ -12,7 +12,11 @@ define(["require", "exports", "WoltLabSuite/Core/Component/Quote/Message"], func * Initializes the quote handler for given object type. */ constructor(quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector, supportDirectInsert) { - (0, Message_1.registerContainer)(containerSelector, messageBodySelector, objectType); + // remove "Action" from className + if (className.endsWith("Action")) { + className = className.substring(0, className.length - 6); + } + (0, Message_1.registerContainer)(containerSelector, messageBodySelector, className, objectType); } } exports.UiMessageQuote = UiMessageQuote; diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index c937638498..8110ba1e3e 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -138,6 +138,7 @@ return static function (): void { $event->register(new \wcf\system\endpoint\controller\core\cronjobs\logs\ClearLogs()); $event->register(new \wcf\system\endpoint\controller\core\messages\GetMentionSuggestions()); $event->register(new \wcf\system\endpoint\controller\core\messages\RenderQuote()); + $event->register(new \wcf\system\endpoint\controller\core\messages\GetMessageAuthor()); $event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession()); $event->register(new \wcf\system\endpoint\controller\core\versionTrackers\RevertVersion()); $event->register(new \wcf\system\endpoint\controller\core\moderationQueues\ChangeJustifiedStatus()); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php new file mode 100644 index 0000000000..e378a79dd8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php @@ -0,0 +1,64 @@ + + * @since 6.2 + */ +#[GetRequest('/core/messages/messageauthor')] +final class GetMessageAuthor implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $parameters = Helper::mapApiParameters($request, GetMessageAuthorParameters::class); + + $object = Helper::fetchObjectFromRequestParameter($parameters->objectID, $parameters->className); + \assert($object instanceof IMessage); + + $userProfile = UserProfileRuntimeCache::getInstance()->getObject($object->getUserID()); + + return new JsonResponse( + [ + "objectID" => $object->getObjectID(), + "authorID" => $userProfile->getUserID(), + "author" => $userProfile->getUsername(), + "avatar" => $userProfile->getAvatar()->getURL(), + "time" => $object->getTime(), + "link" => $object->getLink(), + ], + 200, + [ + 'cache-control' => [ + 'max-age=300', + ], + ] + ); + } +} + +/** @internal */ +final class GetMessageAuthorParameters +{ + public function __construct( + /** @var non-empty-string */ + public readonly string $className, + /** @var positive-int */ + public readonly int $objectID, + ) { + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php index 76d6ba53cd..e6868a7fbf 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use wcf\data\IMessage; use wcf\http\Helper; +use wcf\system\cache\runtime\UserProfileRuntimeCache; use wcf\system\endpoint\GetRequest; use wcf\system\endpoint\IController; use wcf\system\html\input\HtmlInputProcessor; @@ -19,6 +20,7 @@ use wcf\system\html\input\HtmlInputProcessor; * @license GNU Lesser General Public License * @since 6.2 */ + #[GetRequest('/core/messages/renderquote')] final class RenderQuote implements IController { @@ -27,16 +29,27 @@ final class RenderQuote implements IController { $parameters = Helper::mapApiParameters($request, GetRenderQuoteParameters::class); + $object = Helper::fetchObjectFromRequestParameter($parameters->objectID, $parameters->className); + \assert($object instanceof IMessage); + + $userProfile = UserProfileRuntimeCache::getInstance()->getObject($object->getUserID()); + return new JsonResponse( - $this->renderFullQuote($parameters), + [ + "objectID" => $object->getObjectID(), + "authorID" => $userProfile->getUserID(), + "author" => $userProfile->getUsername(), + "avatar" => $userProfile->getAvatar()->getURL(), + "time" => $object->getTime(), + "link" => $object->getLink(), + "message" => $parameters->fullQuote ? $this->renderFullQuote($object) : "" + ], 200, ); } - private function renderFullQuote(GetRenderQuoteParameters $parameters): string + private function renderFullQuote(IMessage $object): string { - // TODO load object - /** @var $object IMessage */ // TODO load embedded objects? $htmlInputProcessor = new HtmlInputProcessor(); @@ -55,9 +68,10 @@ final class GetRenderQuoteParameters { public function __construct( /** @var non-empty-string */ - public readonly string $objectType, + public readonly string $className, /** @var positive-int */ public readonly int $objectID, + public readonly bool $fullQuote = false, ) { } }