Skip to content

Commit 1415eec

Browse files
authored
Merge pull request #6666 from WoltLab/63-reaction-summary-dialog
Overhaul reaction summary dialog
2 parents f981d61 + 78a86b1 commit 1415eec

23 files changed

Lines changed: 637 additions & 49 deletions
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<button
2+
type="button"
3+
class="button small{if !$reactionTypeID} active{/if}"
4+
data-filter-reaction-type-id="0"
5+
data-list-view-id="{$view->getID()}"
6+
>
7+
<span>{lang}wcf.like.reaction.all{/lang}</span>
8+
<span class="badge">{#$totalCount}</span>
9+
</button>
10+
11+
{foreach from=$reactionTypes item='reactionType'}
12+
<button
13+
type="button"
14+
class="button small{if $reactionType->reactionTypeID === $reactionTypeID} active{/if}"
15+
data-filter-reaction-type-id="{$reactionType->reactionTypeID}"
16+
data-list-view-id="{$view->getID()}"
17+
>
18+
{unsafe:$reactionType->renderIcon()}
19+
<span>{$reactionType->getTitle()}</span>
20+
<span class="badge">{#$reactionCounts[$reactionType->reactionTypeID]}</span>
21+
</button>
22+
{/foreach}
23+
24+
<script data-relocate="true">
25+
require(['WoltLabSuite/Core/Component/Reaction/SummaryDetailsFilterButtons'], ({ setup }) => {
26+
setup('{unsafe:$view->getID()|encodeJS}');
27+
});
28+
</script>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{foreach from=$view->getItems() item='reaction'}
2+
{assign var='user' value=$reaction->getUserProfile()}
3+
<div class="simpleUserList__item listView__item" data-object-id="{$reaction->getObjectID()}">
4+
<div class="simpleUserList__item__avatar">
5+
{unsafe:$user->getAvatar()->getImageTag(96)}
6+
</div>
7+
8+
<div class="simpleUserList__item__content">
9+
<div class="simpleUserList__item__title">
10+
<h3 class="simpleUserList__item__username">
11+
<a href="{$user->getLink()}" class="simpleUserList__item__link userLink" data-object-id="{$user->userID}">{unsafe:$user->getFormattedUsername()}</a>
12+
</h3>
13+
{if MODULE_USER_RANK && $user->getUserTitle()}
14+
<span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>
15+
{/if}
16+
</div>
17+
<div class="simpleUserList__item__description">
18+
{time time=$reaction->time}
19+
</div>
20+
</div>
21+
22+
<div class="simpleUserList__item__extra">
23+
{unsafe:$reaction->render()}
24+
25+
<div class="simpleUserList__item__interactions">
26+
{unsafe:$view->renderInteractionContextMenuButton($reaction)}
27+
</div>
28+
</div>
29+
</div>
30+
{/foreach}

com.woltlab.wcf/templates/shared_listView.tpl

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<div class="listView">
2-
{if $view->isSortable() || $view->isFilterable() || $view->hasBulkInteractions()}
2+
{if $view->isSortable() || $view->isFilterable() || $view->hasBulkInteractions() || $view->getAdditionalHeaderContent()}
33
<div class="listView__header">
4+
{if $view->getAdditionalHeaderContent()}
5+
<div class="listView__header__additionalContent">
6+
{unsafe:$view->getAdditionalHeaderContent()}
7+
</div>
8+
{/if}
9+
410
{if $view->isFilterable()}
511
<div class="listView__filters" id="{$view->getID()}_filters">
612
{foreach from=$view->getActiveFilters() item='value' key='key'}
@@ -17,53 +23,57 @@
1723
{/foreach}
1824
</div>
1925
{/if}
20-
<div class="listView__header__buttons">
21-
{if $view->hasAvailableInteractions()}
22-
<div class="listView__header__button">
23-
<button type="button" class="button small listView__editMode__toggle">
24-
{icon name='pencil'}
25-
<span>{lang}wcf.global.button.edit{/lang}</span>
26-
</button>
26+
{hascontent}
27+
<div class="listView__header__buttons">
28+
{content}
29+
{if $view->hasAvailableInteractions()}
30+
<div class="listView__header__button">
31+
<button type="button" class="button small listView__editMode__toggle">
32+
{icon name='pencil'}
33+
<span>{lang}wcf.global.button.edit{/lang}</span>
34+
</button>
2735

28-
{if $view->hasBulkInteractions()}
29-
<label class="listView__selectAllItems__label jsTooltip" title="{lang}wcf.clipboard.item.markAll{/lang}">
30-
<input type="checkbox" id="{$view->getID()}_selectAllItems" class="listView__selectAllItems"
31-
aria-label="{lang}wcf.clipboard.item.markAll{/lang}">
32-
</label>
36+
{if $view->hasBulkInteractions()}
37+
<label class="listView__selectAllItems__label jsTooltip" title="{lang}wcf.clipboard.item.markAll{/lang}">
38+
<input type="checkbox" id="{$view->getID()}_selectAllItems" class="listView__selectAllItems"
39+
aria-label="{lang}wcf.clipboard.item.markAll{/lang}">
40+
</label>
41+
{/if}
42+
</div>
3343
{/if}
34-
</div>
35-
{/if}
36-
{if $view->isSortable()}
37-
<div class="listView__header__button dropdown">
38-
<button type="button" class="button small dropdownToggle">
39-
{icon name='arrow-down-short-wide'}
40-
<span>{lang}wcf.global.sorting{/lang}</span>
41-
</button>
42-
<ul class="dropdownMenu" id="{$view->getID()}_sorting">
43-
{foreach from=$view->getAvailableSortFields() item='sortField'}
44-
<li>
45-
<button type="button" class="listView__sorting__button" data-sort-id="{$sortField->id}">
46-
{unsafe:$sortField}
47-
</button>
48-
</li>
49-
{/foreach}
50-
</ul>
51-
</div>
52-
{/if}
53-
{if $view->isFilterable()}
54-
<div class="listView__header__button">
55-
<button type="button" class="button small" id="{$view->getID()}_filterButton" data-endpoint="{$view->getFilterActionEndpoint()}">
56-
{icon name='sliders'}
57-
{lang}wcf.global.filter{/lang}
58-
</button>
59-
</div>
60-
{/if}
61-
{if $view->getPrimaryButton()}
62-
<div class="listView__header__button">
63-
{unsafe:$view->getPrimaryButton()->render()}
64-
</div>
65-
{/if}
66-
</div>
44+
{if $view->isSortable()}
45+
<div class="listView__header__button dropdown">
46+
<button type="button" class="button small dropdownToggle">
47+
{icon name='arrow-down-short-wide'}
48+
<span>{lang}wcf.global.sorting{/lang}</span>
49+
</button>
50+
<ul class="dropdownMenu" id="{$view->getID()}_sorting">
51+
{foreach from=$view->getAvailableSortFields() item='sortField'}
52+
<li>
53+
<button type="button" class="listView__sorting__button" data-sort-id="{$sortField->id}">
54+
{unsafe:$sortField}
55+
</button>
56+
</li>
57+
{/foreach}
58+
</ul>
59+
</div>
60+
{/if}
61+
{if $view->isFilterable()}
62+
<div class="listView__header__button">
63+
<button type="button" class="button small" id="{$view->getID()}_filterButton" data-endpoint="{$view->getFilterActionEndpoint()}">
64+
{icon name='sliders'}
65+
{lang}wcf.global.filter{/lang}
66+
</button>
67+
</div>
68+
{/if}
69+
{if $view->getPrimaryButton()}
70+
<div class="listView__header__button">
71+
{unsafe:$view->getPrimaryButton()->render()}
72+
</div>
73+
{/if}
74+
{/content}
75+
</div>
76+
{/hascontent}
6777
</div>
6878
{/if}
6979

ts/WoltLabSuite/Core/BootstrapFrontend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export function setup(options: BootstrapOptions): void {
148148
}
149149

150150
whenFirstSeen("woltlab-core-reaction-summary", () => {
151-
void import("./Ui/Reaction/SummaryDetails").then(({ setup }) => setup());
151+
void import("./Component/Reaction/SummaryDetails").then(({ setup }) => setup());
152152
});
153153
whenFirstSeen("woltlab-core-comment", () => {
154154
void import("./Component/Comment/woltlab-core-comment");

ts/WoltLabSuite/Core/Component/ListView.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ export class ListView {
128128
this.#viewElement.addEventListener("interaction:reset-selection", () => {
129129
this.#state.resetSelection();
130130
});
131+
132+
this.#viewElement.addEventListener("interaction:set-parameters", (event: CustomEvent) => {
133+
for (const key of Object.keys(event.detail)) {
134+
if (this.#listViewParameters === undefined) {
135+
this.#listViewParameters = new Map();
136+
}
137+
138+
if (event.detail[key] === undefined) {
139+
this.#listViewParameters.delete(key);
140+
} else {
141+
this.#listViewParameters.set(key, event.detail[key]);
142+
}
143+
}
144+
145+
void this.#loadItems(StateChangeCause.Change);
146+
});
131147
}
132148

133149
#setupState(
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Handles the reaction summary details dialog.
3+
*
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
8+
*/
9+
10+
import { getPhrase } from "WoltLabSuite/Core/Language";
11+
import { dialogFactory } from "../../Component/Dialog";
12+
import { wheneverFirstSeen } from "../../Helper/Selector";
13+
import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
14+
15+
export function setup(): void {
16+
wheneverFirstSeen("woltlab-core-reaction-summary", (element: WoltlabCoreReactionSummaryElement) => {
17+
element.addEventListener(
18+
"showDetails",
19+
promiseMutex(() => {
20+
return dialogFactory()
21+
.usingListView()
22+
.fromPreset(
23+
getPhrase("wcf.reactions.summary.title"),
24+
"wcf\\system\\listView\\user\\ReactionSummaryDetailsListView",
25+
1,
26+
"",
27+
"ASC",
28+
undefined,
29+
new Map([
30+
["objectID", element.objectId.toString()],
31+
["objectType", element.objectType],
32+
]),
33+
);
34+
}),
35+
);
36+
});
37+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Handles the filter buttons in the reaction summary details dialog.
3+
*
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
8+
*/
9+
10+
export function setup(listViewId: string): void {
11+
document
12+
.querySelectorAll<HTMLButtonElement>(`[data-filter-reaction-type-id][data-list-view-id="${listViewId}"]`)
13+
.forEach((button) => {
14+
button.addEventListener("click", () => {
15+
if (button.classList.contains("active")) {
16+
return;
17+
}
18+
19+
document
20+
.querySelectorAll<HTMLButtonElement>(`[data-filter-reaction-type-id][data-list-view-id="${listViewId}"]`)
21+
.forEach((button) => {
22+
button.classList.remove("active");
23+
});
24+
25+
button.classList.add("active");
26+
27+
let reactionTypeID: string | undefined = undefined;
28+
if (button.dataset.filterReactionTypeId && button.dataset.filterReactionTypeId !== "0") {
29+
reactionTypeID = button.dataset.filterReactionTypeId;
30+
}
31+
const listView = document.getElementById(`${listViewId}_items`);
32+
listView!.dispatchEvent(new CustomEvent("interaction:set-parameters", { detail: { reactionTypeID } }));
33+
});
34+
});
35+
}

ts/WoltLabSuite/Core/Ui/Reaction/SummaryDetails.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @copyright 2001-2022 WoltLab GmbH
66
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
77
* @since 6.0
8+
* @deprecated 6.3
89
*/
910

1011
import { dboAction } from "../../Ajax";

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

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

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

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

0 commit comments

Comments
 (0)