diff --git a/com.woltlab.wcf/templates/__userObjectWatchButton.tpl b/com.woltlab.wcf/templates/__userObjectWatchButton.tpl
new file mode 100644
index 00000000000..8a98ab45a8f
--- /dev/null
+++ b/com.woltlab.wcf/templates/__userObjectWatchButton.tpl
@@ -0,0 +1,29 @@
+{if $__wcf->user->userID}
+
+
+
+{/if}
diff --git a/com.woltlab.wcf/templates/categoryArticleList.tpl b/com.woltlab.wcf/templates/categoryArticleList.tpl
index e2743f7e214..249bca68d1d 100644
--- a/com.woltlab.wcf/templates/categoryArticleList.tpl
+++ b/com.woltlab.wcf/templates/categoryArticleList.tpl
@@ -63,9 +63,8 @@
{/capture}
{capture assign='contentInteractionButtons'}
- {if $__wcf->user->userID}
- {lang}wcf.user.objectWatch.button.subscribe{/lang}
- {/if}
+ {include file='__userObjectWatchButton' isSubscribed=$category->isSubscribed() objectType='com.woltlab.wcf.article.category' objectID=$category->categoryID}
+
{if ARTICLE_ENABLE_VISIT_TRACKING}
{lang}wcf.global.button.markAllAsRead{/lang}
{/if}
@@ -109,16 +108,6 @@
{/if}
-
-
{include file='articleAddDialog'}
{include file='footer'}
diff --git a/ts/WoltLabSuite/Core/Ui/User/ObjectWatch.ts b/ts/WoltLabSuite/Core/Ui/User/ObjectWatch.ts
new file mode 100644
index 00000000000..84fe8f77cfc
--- /dev/null
+++ b/ts/WoltLabSuite/Core/Ui/User/ObjectWatch.ts
@@ -0,0 +1,95 @@
+/**
+ * Handles the object watch button.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2022 WoltLab GmbH
+ * @license GNU Lesser General Public License
+ * @module WoltLabSuite/Core/Ui/User/ObjectWatch
+ * @since 6.0
+ */
+
+import * as Ajax from "../../Ajax";
+import * as UiNotification from "../Notification";
+import * as Language from "../../Language";
+import * as EventHandler from "../../Event/Handler";
+
+const dropdowns = new Map>();
+
+async function click(element: HTMLElement): Promise {
+ const dropdown = element.closest(".userObjectWatchDropdown") as HTMLElement;
+ const subscribe = parseInt(element.dataset.subscribe!, 10);
+ const objectID = parseInt(dropdown.dataset.objectId!, 10);
+ const objectType = dropdown.dataset.objectType!;
+
+ await Ajax.dboAction("saveSubscription", "wcf\\data\\user\\object\\watch\\UserObjectWatchAction")
+ .payload({
+ enableNotification: 1,
+ objectID,
+ objectType,
+ subscribe,
+ })
+ .dispatch();
+
+ if (dropdowns.has(objectID)) {
+ dropdowns.get(objectID)!.forEach((element) => {
+ element.querySelectorAll(".userObjectWatchSelect").forEach((li: HTMLElement) => {
+ if (parseInt(li.dataset.subscribe!, 10) === subscribe) {
+ li.classList.add("active");
+ } else {
+ li.classList.remove("active");
+ }
+ });
+ });
+ }
+
+ document
+ .querySelectorAll(`.userObjectWatchDropdownToggle[data-object-type="${objectType}"][data-object-id="${objectID}"]`)
+ .forEach((element: HTMLElement) => {
+ const icon = element.querySelector(".icon")!;
+ const label = element.querySelector("span:not(.icon)")!;
+
+ if (subscribe) {
+ element.classList.add("active");
+ icon.classList.remove("fa-bookmark-o");
+ icon.classList.add("fa-bookmark");
+ label.textContent = Language.get(`wcf.user.objectWatch.button.subscribed`);
+ } else {
+ element.classList.remove("active");
+ icon.classList.remove("fa-bookmark");
+ icon.classList.add("fa-bookmark-o");
+ label.textContent = Language.get("wcf.user.objectWatch.button.subscribe");
+ }
+
+ element.dataset.isSubscribed = subscribe.toString();
+ });
+
+ EventHandler.fire("com.woltlab.wcf.objectWatch", "updatedSubscription");
+ UiNotification.show();
+}
+
+export function setup(): void {
+ document.querySelectorAll(".userObjectWatchDropdown").forEach((element: HTMLElement) => {
+ if (!element.dataset.objectId) {
+ throw new Error("Missing objectId for '.userObjectWatchDropdown' element.");
+ }
+
+ const objectId = parseInt(element.dataset.objectId, 10);
+
+ if (!dropdowns.has(objectId)) {
+ dropdowns.set(objectId, new Set());
+ }
+
+ dropdowns.get(objectId)!.add(element);
+
+ element.querySelectorAll(".userObjectWatchSelect").forEach((element: HTMLElement) => {
+ if (!element.dataset.subscribe) {
+ throw new Error("Missing 'data-subscribe' attribute for '.userObjectWatchSelect' element.");
+ }
+
+ element.addEventListener("click", (event) => {
+ event.preventDefault();
+ void click(element);
+ });
+ });
+ });
+}
diff --git a/wcfsetup/install/files/js/WCF.User.js b/wcfsetup/install/files/js/WCF.User.js
index 63fdce1837b..0bd48341ddc 100644
--- a/wcfsetup/install/files/js/WCF.User.js
+++ b/wcfsetup/install/files/js/WCF.User.js
@@ -2072,6 +2072,8 @@ WCF.User.ObjectWatch = {};
if (COMPILER_TARGET_DEFAULT) {
/**
* Handles subscribe/unsubscribe links.
+ *
+ * @deprecated since 6.0, use `WoltLabSuite/Core/Ui/User/ObjectWatch` instead.
*/
WCF.User.ObjectWatch.Subscribe = Class.extend({
/**
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/ObjectWatch.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/ObjectWatch.js
new file mode 100644
index 00000000000..3392e503305
--- /dev/null
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/ObjectWatch.js
@@ -0,0 +1,88 @@
+/**
+ * Handles the object watch button.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2022 WoltLab GmbH
+ * @license GNU Lesser General Public License
+ * @module WoltLabSuite/Core/Ui/User/ObjectWatch
+ * @since 6.0
+ */
+define(["require", "exports", "tslib", "../../Ajax", "../Notification", "../../Language", "../../Event/Handler"], function (require, exports, tslib_1, Ajax, UiNotification, Language, EventHandler) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.setup = void 0;
+ Ajax = tslib_1.__importStar(Ajax);
+ UiNotification = tslib_1.__importStar(UiNotification);
+ Language = tslib_1.__importStar(Language);
+ EventHandler = tslib_1.__importStar(EventHandler);
+ const dropdowns = new Map();
+ async function click(element) {
+ const dropdown = element.closest(".userObjectWatchDropdown");
+ const subscribe = parseInt(element.dataset.subscribe, 10);
+ const objectID = parseInt(dropdown.dataset.objectId, 10);
+ const objectType = dropdown.dataset.objectType;
+ await Ajax.dboAction("saveSubscription", "wcf\\data\\user\\object\\watch\\UserObjectWatchAction")
+ .payload({
+ enableNotification: 1,
+ objectID,
+ objectType,
+ subscribe,
+ })
+ .dispatch();
+ if (dropdowns.has(objectID)) {
+ dropdowns.get(objectID).forEach((element) => {
+ element.querySelectorAll(".userObjectWatchSelect").forEach((li) => {
+ if (parseInt(li.dataset.subscribe, 10) === subscribe) {
+ li.classList.add("active");
+ }
+ else {
+ li.classList.remove("active");
+ }
+ });
+ });
+ }
+ document
+ .querySelectorAll(`.userObjectWatchDropdownToggle[data-object-type="${objectType}"][data-object-id="${objectID}"]`)
+ .forEach((element) => {
+ const icon = element.querySelector(".icon");
+ const label = element.querySelector("span:not(.icon)");
+ if (subscribe) {
+ element.classList.add("active");
+ icon.classList.remove("fa-bookmark-o");
+ icon.classList.add("fa-bookmark");
+ label.textContent = Language.get(`wcf.user.objectWatch.button.subscribed`);
+ }
+ else {
+ element.classList.remove("active");
+ icon.classList.remove("fa-bookmark");
+ icon.classList.add("fa-bookmark-o");
+ label.textContent = Language.get("wcf.user.objectWatch.button.subscribe");
+ }
+ element.dataset.isSubscribed = subscribe.toString();
+ });
+ EventHandler.fire("com.woltlab.wcf.objectWatch", "updatedSubscription");
+ UiNotification.show();
+ }
+ function setup() {
+ document.querySelectorAll(".userObjectWatchDropdown").forEach((element) => {
+ if (!element.dataset.objectId) {
+ throw new Error("Missing objectId for '.userObjectWatchDropdown' element.");
+ }
+ const objectId = parseInt(element.dataset.objectId, 10);
+ if (!dropdowns.has(objectId)) {
+ dropdowns.set(objectId, new Set());
+ }
+ dropdowns.get(objectId).add(element);
+ element.querySelectorAll(".userObjectWatchSelect").forEach((element) => {
+ if (!element.dataset.subscribe) {
+ throw new Error("Missing 'data-subscribe' attribute for '.userObjectWatchSelect' element.");
+ }
+ element.addEventListener("click", (event) => {
+ event.preventDefault();
+ void click(element);
+ });
+ });
+ });
+ }
+ exports.setup = setup;
+});
diff --git a/wcfsetup/install/files/style/ui/dropdown.scss b/wcfsetup/install/files/style/ui/dropdown.scss
index c1b33f9afdc..6360a227d09 100644
--- a/wcfsetup/install/files/style/ui/dropdown.scss
+++ b/wcfsetup/install/files/style/ui/dropdown.scss
@@ -101,3 +101,18 @@
display: inline-flex !important;
}
}
+
+.userObjectWatchSelect {
+ .userObjectWatchSelectHeader {
+ font-weight: 600;
+ padding-bottom: 0;
+ }
+
+ .userObjectWatchSelectDescription {
+ @include wcfFontSmall;
+
+ color: $wcfContentDimmedText;
+ padding-top: 0;
+ white-space: normal;
+ }
+}
diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml
index 1d1cbafdc64..0577afa07dc 100644
--- a/wcfsetup/install/lang/de.xml
+++ b/wcfsetup/install/lang/de.xml
@@ -5467,6 +5467,11 @@ Benachrichtigungen auf {PAGE_TITLE|phra
+
+
+
+
+
diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml
index b7172fd06d0..5e4ffffcf58 100644
--- a/wcfsetup/install/lang/en.xml
+++ b/wcfsetup/install/lang/en.xml
@@ -5469,6 +5469,11 @@ your notifications on {PAGE_TITLE|phras
+
+
+
+
+