Skip to content

Commit 51931fd

Browse files
committed
Add support for interactions
1 parent e12e41a commit 51931fd

File tree

9 files changed

+234
-17
lines changed

9 files changed

+234
-17
lines changed

com.woltlab.wcf/templates/articleListItems.tpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
<div class="contentItemList">
44
{foreach from=$view->getItems() item='article' name='articles'}
55
{if $article->getArticleContent()}
6-
<article class="contentItem contentItemMultiColumn">
6+
<article class="contentItem contentItemMultiColumn listView__item" data-object-id="{$article->getObjectID()}">
7+
<div class="contentItemOptions">
8+
{unsafe:$view->renderInteractionContextMenuButton($article)}
9+
</div>
10+
711
<div class="contentItemLink">
812
<div class="contentItemImage contentItemImageLarge">
913
<img
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
{if $contextMenuOptions}
2-
<div class="dropdown">
3-
<button type="button" class="button small dropdownToggle" aria-label="{lang}wcf.global.button.more{/lang}">
4-
{icon name='ellipsis-vertical'}
5-
</button>
6-
7-
<ul class="dropdownMenu">
8-
{unsafe:$contextMenuOptions}
9-
</ul>
10-
</div>
11-
{else}
12-
<button type="button" disabled class="button small" aria-label="{lang}wcf.global.button.more{/lang}">
1+
<div class="dropdown">
2+
<button type="button" class="button small dropdownToggle" aria-label="{lang}wcf.global.button.more{/lang}">
133
{icon name='ellipsis-vertical'}
144
</button>
15-
{/if}
5+
6+
<ul class="dropdownMenu">
7+
{unsafe:$contextMenuOptions}
8+
</ul>
9+
</div>

com.woltlab.wcf/templates/shared_listView.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@
8787
);
8888
});
8989
</script>
90-
{*if $view->hasInteractions()}
90+
{if $view->hasInteractions()}
9191
{unsafe:$view->renderInteractionInitialization()}
9292
{/if}
93-
{if $view->hasBulkInteractions()}
93+
{*if $view->hasBulkInteractions()}
9494
{unsafe:$view->renderBulkInteractionInitialization()}
9595
{/if*}

ts/WoltLabSuite/Core/Component/ListView.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { trigger as triggerDomChange } from "../Dom/Change/Listener";
33
import { setInnerHtml } from "../Dom/Util";
44
import { getItems } from "../Api/ListViews/GetItems";
55
import { element as scrollToElement } from "WoltLabSuite/Core/Ui/Scroll";
6+
import { wheneverFirstSeen } from "../Helper/Selector";
7+
import UiDropdownSimple from "../Ui/Dropdown/Simple";
68

79
export class ListView {
810
readonly #viewClassName: string;
@@ -22,6 +24,7 @@ export class ListView {
2224
this.#viewElement = document.getElementById(`${viewId}_items`) as HTMLTableElement;
2325
this.#noItemsNotice = document.getElementById(`${viewId}_noItemsNotice`) as HTMLElement;
2426

27+
this.#initInteractions();
2528
this.#state = this.#setupState(viewId, pageNo, baseUrl, sortField, sortOrder);
2629
}
2730

@@ -59,4 +62,26 @@ export class ListView {
5962

6063
triggerDomChange();
6164
}
65+
66+
#initInteractions(): void {
67+
wheneverFirstSeen(`#${this.#viewElement.id} .listView__item`, (item) => {
68+
item.querySelectorAll<HTMLElement>(".dropdownToggle").forEach((element) => {
69+
let dropdown = UiDropdownSimple.getDropdownMenu(element.dataset.target!);
70+
if (!dropdown) {
71+
dropdown = element.closest(".dropdown")!.querySelector<HTMLElement>(".dropdownMenu")!;
72+
}
73+
74+
dropdown?.querySelectorAll<HTMLButtonElement>("[data-interaction]").forEach((element) => {
75+
element.addEventListener("click", () => {
76+
item.dispatchEvent(
77+
new CustomEvent("interaction:execute", {
78+
detail: element.dataset,
79+
bubbles: true,
80+
}),
81+
);
82+
});
83+
});
84+
});
85+
});
86+
}
6287
}

wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView.js

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/lib/system/interaction/InteractionContextMenuView.class.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public function renderContextMenuOptions(DatabaseObject $object): string
3030

3131
public function renderButton(DatabaseObject $object): string
3232
{
33+
$options = $this->renderContextMenuOptions($object);
34+
if (!$options) {
35+
return '';
36+
}
37+
3338
return WCF::getTPL()->render(
3439
'wcf',
3540
'shared_interactionButton',
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace wcf\system\interaction\user;
4+
5+
use wcf\data\article\Article;
6+
use wcf\data\article\ViewableArticle;
7+
use wcf\event\interaction\admin\ArticleInteractionCollecting;
8+
use wcf\form\ArticleEditForm;
9+
use wcf\system\event\EventHandler;
10+
use wcf\system\interaction\AbstractInteractionProvider;
11+
use wcf\system\interaction\DeleteInteraction;
12+
use wcf\system\interaction\Divider;
13+
use wcf\system\interaction\EditInteraction;
14+
use wcf\system\interaction\LinkableObjectInteraction;
15+
use wcf\system\interaction\RestoreInteraction;
16+
use wcf\system\interaction\RpcInteraction;
17+
use wcf\system\interaction\TrashInteraction;
18+
19+
/**
20+
* Interaction provider for articles.
21+
*
22+
* @author Marcel Werk
23+
* @copyright 2001-2025 WoltLab GmbH
24+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
25+
* @since 6.2
26+
*/
27+
final class ArticleInteractions extends AbstractInteractionProvider
28+
{
29+
public function __construct()
30+
{
31+
$this->addInteractions([
32+
new TrashInteraction('core/articles/%s/trash', function (ViewableArticle $article): bool {
33+
if (!$article->canDelete()) {
34+
return false;
35+
}
36+
37+
return $article->isDeleted !== 1;
38+
}),
39+
new RestoreInteraction('core/articles/%s/restore', function (ViewableArticle $article): bool {
40+
if (!$article->canDelete()) {
41+
return false;
42+
}
43+
44+
return $article->isDeleted === 1;
45+
}),
46+
new DeleteInteraction('core/articles/%s', function (ViewableArticle $article): bool {
47+
if (!$article->canDelete()) {
48+
return false;
49+
}
50+
51+
return $article->isDeleted === 1;
52+
}),
53+
new RpcInteraction(
54+
'publish',
55+
'core/articles/%s/publish',
56+
'wcf.article.button.publish',
57+
isAvailableCallback: static function (ViewableArticle $article): bool {
58+
if (!$article->canPublish()) {
59+
return false;
60+
}
61+
62+
return $article->publicationStatus !== Article::PUBLISHED;
63+
}
64+
),
65+
new RpcInteraction(
66+
'unpublish',
67+
'core/articles/%s/unpublish',
68+
'wcf.article.button.unpublish',
69+
isAvailableCallback: static function (ViewableArticle $article): bool {
70+
if (!$article->canPublish()) {
71+
return false;
72+
}
73+
74+
return $article->publicationStatus === Article::PUBLISHED;
75+
}
76+
),
77+
new Divider(),
78+
new EditInteraction(ArticleEditForm::class, function (ViewableArticle $article): bool {
79+
return $article->canEdit();
80+
})
81+
]);
82+
83+
/*EventHandler::getInstance()->fire(
84+
new ArticleInteractionCollecting($this)
85+
);*/
86+
}
87+
88+
#[\Override]
89+
public function getObjectClassName(): string
90+
{
91+
return Article::class;
92+
}
93+
}

wcfsetup/install/files/lib/system/listView/AbstractListView.class.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use wcf\data\DatabaseObject;
77
use wcf\data\DatabaseObjectDecorator;
88
use wcf\data\DatabaseObjectList;
9+
use wcf\system\interaction\IInteractionProvider;
10+
use wcf\system\interaction\InteractionContextMenuView;
911
use wcf\system\listView\filter\IListViewFilter;
1012
use wcf\system\request\LinkHandler;
1113
use wcf\system\WCF;
@@ -29,6 +31,8 @@ abstract class AbstractListView
2931
private string $sortField = '';
3032
private string $sortOrder = 'ASC';
3133
private int $pageNo = 1;
34+
private ?IInteractionProvider $interactionProvider = null;
35+
private InteractionContextMenuView $interactionContextMenuView;
3236

3337
/**
3438
* @var array<string, string>
@@ -386,6 +390,73 @@ public function getFilterLabel(string $id): string
386390
return $this->availableFilters[$id]->getLabel() . ($value !== '' ? ': ' . $value : '');
387391
}
388392

393+
/**
394+
* Sets the interaction provider that is used to render the interaction context menu.
395+
*/
396+
public function setInteractionProvider(IInteractionProvider $provider): void
397+
{
398+
$this->interactionProvider = $provider;
399+
}
400+
401+
/**
402+
* Returns the interaction provider of the list view.
403+
*/
404+
public function getInteractionProvider(): ?IInteractionProvider
405+
{
406+
return $this->interactionProvider;
407+
}
408+
409+
/**
410+
* Returns true, if this list view has interactions.
411+
*/
412+
public function hasInteractions(): bool
413+
{
414+
return $this->interactionProvider !== null;
415+
}
416+
417+
/**
418+
* Renders the initialization code for the interactions of the list view.
419+
*/
420+
public function renderInteractionInitialization(): string
421+
{
422+
$code = '';
423+
if ($this->interactionProvider !== null) {
424+
$code = $this->getInteractionContextMenuView()->renderInitialization($this->getID() . '_items');
425+
}
426+
427+
return $code;
428+
}
429+
430+
/**
431+
* Returns the view of the interaction context menu.
432+
*/
433+
public function getInteractionContextMenuView(): InteractionContextMenuView
434+
{
435+
if ($this->interactionProvider === null) {
436+
throw new \BadMethodCallException("Missing interaction provider.");
437+
}
438+
439+
if (!isset($this->interactionContextMenuView)) {
440+
$this->interactionContextMenuView = new InteractionContextMenuView($this->interactionProvider);
441+
}
442+
443+
return $this->interactionContextMenuView;
444+
}
445+
446+
/**
447+
* Renders the interactions for the given row.
448+
*
449+
* @param TDatabaseObject $row
450+
*/
451+
public function renderInteractionContextMenuButton(DatabaseObject $row): string
452+
{
453+
if ($this->interactionProvider === null) {
454+
return '';
455+
}
456+
457+
return $this->getInteractionContextMenuView()->renderButton($row);
458+
}
459+
389460
public function render(): string
390461
{
391462
return WCF::getTPL()->render('wcf', 'shared_listView', ['view' => $this]);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
use wcf\data\DatabaseObjectList;
99
use wcf\data\label\group\ViewableLabelGroup;
1010
use wcf\data\object\type\ObjectTypeCache;
11+
use wcf\form\ArticleEditForm;
12+
use wcf\system\interaction\Divider;
13+
use wcf\system\interaction\EditInteraction;
14+
use wcf\system\interaction\user\ArticleInteractions;
1115
use wcf\system\label\LabelHandler;
1216
use wcf\system\listView\AbstractListView;
1317
use wcf\system\listView\filter\BooleanFilter;
@@ -47,6 +51,7 @@ public function __construct()
4751
}
4852
}
4953

54+
$this->setInteractionProvider(new ArticleInteractions());
5055
$this->setItemsPerPage(\ARTICLES_PER_PAGE);
5156
$this->setSortField('time');
5257
$this->setSortOrder(\ARTICLE_SORT_ORDER);

0 commit comments

Comments
 (0)