Skip to content

Commit 3d62095

Browse files
authored
Merge pull request #6601 from WoltLab/63-article-mark-as-read
Unify 'mark as read' function for articles
2 parents 53646e4 + 20c1968 commit 3d62095

8 files changed

Lines changed: 115 additions & 44 deletions

File tree

com.woltlab.wcf/templates/articleList.tpl

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,20 @@
1717
{/capture}
1818

1919
{capture assign='contentInteractionButtons'}
20-
{if $__wcf->user->userID}
21-
<button type="button" class="markAllAsReadButton contentInteractionButton button small jsOnly">{icon name='check'} <span>{lang}wcf.global.button.markAllAsRead{/lang}</span></button>
20+
{if $listView->canMarkAsRead()}
21+
<button type="button" class="markAllArticlesAsReadButton contentInteractionButton button small jsOnly">
22+
{icon name='check'}
23+
<span>{lang}wcf.global.button.markAllAsRead{/lang}</span>
24+
</button>
25+
26+
<script data-relocate="true">
27+
require(['WoltLabSuite/Core/Component/Article/MarkAllArticlesAsRead'], ({ setup }) => {
28+
setup(
29+
document.querySelector('.markAllArticlesAsReadButton'),
30+
document.getElementById('{unsafe:$listView->getID()|encodeJS}_items')
31+
);
32+
});
33+
</script>
2234
{/if}
2335
{/capture}
2436

@@ -36,14 +48,6 @@
3648
{unsafe:$listView->render()}
3749
</div>
3850

39-
{if $__wcf->user->userID}
40-
<script data-relocate="true">
41-
require(['WoltLabSuite/Core/Ui/Article/MarkAllAsRead'], ({ setup }) => {
42-
setup(document.getElementById('{unsafe:$listView->getID()|encodeJS}_items'));
43-
});
44-
</script>
45-
{/if}
46-
4751
{if $canManageArticles}
4852
{include file='shared_articleAddDialog'}
4953
{/if}

com.woltlab.wcf/templates/articleListItems.tpl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
{content}
3333
{if $article->isDeleted}<span class="badge red">{lang}wcf.message.status.deleted{/lang}</span>{/if}
3434
{if !$article->isPublished()}<span class="badge green">{lang}wcf.message.status.disabled{/lang}</span>{/if}
35-
{if $article->isNew()}<span class="badge">{lang}wcf.message.new{/lang}</span>{/if}
3635

3736
{event name='contentItemBadges'}{* deprecated: use badges instead *}
3837
{event name='badges'}
@@ -51,6 +50,10 @@
5150
{/if}
5251

5352
<h2 class="entryCardList__item__title">
53+
{if $article->isNew()}
54+
{unsafe:$view->renderMarkAsReadButton($article)}
55+
{/if}
56+
5457
<a href="{$article->getLink()}" class="entryCardList__item__link">{$article->getTitle()}</a>
5558
</h2>
5659

com.woltlab.wcf/templates/categoryArticleList.tpl

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,20 @@
2424
{capture assign='contentInteractionButtons'}
2525
{include file='__userObjectWatchButton' isSubscribed=$category->isSubscribed() objectType='com.woltlab.wcf.article.category' objectID=$category->categoryID}
2626

27-
{if $__wcf->user->userID}
28-
<button type="button" class="markAllAsReadButton contentInteractionButton button small jsOnly">{icon name='check'} <span>{lang}wcf.global.button.markAllAsRead{/lang}</span></button>
27+
{if $listView->canMarkAsRead()}
28+
<button type="button" class="markAllArticlesAsReadButton contentInteractionButton button small jsOnly">
29+
{icon name='check'}
30+
<span>{lang}wcf.global.button.markAllAsRead{/lang}</span>
31+
</button>
32+
33+
<script data-relocate="true">
34+
require(['WoltLabSuite/Core/Component/Article/MarkAllArticlesAsRead'], ({ setup }) => {
35+
setup(
36+
document.querySelector('.markAllArticlesAsReadButton'),
37+
document.getElementById('{unsafe:$listView->getID()|encodeJS}_items')
38+
);
39+
});
40+
</script>
2941
{/if}
3042
{/capture}
3143

@@ -43,14 +55,6 @@
4355
{unsafe:$listView->render()}
4456
</div>
4557

46-
{if $__wcf->user->userID}
47-
<script data-relocate="true">
48-
require(['WoltLabSuite/Core/Ui/Article/MarkAllAsRead'], ({ setup }) => {
49-
setup(document.getElementById('{unsafe:$listView->getID()|encodeJS}_items'));
50-
});
51-
</script>
52-
{/if}
53-
5458
{if $canManageArticles}
5559
{include file='shared_articleAddDialog'}
5660
{/if}

ts/WoltLabSuite/Core/Ui/Article/MarkAllAsRead.ts renamed to ts/WoltLabSuite/Core/Component/Article/MarkAllArticlesAsRead.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
/**
22
* Handles the 'mark as read' action for articles.
33
*
4-
* @author Marcel Werk
5-
* @copyright 2001-2023 WoltLab GmbH
6-
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
4+
* @author Marcel Werk
5+
* @copyright 2001-2026 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.3
78
* @woltlabExcludeBundle tiny
89
*/
910

1011
import { showDefaultSuccessSnackbar } from "WoltLabSuite/Core/Component/Snackbar";
1112
import { markAllArticlesAsRead } from "WoltLabSuite/Core/Api/Articles/MarkAllArticlesAsRead";
1213
import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
1314

14-
async function markAllAsRead(listView?: HTMLElement): Promise<void> {
15+
async function markAllAsRead(button: HTMLElement, listView?: HTMLElement): Promise<void> {
1516
await markAllArticlesAsRead();
1617

1718
if (listView !== undefined) {
@@ -20,16 +21,16 @@ async function markAllAsRead(listView?: HTMLElement): Promise<void> {
2021

2122
document.querySelectorAll(".boxMenu .active .badgeUpdate").forEach((el: HTMLElement) => el.remove());
2223

24+
button.remove();
25+
2326
showDefaultSuccessSnackbar();
2427
}
2528

26-
export function setup(listView?: HTMLElement): void {
27-
document.querySelectorAll(".markAllAsReadButton").forEach((el: HTMLElement) => {
28-
el.addEventListener(
29-
"click",
30-
promiseMutex(async () => {
31-
await markAllAsRead(listView);
32-
}),
33-
);
34-
});
29+
export function setup(button: HTMLElement, listView?: HTMLElement): void {
30+
button.addEventListener(
31+
"click",
32+
promiseMutex(async () => {
33+
await markAllAsRead(button, listView);
34+
}),
35+
);
3536
}

wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Article/MarkAllAsRead.js renamed to wcfsetup/install/files/js/WoltLabSuite/Core/Component/Article/MarkAllArticlesAsRead.js

Lines changed: 10 additions & 10 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
@@ -194,6 +194,7 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) {
194194
$event->register(new \wcf\system\endpoint\controller\core\articles\PublishArticle());
195195
$event->register(new \wcf\system\endpoint\controller\core\articles\UnpublishArticle());
196196
$event->register(new \wcf\system\endpoint\controller\core\articles\MarkAllArticlesAsRead());
197+
$event->register(new \wcf\system\endpoint\controller\core\articles\MarkArticleAsRead());
197198
$event->register(new \wcf\system\endpoint\controller\core\articles\contents\GetArticleContentHeaderTitle());
198199
$event->register(new \wcf\system\endpoint\controller\core\attachments\DeleteAttachment());
199200
$event->register(new \wcf\system\endpoint\controller\core\cronjobs\EnableCronjob());
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\http\Helper;
10+
use wcf\system\endpoint\IController;
11+
use wcf\system\endpoint\PostRequest;
12+
use wcf\system\exception\IllegalLinkException;
13+
use wcf\system\exception\PermissionDeniedException;
14+
use wcf\system\WCF;
15+
16+
/**
17+
* Marks the article with the given ID as read for the current user.
18+
*
19+
* @author Marcel Werk
20+
* @copyright 2001-2026 WoltLab GmbH
21+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22+
* @since 6.3
23+
*/
24+
#[PostRequest('/core/articles/{id:\d+}/mark-as-read')]
25+
final class MarkArticleAsRead implements IController
26+
{
27+
#[\Override]
28+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
29+
{
30+
if (!MODULE_ARTICLE) {
31+
throw new IllegalLinkException();
32+
}
33+
34+
$article = Helper::fetchObjectFromRequestParameter($variables['id'], Article::class);
35+
$this->assertArticleIsAccessible($article);
36+
37+
(new \wcf\command\article\MarkArticleAsRead($article))();
38+
39+
return new JsonResponse([]);
40+
}
41+
42+
private function assertArticleIsAccessible(Article $article): void
43+
{
44+
if (!WCF::getUser()->userID || !$article->canRead()) {
45+
throw new PermissionDeniedException();
46+
}
47+
}
48+
}

wcfsetup/install/files/lib/system/listView/user/ArticleListView.class.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public function __construct()
5757
$this->setDefaultSortOrder(\ARTICLE_SORT_ORDER);
5858
$this->setCssClassName('entryCardList articleList');
5959
$this->setContainerCssClassName('entryCardList__container');
60+
$this->setMarkAsReadEndpoint('core/articles/%s/mark-as-read');
6061
}
6162

6263
#[\Override]
@@ -184,4 +185,13 @@ protected function getInitializedEvent(): ArticleListViewInitialized
184185
{
185186
return new ArticleListViewInitialized($this);
186187
}
188+
189+
public function canMarkAsRead(): bool
190+
{
191+
if (!WCF::getUser()->userID) {
192+
return false;
193+
}
194+
195+
return ViewableArticle::getUnreadArticles() > 0;
196+
}
187197
}

0 commit comments

Comments
 (0)