force: {if $forceBackgroundQueuePerform|isset}true{else}false{/if}
},
dynamicColorScheme: {if $__wcf->getStyleHandler()->getColorScheme() === 'system'}true{else}false{/if},
- enableUserPopover: {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile')}true{else}false{/if},
+ endpointUserPopover: {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile')}'{link controller='UserPopover'}{/link}'{else}''{/if},
executeCronjobs: {if $executeCronjobs}'{link controller="CronjobPerform"}{/link}'{else}undefined{/if},
{if ENABLE_SHARE_BUTTONS}
{assign var='__shareProviders' value="\n"|explode:SHARE_BUTTONS_PROVIDERS}
import * as BackgroundQueue from "./BackgroundQueue";
import * as Bootstrap from "./Bootstrap";
-import * as ControllerPopover from "./Controller/Popover";
import * as UiUserIgnore from "./Ui/User/Ignore";
import * as UiPageHeaderMenu from "./Ui/Page/Header/Menu";
import * as UiMessageUserConsent from "./Ui/Message/UserConsent";
force: boolean;
};
dynamicColorScheme: boolean;
- enableUserPopover: boolean;
+ endpointUserPopover: string;
executeCronjobs: string | undefined;
shareButtonProviders?: ShareProvider[];
styleChanger: boolean;
/**
* Initializes user profile popover.
*/
-function _initUserPopover(): void {
- ControllerPopover.init({
- className: "userLink",
- dboAction: "wcf\\data\\user\\UserProfileAction",
- identifier: "com.woltlab.wcf.user",
- });
+function setupUserPopover(endpoint: string): void {
+ if (endpoint === "") {
+ return;
+ }
- // @deprecated since 5.3
- ControllerPopover.init({
- attributeName: "data-user-id",
- className: "userLink",
- dboAction: "wcf\\data\\user\\UserProfileAction",
- identifier: "com.woltlab.wcf.user.deprecated",
+ whenFirstSeen(".userLink", () => {
+ void import("./Component/Popover").then(({ setupFor }) => {
+ setupFor({
+ endpoint,
+ identifier: "com.woltlab.wcf.user",
+ selector: ".userLink",
+ });
+ });
});
}
});
}
- if (options.enableUserPopover) {
- _initUserPopover();
- }
+ setupUserPopover(options.endpointUserPopover);
if (options.executeCronjobs !== undefined) {
void prepareRequest(options.executeCronjobs)
--- /dev/null
+import { prepareRequest } from "../Ajax/Backend";
+import DomUtil from "../Dom/Util";
+import { getPageOverlayContainer } from "../Helper/PageOverlay";
+import { wheneverFirstSeen } from "../Helper/Selector";
+
+class Popover {
+ readonly #cache = new Map<number, string>();
+ #currentElement: HTMLElement | undefined = undefined;
+ #container: HTMLElement | undefined = undefined;
+ readonly #endpoint: URL;
+ #enabled = true;
+ readonly #identifier: string;
+
+ constructor(selector: string, endpoint: string, identifier: string) {
+ this.#identifier = identifier;
+ this.#endpoint = new URL(endpoint);
+
+ wheneverFirstSeen(selector, (element) => {
+ element.addEventListener("mouseenter", () => {
+ void this.#hoverStart(element);
+ });
+ element.addEventListener("mouseleave", () => {
+ this.#hoverEnd(element);
+ });
+ });
+
+ const mq = window.matchMedia("(hover:hover)");
+ this.#setEnabled(mq.matches);
+
+ mq.addEventListener("change", (event) => {
+ this.#setEnabled(event.matches);
+ });
+
+ window.addEventListener("beforeunload", () => {
+ this.#setEnabled(false);
+ });
+ }
+
+ async #hoverStart(element: HTMLElement): Promise<void> {
+ const objectId = this.#getObjectId(element);
+
+ let content = this.#cache.get(objectId);
+ if (content === undefined) {
+ content = await this.#fetch(objectId);
+ this.#cache.set(objectId, content);
+ }
+
+ DomUtil.setInnerHtml(this.#getContainer(), content);
+ }
+
+ #hoverEnd(element: HTMLElement): void {}
+
+ async #fetch(objectId: number): Promise<string> {
+ this.#endpoint.searchParams.set("id", objectId.toString());
+
+ const response = await prepareRequest(this.#endpoint).get().fetchAsResponse();
+ if (response?.ok) {
+ return await response.text();
+ }
+
+ return "";
+ }
+
+ #setEnabled(enabled: boolean): void {
+ this.#enabled = enabled;
+ }
+
+ #getObjectId(element: HTMLElement): number {
+ return parseInt(element.dataset.objectId!);
+ }
+
+ #getContainer(): HTMLElement {
+ if (this.#container === undefined) {
+ this.#container = document.createElement("div");
+ this.#container.dataset.identifier = this.#identifier;
+ }
+
+ this.#container.remove();
+ getPageOverlayContainer().append(this.#container);
+
+ return this.#container;
+ }
+}
+
+type Configuration = {
+ endpoint: string;
+ identifier: string;
+ selector: string;
+};
+
+export function setupFor(configuration: Configuration): void {
+ const { identifier, endpoint, selector } = configuration;
+
+ new Popover(selector, endpoint, identifier);
+}
* @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
-define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Controller/Popover", "./Ui/User/Ignore", "./Ui/Page/Header/Menu", "./Ui/Message/UserConsent", "./Ui/Message/Share/Dialog", "./Ui/Message/Share/Providers", "./Ui/Feed/Dialog", "./User", "./Ui/Page/Menu/Main/Frontend", "./LazyLoader", "./Ajax/Backend"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, ControllerPopover, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, UiMessageShareDialog, Providers_1, UiFeedDialog, User_1, Frontend_1, LazyLoader_1, Backend_1) {
+define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Ui/User/Ignore", "./Ui/Page/Header/Menu", "./Ui/Message/UserConsent", "./Ui/Message/Share/Dialog", "./Ui/Message/Share/Providers", "./Ui/Feed/Dialog", "./User", "./Ui/Page/Menu/Main/Frontend", "./LazyLoader", "./Ajax/Backend"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, UiMessageShareDialog, Providers_1, UiFeedDialog, User_1, Frontend_1, LazyLoader_1, Backend_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setup = void 0;
BackgroundQueue = tslib_1.__importStar(BackgroundQueue);
Bootstrap = tslib_1.__importStar(Bootstrap);
- ControllerPopover = tslib_1.__importStar(ControllerPopover);
UiUserIgnore = tslib_1.__importStar(UiUserIgnore);
UiPageHeaderMenu = tslib_1.__importStar(UiPageHeaderMenu);
UiMessageUserConsent = tslib_1.__importStar(UiMessageUserConsent);
/**
* Initializes user profile popover.
*/
- function _initUserPopover() {
- ControllerPopover.init({
- className: "userLink",
- dboAction: "wcf\\data\\user\\UserProfileAction",
- identifier: "com.woltlab.wcf.user",
- });
- // @deprecated since 5.3
- ControllerPopover.init({
- attributeName: "data-user-id",
- className: "userLink",
- dboAction: "wcf\\data\\user\\UserProfileAction",
- identifier: "com.woltlab.wcf.user.deprecated",
+ function setupUserPopover(endpoint) {
+ if (endpoint === "") {
+ return;
+ }
+ (0, LazyLoader_1.whenFirstSeen)(".userLink", () => {
+ void new Promise((resolve_1, reject_1) => { require(["./Component/Popover"], resolve_1, reject_1); }).then(tslib_1.__importStar).then(({ setupFor }) => {
+ setupFor({
+ endpoint,
+ identifier: "com.woltlab.wcf.user",
+ selector: ".userLink",
+ });
+ });
});
}
/**
});
UiPageHeaderMenu.init();
if (options.styleChanger) {
- void new Promise((resolve_1, reject_1) => { require(["./Controller/Style/Changer"], resolve_1, reject_1); }).then(tslib_1.__importStar).then((ControllerStyleChanger) => {
+ void new Promise((resolve_2, reject_2) => { require(["./Controller/Style/Changer"], resolve_2, reject_2); }).then(tslib_1.__importStar).then((ControllerStyleChanger) => {
ControllerStyleChanger.setup();
});
}
- if (options.enableUserPopover) {
- _initUserPopover();
- }
+ setupUserPopover(options.endpointUserPopover);
if (options.executeCronjobs !== undefined) {
void (0, Backend_1.prepareRequest)(options.executeCronjobs)
.get()
UiFeedDialog.setup();
}
(0, LazyLoader_1.whenFirstSeen)("woltlab-core-reaction-summary", () => {
- void new Promise((resolve_2, reject_2) => { require(["./Ui/Reaction/SummaryDetails"], resolve_2, reject_2); }).then(tslib_1.__importStar).then(({ setup }) => setup());
+ void new Promise((resolve_3, reject_3) => { require(["./Ui/Reaction/SummaryDetails"], resolve_3, reject_3); }).then(tslib_1.__importStar).then(({ setup }) => setup());
});
(0, LazyLoader_1.whenFirstSeen)("woltlab-core-comment", () => {
- void new Promise((resolve_3, reject_3) => { require(["./Component/Comment/woltlab-core-comment"], resolve_3, reject_3); }).then(tslib_1.__importStar);
+ void new Promise((resolve_4, reject_4) => { require(["./Component/Comment/woltlab-core-comment"], resolve_4, reject_4); }).then(tslib_1.__importStar);
});
(0, LazyLoader_1.whenFirstSeen)("woltlab-core-comment-response", () => {
- void new Promise((resolve_4, reject_4) => { require(["./Component/Comment/Response/woltlab-core-comment-response"], resolve_4, reject_4); }).then(tslib_1.__importStar);
+ void new Promise((resolve_5, reject_5) => { require(["./Component/Comment/Response/woltlab-core-comment-response"], resolve_5, reject_5); }).then(tslib_1.__importStar);
});
(0, LazyLoader_1.whenFirstSeen)("[data-follow-user]", () => {
- void new Promise((resolve_5, reject_5) => { require(["./Component/User/Follow"], resolve_5, reject_5); }).then(tslib_1.__importStar).then(({ setup }) => setup());
+ void new Promise((resolve_6, reject_6) => { require(["./Component/User/Follow"], resolve_6, reject_6); }).then(tslib_1.__importStar).then(({ setup }) => setup());
});
(0, LazyLoader_1.whenFirstSeen)("[data-ignore-user]", () => {
void new Promise((resolve_6, reject_6) => { require(["./Component/User/Ignore"], resolve_6, reject_6); }).then(tslib_1.__importStar).then(({ setup }) => setup());
--- /dev/null
+define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Helper/PageOverlay", "../Helper/Selector"], function (require, exports, tslib_1, Backend_1, Util_1, PageOverlay_1, Selector_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.setupFor = void 0;
+ Util_1 = tslib_1.__importDefault(Util_1);
+ class Popover {
+ #cache = new Map();
+ #currentElement = undefined;
+ #container = undefined;
+ #endpoint;
+ #enabled = true;
+ #identifier;
+ constructor(selector, endpoint, identifier) {
+ this.#identifier = identifier;
+ this.#endpoint = new URL(endpoint);
+ (0, Selector_1.wheneverFirstSeen)(selector, (element) => {
+ element.addEventListener("mouseenter", () => {
+ void this.#hoverStart(element);
+ });
+ element.addEventListener("mouseleave", () => {
+ this.#hoverEnd(element);
+ });
+ });
+ const mq = window.matchMedia("(hover:hover)");
+ this.#setEnabled(mq.matches);
+ mq.addEventListener("change", (event) => {
+ this.#setEnabled(event.matches);
+ });
+ window.addEventListener("beforeunload", () => {
+ this.#setEnabled(false);
+ });
+ }
+ async #hoverStart(element) {
+ const objectId = this.#getObjectId(element);
+ let content = this.#cache.get(objectId);
+ if (content === undefined) {
+ content = await this.#fetch(objectId);
+ this.#cache.set(objectId, content);
+ }
+ Util_1.default.setInnerHtml(this.#getContainer(), content);
+ }
+ #hoverEnd(element) { }
+ async #fetch(objectId) {
+ this.#endpoint.searchParams.set("id", objectId.toString());
+ const response = await (0, Backend_1.prepareRequest)(this.#endpoint).get().fetchAsResponse();
+ if (response?.ok) {
+ return await response.text();
+ }
+ return "";
+ }
+ #setEnabled(enabled) {
+ this.#enabled = enabled;
+ }
+ #getObjectId(element) {
+ return parseInt(element.dataset.objectId);
+ }
+ #getContainer() {
+ if (this.#container === undefined) {
+ this.#container = document.createElement("div");
+ this.#container.dataset.identifier = this.#identifier;
+ }
+ this.#container.remove();
+ (0, PageOverlay_1.getPageOverlayContainer)().append(this.#container);
+ return this.#container;
+ }
+ }
+ function setupFor(configuration) {
+ const { identifier, endpoint, selector } = configuration;
+ new Popover(selector, endpoint, identifier);
+ }
+ exports.setupFor = setupFor;
+});
--- /dev/null
+<?php
+
+namespace wcf\action;
+
+use Laminas\Diactoros\Response\HtmlResponse;
+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;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\WCF;
+
+/**
+ * Provides the popover content for a user.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class UserPopoverAction implements RequestHandlerInterface
+{
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $parameters = Helper::mapQueryParameters(
+ $request->getQueryParams(),
+ <<<'EOT'
+ array {
+ id: positive-int
+ }
+ EOT,
+ );
+
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject($parameters['id']);
+ if ($userProfile) {
+ WCF::getTPL()->assign('user', $userProfile);
+ } else {
+ WCF::getTPL()->assign('unknownUser', true);
+ }
+
+ return new HtmlResponse(
+ WCF::getTPL()->fetch('userProfilePreview'),
+ );
+ }
+}