Skip to content

Commit f059832

Browse files
committed
Popover for CMS articles
1 parent b3929ac commit f059832

File tree

10 files changed

+185
-12
lines changed

10 files changed

+185
-12
lines changed

com.woltlab.wcf/templates/article.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@
272272
<span class="articleNavigationArticleContent">
273273
<span class="articleNavigationEntityName">{lang}wcf.article.previousArticle{/lang}</span>
274274
<span class="articleNavigationArticleTitle">
275-
<a href="{$previousArticle->getLink()}" rel="prev" class="articleNavigationArticleLink">
275+
<a href="{$previousArticle->getLink()}" rel="prev" class="articleNavigationArticleLink articleLink" data-object-id="{$previousArticle->getObjectID()}">
276276
{$previousArticle->getTitle()}
277277
</a>
278278
</span>
@@ -291,7 +291,7 @@
291291
<span class="articleNavigationArticleContent">
292292
<span class="articleNavigationEntityName">{lang}wcf.article.nextArticle{/lang}</span>
293293
<span class="articleNavigationArticleTitle">
294-
<a href="{$nextArticle->getLink()}" rel="next" class="articleNavigationArticleLink">
294+
<a href="{$nextArticle->getLink()}" rel="next" class="articleNavigationArticleLink articleLink" data-object-id="{$nextArticle->getObjectID()}">
295295
{$nextArticle->getTitle()}
296296
</a>
297297
</span>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<div class="popover__layout">
2+
{if $article->getTeaserImage()}
3+
<div class="popover__coverPhoto">
4+
<img class="popover__coverPhoto__image" src="{$article->getTeaserImage()->getThumbnailLink('medium')}" alt="">
5+
</div>
6+
{/if}
7+
8+
<div class="popover__header">
9+
{event name='beforeHeader'}
10+
11+
<div class="popover__avatar">
12+
{user object=$article->getUserProfile() type='avatar48' ariaHidden='true' tabindex='-1'}
13+
</div>
14+
<div class="popover__title">
15+
<a href="{$article->getLink()}">{$article->getTitle()}</a>
16+
</div>
17+
<div class="popover__time">
18+
{time time=$article->time}
19+
</div>
20+
21+
{event name='afterHeader'}
22+
</div>
23+
24+
{event name='beforeText'}
25+
26+
<div class="popover__text htmlContent">
27+
{unsafe:$article->getFormattedTeaser()}
28+
</div>
29+
30+
{event name='afterText'}
31+
</div>

com.woltlab.wcf/templates/boxArticleList.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<a href="{$boxArticle->getLink()}" aria-hidden="true" tabindex="-1">{unsafe:$boxArticle->getUserProfile()->getAvatar()->getImageTag(24)}</a>
66

77
<div class="sidebarItemTitle">
8-
<h3><a href="{$boxArticle->getLink()}">{$boxArticle->getTitle()}</a></h3>
8+
<h3>{anchor object=$boxArticle class='articleLink' title=$boxArticle->getTitle()}</h3>
99

1010
<small>
1111
{if $boxSortField == 'time'}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Gets the html code for the rendering of an article popover.
3+
*
4+
* @author Marcel Werk
5+
* @copyright 2001-2025 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.2
8+
*/
9+
10+
import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
11+
import { ApiResult, apiResultFromError, apiResultFromValue } from "WoltLabSuite/Core/Api/Result";
12+
13+
type Response = {
14+
template: string;
15+
};
16+
17+
export async function getArticlePopover(articleId: number): Promise<ApiResult<string>> {
18+
const url = new URL(`${window.WSC_RPC_API_URL}core/articles/${articleId}/popover`);
19+
20+
let response: Response;
21+
try {
22+
response = (await prepareRequest(url).get().fetchAsJson()) as Response;
23+
} catch (e) {
24+
return apiResultFromError(e);
25+
}
26+
27+
return apiResultFromValue(response.template);
28+
}

ts/WoltLabSuite/Core/BootstrapFrontend.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import UiPageMenuMainFrontend from "./Ui/Page/Menu/Main/Frontend";
1919
import { whenFirstSeen } from "./LazyLoader";
2020
import { prepareRequest } from "./Ajax/Backend";
2121
import { setup as serviceWorkerSetup } from "./Notification/ServiceWorker";
22+
import { getArticlePopover } from "./Api/Articles/GetArticlePopover";
2223

2324
interface BootstrapOptions {
2425
backgroundQueue: {
@@ -57,6 +58,20 @@ function setupUserPopover(endpoint: string): void {
5758
});
5859
}
5960

61+
function setupArticlePopover(): void {
62+
whenFirstSeen(".articleLink", () => {
63+
void import("WoltLabSuite/Core/Component/Popover").then(({ setupFor }) => {
64+
setupFor({
65+
endpoint: async (objectId: number) => {
66+
return (await getArticlePopover(objectId)).unwrap();
67+
},
68+
identifier: "com.woltlab.wcf.article",
69+
selector: ".articleLink",
70+
});
71+
});
72+
});
73+
}
74+
6075
declare const COMPILER_TARGET_DEFAULT: boolean;
6176

6277
/**
@@ -80,6 +95,7 @@ export function setup(options: BootstrapOptions): void {
8095
}
8196

8297
setupUserPopover(options.endpointUserPopover);
98+
setupArticlePopover();
8399

84100
if (options.executeCronjobs !== undefined) {
85101
void prepareRequest(options.executeCronjobs)

wcfsetup/install/files/js/WoltLabSuite/Core/Api/Articles/GetArticlePopover.js

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js

Lines changed: 22 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ static function (\wcf\event\acp\dashboard\box\BoxCollecting $event) {
116116
$eventHandler->register(
117117
\wcf\event\endpoint\ControllerCollecting::class,
118118
static function (\wcf\event\endpoint\ControllerCollecting $event) {
119+
$event->register(new \wcf\system\endpoint\controller\core\articles\GetArticlePopover);
119120
$event->register(new \wcf\system\endpoint\controller\core\files\DeleteFile);
120121
$event->register(new \wcf\system\endpoint\controller\core\files\GenerateThumbnails);
121122
$event->register(new \wcf\system\endpoint\controller\core\files\PrepareUpload);

wcfsetup/install/files/lib/data/article/Article.class.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use wcf\data\attachment\GroupedAttachmentList;
88
use wcf\data\DatabaseObject;
99
use wcf\data\ILinkableObject;
10+
use wcf\data\IPopoverObject;
1011
use wcf\data\IUserContent;
1112
use wcf\data\object\type\ObjectTypeCache;
1213
use wcf\data\user\UserProfile;
@@ -38,7 +39,7 @@
3839
* @property-read int $isDeleted is 1 if the article is in trash bin, otherwise 0
3940
* @property-read int $hasLabels is `1` if labels are assigned to the article
4041
*/
41-
class Article extends DatabaseObject implements ILinkableObject, IUserContent
42+
class Article extends DatabaseObject implements ILinkableObject, IPopoverObject, IUserContent
4243
{
4344
/**
4445
* indicates that article is unpublished
@@ -443,4 +444,10 @@ public function getAttachments(): ?GroupedAttachmentList
443444

444445
return null;
445446
}
447+
448+
#[\Override]
449+
public function getPopoverLinkClass()
450+
{
451+
return 'articleLink';
452+
}
446453
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace wcf\system\endpoint\controller\core\articles;
4+
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use wcf\data\article\Article;
9+
use wcf\data\article\ViewableArticle;
10+
use wcf\http\Helper;
11+
use wcf\system\endpoint\GetRequest;
12+
use wcf\system\endpoint\IController;
13+
use wcf\system\exception\PermissionDeniedException;
14+
use wcf\system\WCF;
15+
16+
/**
17+
* API endpoint for the rendering of the article popover.
18+
*
19+
* @author Marcel Werk
20+
* @copyright 2001-2025 WoltLab GmbH
21+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22+
* @since 6.2
23+
*/
24+
#[GetRequest('/core/articles/{id:\d+}/popover')]
25+
final class GetArticlePopover implements IController
26+
{
27+
#[\Override]
28+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
29+
{
30+
$article = Helper::fetchObjectFromRequestParameter($variables['id'], Article::class);
31+
32+
$this->assertArticleIsAccessible($article);
33+
34+
return new JsonResponse([
35+
'template' => $this->renderPopover($article),
36+
]);
37+
}
38+
39+
private function assertArticleIsAccessible(Article $article): void
40+
{
41+
if (!$article->canRead()) {
42+
throw new PermissionDeniedException();
43+
}
44+
}
45+
46+
private function renderPopover(Article $article): string
47+
{
48+
return WCF::getTPL()->fetch('articlePopover', 'wcf', [
49+
'article' => ViewableArticle::getArticle($article->articleID),
50+
], true);
51+
}
52+
}

0 commit comments

Comments
 (0)