--- /dev/null
+/**
+ * Requests render a full quote of a message.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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<ApiResult<Response>> {
+ 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);
+}
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<ApiResult<Response>> {
+export async function renderQuote(
+ objectType: string,
+ className: string,
+ objectID: number,
+): Promise<ApiResult<Response>> {
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;
element: HTMLElement;
messageBodySelector: string;
objectType: string;
+ className: string;
objectId: number;
}
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!,
});
promiseMutex(async (event: MouseEvent) => {
event.preventDefault();
- await saveFullQuote(objectType, ~~container.dataset.objectId!);
+ await saveFullQuote(objectType, className, ~~container.dataset.objectId!);
//TODO insert into `activeEditor`
}),
);
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);
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<string, Set<string>>;
+ messages: Map<string, Message>;
}
-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<string, Set<string>> {
if (data === null) {
return {
quotes: new Map(),
+ messages: new Map(),
};
} else {
return JSON.parse(data, (key, value) => {
result.set(key, new Set(setValue));
}
return result;
+ } else if (key === "messages") {
+ return new Map<string, Message>(value);
}
return value;
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);
}
}
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")) {
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`
}));
});
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));
* 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;
$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());
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\messages;
+
+use Laminas\Diactoros\Response\JsonResponse;
+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;
+
+/**
+ * Returns information about the author of a message.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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,
+ ) {
+ }
+}
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;
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.2
*/
+
#[GetRequest('/core/messages/renderquote')]
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();
{
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,
) {
}
}