diff --git a/ts/WoltLabSuite/Core/Component/ListView/State.ts b/ts/WoltLabSuite/Core/Component/ListView/State.ts index 6dd09810290..cbec1dd878c 100644 --- a/ts/WoltLabSuite/Core/Component/ListView/State.ts +++ b/ts/WoltLabSuite/Core/Component/ListView/State.ts @@ -7,9 +7,12 @@ * @since 6.2 */ +import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector"; import Filter from "./Filter"; import Selection from "./Selection"; import Sorting from "./Sorting"; +import { postObject } from "WoltLabSuite/Core/Api/PostObject"; +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; export const enum StateChangeCause { Change, @@ -71,6 +74,18 @@ export class State extends EventTarget { this.#handlePopState(); }); + wheneverFirstSeen(`#${viewId}_items .listView__item__markAsRead`, (button: HTMLButtonElement) => { + button.addEventListener( + "click", + promiseMutex(async () => { + await postObject(button.dataset.endpoint!); + button + .closest(".listView__item") + ?.dispatchEvent(new CustomEvent("interaction:invalidate", { bubbles: true })); + }), + ); + }); + this.#updatePaginationUrl(); this.#updateListViewFooter(); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js index 95b11a3c12d..fb681525561 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js @@ -6,7 +6,7 @@ * @license GNU Lesser General Public License * @since 6.2 */ -define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], function (require, exports, tslib_1, Filter_1, Selection_1, Sorting_1) { +define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./Filter", "./Selection", "./Sorting", "WoltLabSuite/Core/Api/PostObject", "WoltLabSuite/Core/Helper/PromiseMutex"], function (require, exports, tslib_1, Selector_1, Filter_1, Selection_1, Sorting_1, PostObject_1, PromiseMutex_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.State = void 0; @@ -49,6 +49,14 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], window.addEventListener("popstate", () => { this.#handlePopState(); }); + (0, Selector_1.wheneverFirstSeen)(`#${viewId}_items .listView__item__markAsRead`, (button) => { + button.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { + await (0, PostObject_1.postObject)(button.dataset.endpoint); + button + .closest(".listView__item") + ?.dispatchEvent(new CustomEvent("interaction:invalidate", { bubbles: true })); + })); + }); this.#updatePaginationUrl(); this.#updateListViewFooter(); } diff --git a/wcfsetup/install/files/lib/system/listView/AbstractListView.class.php b/wcfsetup/install/files/lib/system/listView/AbstractListView.class.php index c428bfc5bd6..1b6abb36c9e 100644 --- a/wcfsetup/install/files/lib/system/listView/AbstractListView.class.php +++ b/wcfsetup/install/files/lib/system/listView/AbstractListView.class.php @@ -2,6 +2,7 @@ namespace wcf\system\listView; +use wcf\action\ApiAction; use wcf\action\ListViewFilterAction; use wcf\data\DatabaseObject; use wcf\data\DatabaseObjectDecorator; @@ -16,6 +17,7 @@ use wcf\system\listView\filter\exception\InvalidFilterValue; use wcf\system\request\LinkHandler; use wcf\system\WCF; +use wcf\util\StringUtil; /** * Abstract implementation of a list view. @@ -48,6 +50,7 @@ abstract class AbstractListView private bool $allowInteractions = true; private bool $allowBulkInteractions = true; private int $fixedNumberOfItems = 0; + private string $markAsReadEndpoint = ''; /** * @var array @@ -673,6 +676,35 @@ public function getContainerCssClassName(): string return $this->containerCssClassName; } + public function setMarkAsReadEndpoints(string $endpoint): void + { + $this->markAsReadEndpoint = $endpoint; + } + + public function renderMarkAsReadButton(DatabaseObject $object): string + { + if (!$this->markAsReadEndpoint) { + throw new \BadMethodCallException("No mark as read endpoint has been specified."); + } + + $endpoint = StringUtil::encodeHTML( + LinkHandler::getInstance()->getControllerLink(ApiAction::class, ['id' => 'rpc']) . + \sprintf($this->markAsReadEndpoint, $object->getObjectID()) + ); + $title = WCF::getLanguage()->get('wcf.global.button.markAsRead'); + + return << + + + HTML; + } + /** * @return TDatabaseObjectList */ diff --git a/wcfsetup/install/files/style/ui/discussionList.scss b/wcfsetup/install/files/style/ui/discussionList.scss index 5330dfdc15d..d33c2aa7881 100644 --- a/wcfsetup/install/files/style/ui/discussionList.scss +++ b/wcfsetup/install/files/style/ui/discussionList.scss @@ -140,29 +140,12 @@ grid-template-rows: min-content min-content 1fr; } -.discussionList__item__markAsRead { - align-items: center; +.discussionList__item .listView__item__markAsRead { grid-area: markAsRead; - display: flex; - padding: 0 5px; margin-right: 5px; z-index: 1; } -.discussionList__item__unread__indicator { - background-color: var(--wcfButtonPrimaryBackground); - border-radius: 50%; - display: inline-block; - height: 10px; - width: 10px; -} - -@media (hover: hover) { - .discussionList__item__markAsRead:hover .discussionList__item__unread__indicator { - background-color: var(--wcfButtonPrimaryBackgroundActive); - } -} - .discussionList__item__title { grid-area: title; diff --git a/wcfsetup/install/files/style/ui/listView.scss b/wcfsetup/install/files/style/ui/listView.scss index 9de3dc98da1..cfb99cf3021 100644 --- a/wcfsetup/install/files/style/ui/listView.scss +++ b/wcfsetup/install/files/style/ui/listView.scss @@ -114,3 +114,23 @@ display: flex; padding: 0 6px; } + +.listView__item__markAsRead { + align-items: center; + display: flex; + padding: 0 5px; +} + +.listView__item__unread__indicator { + background-color: var(--wcfButtonPrimaryBackground); + border-radius: 50%; + display: inline-block; + height: 10px; + width: 10px; +} + +@media (hover: hover) { + .listView__item__markAsRead:hover .listView__item__unread__indicator { + background-color: var(--wcfButtonPrimaryBackgroundActive); + } +}