Skip to content

Commit e6f2a06

Browse files
committed
Refresh items after doing interactions
1 parent 51931fd commit e6f2a06

File tree

10 files changed

+320
-112
lines changed

10 files changed

+320
-112
lines changed
Lines changed: 102 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,126 @@
11
{if !$disableAds|isset}{assign var='disableAds' value=false}{/if}
22

3-
<div class="contentItemList">
4-
{foreach from=$view->getItems() item='article' name='articles'}
5-
{if $article->getArticleContent()}
6-
<article class="contentItem contentItemMultiColumn listView__item" data-object-id="{$article->getObjectID()}">
7-
<div class="contentItemOptions">
8-
{unsafe:$view->renderInteractionContextMenuButton($article)}
3+
{foreach from=$view->getItems() item='article' name='articles'}
4+
{if $article->getArticleContent()}
5+
<article class="contentItem contentItemMultiColumn listView__item" data-object-id="{$article->getObjectID()}">
6+
<div class="contentItemOptions">
7+
{unsafe:$view->renderInteractionContextMenuButton($article)}
8+
</div>
9+
10+
<div class="contentItemLink">
11+
<div class="contentItemImage contentItemImageLarge">
12+
<img
13+
class="contentItemImageElement"
14+
src="{if $article->getTeaserImage()}{$article->getTeaserImage()->getThumbnailLink('medium')}{else}{$__wcf->getStyleHandler()->getStyle()->getCoverPhotoURL()}{/if}"
15+
height="{if $article->getTeaserImage()}{@$article->getTeaserImage()->getThumbnailHeight('medium')}{else}{@$__wcf->getStyleHandler()->getStyle()->getCoverPhotoHeight()}{/if}"
16+
width="{if $article->getTeaserImage()}{@$article->getTeaserImage()->getThumbnailWidth('medium')}{else}{@$__wcf->getStyleHandler()->getStyle()->getCoverPhotoWidth()}{/if}"
17+
loading="lazy"
18+
alt="">
19+
20+
{hascontent}
21+
<div class="contentItemBadges">
22+
{content}
23+
{if $article->isDeleted}<span class="badge label red contentItemBadge contentItemBadgeIsDeleted">{lang}wcf.message.status.deleted{/lang}</span>{/if}
24+
{if !$article->isPublished()}<span class="badge label green contentItemBadge contentItemBadgeIsDisabled">{lang}wcf.message.status.disabled{/lang}</span>{/if}
25+
{if $article->isNew()}<span class="badge label contentItemBadge contentItemBadgeNew">{lang}wcf.message.new{/lang}</span>{/if}
26+
27+
{event name='contentItemBadges'}
28+
{/content}
29+
</div>
30+
{/hascontent}
931
</div>
1032

11-
<div class="contentItemLink">
12-
<div class="contentItemImage contentItemImageLarge">
13-
<img
14-
class="contentItemImageElement"
15-
src="{if $article->getTeaserImage()}{$article->getTeaserImage()->getThumbnailLink('medium')}{else}{$__wcf->getStyleHandler()->getStyle()->getCoverPhotoURL()}{/if}"
16-
height="{if $article->getTeaserImage()}{@$article->getTeaserImage()->getThumbnailHeight('medium')}{else}{@$__wcf->getStyleHandler()->getStyle()->getCoverPhotoHeight()}{/if}"
17-
width="{if $article->getTeaserImage()}{@$article->getTeaserImage()->getThumbnailWidth('medium')}{else}{@$__wcf->getStyleHandler()->getStyle()->getCoverPhotoWidth()}{/if}"
18-
loading="lazy"
19-
alt="">
20-
21-
{hascontent}
22-
<div class="contentItemBadges">
23-
{content}
24-
{if $article->isDeleted}<span class="badge label red contentItemBadge contentItemBadgeIsDeleted">{lang}wcf.message.status.deleted{/lang}</span>{/if}
25-
{if !$article->isPublished()}<span class="badge label green contentItemBadge contentItemBadgeIsDisabled">{lang}wcf.message.status.disabled{/lang}</span>{/if}
26-
{if $article->isNew()}<span class="badge label contentItemBadge contentItemBadgeNew">{lang}wcf.message.new{/lang}</span>{/if}
27-
28-
{event name='contentItemBadges'}
29-
{/content}
30-
</div>
31-
{/hascontent}
32-
</div>
33-
34-
<div class="contentItemContent">
35-
{if $article->hasLabels()}
36-
<div class="contentItemLabels">
37-
{foreach from=$article->getLabels() item=label}
38-
{@$label->render('contentItemLabel')}
39-
{/foreach}
40-
</div>
41-
{/if}
42-
43-
<h2 class="contentItemTitle"><a href="{$article->getLink()}" class="contentItemTitleLink">{$article->getTitle()}</a></h2>
44-
45-
<div class="contentItemDescription">
46-
{@$article->getFormattedTeaser()}
33+
<div class="contentItemContent">
34+
{if $article->hasLabels()}
35+
<div class="contentItemLabels">
36+
{foreach from=$article->getLabels() item=label}
37+
{@$label->render('contentItemLabel')}
38+
{/foreach}
4739
</div>
40+
{/if}
41+
42+
<h2 class="contentItemTitle"><a href="{$article->getLink()}" class="contentItemTitleLink">{$article->getTitle()}</a></h2>
43+
44+
<div class="contentItemDescription">
45+
{@$article->getFormattedTeaser()}
4846
</div>
4947
</div>
48+
</div>
49+
50+
<div class="contentItemMeta">
51+
<span class="contentItemMetaImage">
52+
{@$article->getUserProfile()->getAvatar()->getImageTag(32)}
53+
</span>
5054

51-
<div class="contentItemMeta">
52-
<span class="contentItemMetaImage">
53-
{@$article->getUserProfile()->getAvatar()->getImageTag(32)}
54-
</span>
55-
56-
<div class="contentItemMetaContent">
57-
<div class="contentItemMetaAuthor">
58-
{@$article->getUserProfile()->getFormattedUsername()}
55+
<div class="contentItemMetaContent">
56+
<div class="contentItemMetaAuthor">
57+
{@$article->getUserProfile()->getFormattedUsername()}
58+
</div>
59+
<div class="contentItemMetaTime">
60+
{@$article->time|time}
61+
</div>
62+
</div>
63+
64+
<div class="contentItemMetaIcons">
65+
{if MODULE_LIKE && $__wcf->getSession()->getPermission('user.like.canViewLike') && $article->cumulativeLikes}
66+
<div class="contentItemMetaIcon">
67+
{include file='shared_topReaction' cachedReactions=$article->cachedReactions render='short'}
5968
</div>
60-
<div class="contentItemMetaTime">
61-
{@$article->time|time}
69+
{/if}
70+
{if $article->getDiscussionProvider()->getDiscussionCountPhrase()}{* empty phrase indicates that comments are disabled *}
71+
<div class="contentItemMetaIcon">
72+
{icon name='comments'}
73+
<span aria-label="{$article->getDiscussionProvider()->getDiscussionCountPhrase()}">
74+
{$article->getDiscussionProvider()->getDiscussionCount()}
75+
</span>
6276
</div>
63-
</div>
64-
65-
<div class="contentItemMetaIcons">
66-
{if MODULE_LIKE && $__wcf->getSession()->getPermission('user.like.canViewLike') && $article->cumulativeLikes}
67-
<div class="contentItemMetaIcon">
68-
{include file='shared_topReaction' cachedReactions=$article->cachedReactions render='short'}
69-
</div>
70-
{/if}
71-
{if $article->getDiscussionProvider()->getDiscussionCountPhrase()}{* empty phrase indicates that comments are disabled *}
72-
<div class="contentItemMetaIcon">
73-
{icon name='comments'}
74-
<span aria-label="{$article->getDiscussionProvider()->getDiscussionCountPhrase()}">
75-
{$article->getDiscussionProvider()->getDiscussionCount()}
76-
</span>
77-
</div>
78-
{/if}
77+
{/if}
7978

80-
{event name='contentItemMetaIcons'}
81-
</div>
79+
{event name='contentItemMetaIcons'}
8280
</div>
83-
</article>
84-
{/if}
85-
86-
{if MODULE_WCF_AD && !$disableAds}
87-
{if $tpl[foreach][articles][iteration] === 1}
81+
</div>
82+
</article>
83+
{/if}
84+
85+
{if MODULE_WCF_AD && !$disableAds}
86+
{if $tpl[foreach][articles][iteration] === 1}
87+
{hascontent}
88+
<div class="contentItem contentItemAd">
89+
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.after1stArticle')}{/content}
90+
</div>
91+
{/hascontent}
92+
{else}
93+
{if $tpl[foreach][articles][iteration] % 2 === 0}
8894
{hascontent}
8995
<div class="contentItem contentItemAd">
90-
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.after1stArticle')}{/content}
96+
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery2ndArticle')}{/content}
97+
</div>
98+
{/hascontent}
99+
{/if}
100+
101+
{if $tpl[foreach][articles][iteration] % 3 === 0}
102+
{hascontent}
103+
<div class="contentItem contentItemAd">
104+
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery3rdArticle')}{/content}
105+
</div>
106+
{/hascontent}
107+
{/if}
108+
109+
{if $tpl[foreach][articles][iteration] % 5 === 0}
110+
{hascontent}
111+
<div class="contentItem contentItemAd">
112+
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery5thArticle')}{/content}
91113
</div>
92114
{/hascontent}
93-
{else}
94-
{if $tpl[foreach][articles][iteration] % 2 === 0}
95-
{hascontent}
96-
<div class="contentItem contentItemAd">
97-
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery2ndArticle')}{/content}
98-
</div>
99-
{/hascontent}
100-
{/if}
101-
102-
{if $tpl[foreach][articles][iteration] % 3 === 0}
103-
{hascontent}
104-
<div class="contentItem contentItemAd">
105-
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery3rdArticle')}{/content}
106-
</div>
107-
{/hascontent}
108-
{/if}
109115

110-
{if $tpl[foreach][articles][iteration] % 5 === 0}
116+
{if $tpl[foreach][articles][iteration] % 10 === 0}
111117
{hascontent}
112118
<div class="contentItem contentItemAd">
113-
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery5thArticle')}{/content}
119+
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery10thArticle')}{/content}
114120
</div>
115121
{/hascontent}
116-
117-
{if $tpl[foreach][articles][iteration] % 10 === 0}
118-
{hascontent}
119-
<div class="contentItem contentItemAd">
120-
{content}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.article.afterEvery10thArticle')}{/content}
121-
</div>
122-
{/hascontent}
123-
{/if}
124122
{/if}
125123
{/if}
126124
{/if}
127-
{/foreach}
128-
</div>
125+
{/if}
126+
{/foreach}

com.woltlab.wcf/templates/shared_listView.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
{/if}
4343

4444
<div class="listView__itemContainer">
45-
<div class="listView__items" id="{$view->getID()}_items"{if !$view->countItems()} hidden{/if}>
45+
<div class="listView__items {$view->getCssClassName()}" id="{$view->getID()}_items"{if !$view->countItems()} hidden{/if}>
4646
{unsafe:$view->renderItems()}
4747
</div>
4848
</div>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Gets a single item for rendering in a list view.
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 "../Result";
12+
13+
type Response = {
14+
template: string;
15+
};
16+
17+
export async function getItem(
18+
listViewClass: string,
19+
objectId: string | number,
20+
listViewParameters?: Map<string, string>,
21+
): Promise<ApiResult<Response>> {
22+
const url = new URL(`${window.WSC_RPC_API_URL}core/list-views/item`);
23+
url.searchParams.set("listView", listViewClass);
24+
url.searchParams.set("objectID", objectId.toString());
25+
if (listViewParameters) {
26+
listViewParameters.forEach((value, key) => {
27+
url.searchParams.set(`listViewParameters[${key}]`, value);
28+
});
29+
}
30+
31+
let response: Response;
32+
try {
33+
response = (await prepareRequest(url).get().allowCaching().disableLoadingIndicator().fetchAsJson()) as Response;
34+
} catch (e) {
35+
return apiResultFromError(e);
36+
}
37+
38+
return apiResultFromValue(response);
39+
}

ts/WoltLabSuite/Core/Component/ListView.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import State, { StateChangeCause } from "./ListView/State";
22
import { trigger as triggerDomChange } from "../Dom/Change/Listener";
3-
import { setInnerHtml } from "../Dom/Util";
3+
import { setInnerHtml, createFragmentFromHtml } from "../Dom/Util";
44
import { getItems } from "../Api/ListViews/GetItems";
55
import { element as scrollToElement } from "WoltLabSuite/Core/Ui/Scroll";
66
import { wheneverFirstSeen } from "../Helper/Selector";
77
import UiDropdownSimple from "../Ui/Dropdown/Simple";
8+
import { getItem } from "../Api/ListViews/GetItem";
89

910
export class ListView {
1011
readonly #viewClassName: string;
@@ -26,6 +27,7 @@ export class ListView {
2627

2728
this.#initInteractions();
2829
this.#state = this.#setupState(viewId, pageNo, baseUrl, sortField, sortOrder);
30+
this.#initEventListeners();
2931
}
3032

3133
#setupState(viewId: string, pageNo: number, baseUrl: string, sortField: string, sortOrder: string): State {
@@ -63,6 +65,15 @@ export class ListView {
6365
triggerDomChange();
6466
}
6567

68+
async #refreshItem(item: HTMLElement): Promise<void> {
69+
const response = (
70+
await getItem(this.#viewClassName, item.dataset.objectId! /*, this.#gridViewParameters*/)
71+
).unwrap();
72+
item.replaceWith(createFragmentFromHtml(response.template));
73+
this.#state.refreshSelection();
74+
triggerDomChange();
75+
}
76+
6677
#initInteractions(): void {
6778
wheneverFirstSeen(`#${this.#viewElement.id} .listView__item`, (item) => {
6879
item.querySelectorAll<HTMLElement>(".dropdownToggle").forEach((element) => {
@@ -84,4 +95,23 @@ export class ListView {
8495
});
8596
});
8697
}
98+
99+
#initEventListeners(): void {
100+
this.#viewElement.addEventListener("interaction:invalidate-all", () => {
101+
void this.#loadItems(StateChangeCause.Change);
102+
});
103+
104+
this.#viewElement.addEventListener("interaction:invalidate", (event) => {
105+
void this.#refreshItem(event.target as HTMLElement);
106+
});
107+
108+
this.#viewElement.addEventListener("interaction:remove", (event) => {
109+
(event.target as HTMLElement).remove();
110+
// this.#checkEmptyTable();
111+
});
112+
113+
this.#viewElement.addEventListener("interaction:reset-selection", () => {
114+
this.#state.resetSelection();
115+
});
116+
}
87117
}

wcfsetup/install/files/js/WoltLabSuite/Core/Api/ListViews/GetItem.js

Lines changed: 31 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)