Skip to content

Commit a334145

Browse files
authored
Merge pull request #6306 from WoltLab/6.2-article-layout-overhaul
Article layout overhaul
2 parents 830765e + 9b9c3be commit a334145

File tree

24 files changed

+843
-288
lines changed

24 files changed

+843
-288
lines changed

com.woltlab.wcf/templates/article.tpl

Lines changed: 131 additions & 192 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<div class="contentHeaderTitle">
2+
<h1 class="contentTitle" itemprop="name headline">{$articleContent->title}</h1>
3+
<ul class="inlineList contentHeaderMetaData articleMetaData">
4+
<li itemprop="author" itemscope itemtype="http://schema.org/Person">
5+
{icon name='user'}
6+
{if $article->userID}
7+
<a href="{$article->getUserProfile()->getLink()}" class="userLink" data-object-id="{$article->userID}" itemprop="url">
8+
<span itemprop="name">{unsafe:$article->getUserProfile()->getFormattedUsername()}</span>
9+
</a>
10+
{else}
11+
<span itemprop="name">{$article->username}</span>
12+
{/if}
13+
</li>
14+
15+
<li>
16+
{icon name='clock'}
17+
<a href="{$article->getLink()}">{time time=$article->time}</a>
18+
<meta itemprop="datePublished" content="{$article->time|date:'c'}">
19+
</li>
20+
21+
{if $article->hasLabels()}
22+
<li>
23+
{icon name='tags'}
24+
<ul class="labelList">
25+
{foreach from=$article->getLabels() item=label}
26+
<li>{unsafe:$label->render()}</li>
27+
{/foreach}
28+
</ul>
29+
</li>
30+
{/if}
31+
32+
<li>
33+
{icon name='eye'}
34+
{lang}wcf.article.articleViews{/lang}
35+
</li>
36+
37+
{if $article->getDiscussionProvider()->getDiscussionCountPhrase()}
38+
<li itemprop="interactionStatistic" itemscope itemtype="http://schema.org/InteractionCounter">
39+
{icon name='comments'}
40+
{if $article->getDiscussionProvider()->getDiscussionLink()}<a href="{$article->getDiscussionProvider()->getDiscussionLink()}">{else}<span>{/if}
41+
{$article->getDiscussionProvider()->getDiscussionCountPhrase()}
42+
{if $article->getDiscussionProvider()->getDiscussionLink()}</a>{else}</span>{/if}
43+
<meta itemprop="interactionType" content="http://schema.org/CommentAction">
44+
<meta itemprop="userInteractionCount" content="{$article->getDiscussionProvider()->getDiscussionCount()}">
45+
</li>
46+
{/if}
47+
48+
{hascontent}
49+
<li>
50+
{icon name='flag'}
51+
{content}
52+
{if $article->isDeleted}
53+
<span class="badge red">{lang}wcf.message.status.deleted{/lang}</span>
54+
{/if}
55+
{if !$article->isPublished()}
56+
<span class="badge green">{lang}wcf.message.status.disabled{/lang}</span>
57+
{/if}
58+
{event name='contentHeaderMetaDataFlag'}
59+
{/content}
60+
</li>
61+
{/hascontent}
62+
63+
{event name='contentHeaderMetaData'}
64+
</ul>
65+
66+
<div itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
67+
<meta itemprop="name" content="{PAGE_TITLE|phrase}">
68+
<div itemprop="logo" itemscope itemtype="http://schema.org/ImageObject">
69+
<meta itemprop="url" content="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}">
70+
</div>
71+
</div>
72+
</div>

com.woltlab.wcf/templates/boxArticleList.tpl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
</li>
4040
{/foreach}
4141
</ul>
42-
{elseif $boxPosition == 'footerBoxes'}
42+
{else}
4343
<ul class="articleList">
4444
{foreach from=$boxArticleList item=boxArticle}
4545
<li>
@@ -69,6 +69,4 @@
6969
</li>
7070
{/foreach}
7171
</ul>
72-
{else}
73-
{include file='articleListItems' objects=$boxArticleList disableAds=true}
74-
{/if}
72+
{if}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{if $attachmentList && $attachmentList->getGroupedObjects($objectID)|count}
2+
{hascontent}
3+
<section class="entry__attachments__thumbnails">
4+
<h2 class="entry__attachments__title">{lang}wcf.attachment.images{/lang}</h2>
5+
6+
<ul class="inlineList">
7+
{content}
8+
{foreach from=$attachmentList->getGroupedObjects($objectID) item=attachment}
9+
{if $attachment->showAsImage() && !$attachment->isEmbedded()}
10+
<li class="attachmentThumbnail" data-attachment-id="{$attachment->attachmentID}">
11+
<a href="{$attachment->getLink()}"{if $attachment->canDownload()} data-fancybox="message-{$attachmentList->getObjectTypeName()}-{$objectID}" data-caption="{$attachment->filename}" aria-title="{lang}wcf.attachment.image.title{/lang}"{/if}>
12+
<div class="attachmentThumbnailContainer">
13+
<span class="attachmentThumbnailImage">
14+
{if $attachment->hasThumbnail()}
15+
<img
16+
src="{$attachment->getThumbnailLink('thumbnail')}"
17+
alt=""
18+
{if $attachment->thumbnailWidth >= ATTACHMENT_THUMBNAIL_WIDTH && $attachment->thumbnailHeight >= ATTACHMENT_THUMBNAIL_HEIGHT}
19+
class="attachmentThumbnailImageScalable"
20+
{/if}
21+
width="{$attachment->thumbnailWidth}"
22+
height="{$attachment->thumbnailHeight}"
23+
loading="lazy"
24+
>
25+
{else}
26+
<img
27+
src="{$attachment->getLink()}"
28+
alt=""
29+
{if $attachment->width >= ATTACHMENT_THUMBNAIL_WIDTH && $attachment->height >= ATTACHMENT_THUMBNAIL_HEIGHT}
30+
class="attachmentThumbnailImageScalable"
31+
{/if}
32+
width="{$attachment->width}"
33+
height="{$attachment->height}"
34+
loading="lazy"
35+
>
36+
{/if}
37+
</span>
38+
39+
<span class="attachmentThumbnailData">
40+
<span class="attachmentFilename">{$attachment->filename}</span>
41+
</span>
42+
</div>
43+
44+
<ul class="attachmentMetaData inlineList">
45+
<li>
46+
{icon name='file-lines'}
47+
{$attachment->filesize|filesize}
48+
</li>
49+
<li>
50+
{icon name='up-right-and-down-left-from-center'}
51+
{#$attachment->width} × {#$attachment->height}
52+
</li>
53+
</ul>
54+
</a>
55+
</li>
56+
{/if}
57+
{/foreach}
58+
{/content}
59+
</ul>
60+
</section>
61+
{/hascontent}
62+
63+
{hascontent}
64+
<section class="entry__attachments__files">
65+
<h2 class="entry__attachments__title">{lang}wcf.attachment.files{/lang}</h2>
66+
67+
<div class="messageAttachmentList">
68+
{content}
69+
{foreach from=$attachmentList->getGroupedObjects($objectID) item=attachment}
70+
{if $attachment->showAsFile() && !$attachment->isEmbedded()}
71+
<a href="{$attachment->getLink()}" class="messageAttachment jsTooltip" title="{lang}wcf.attachment.file.title{/lang}">
72+
<span class="messageAttachmentIcon">
73+
<span class="messageAttachmentIconDefault">
74+
{icon size=32 name=$attachment->getIconName()}
75+
</span>
76+
<span class="messageAttachmentIconDownload">
77+
{icon size=32 name='download'}
78+
</span>
79+
</span>
80+
<span class="messageAttachmentFilename">{$attachment->filename}</span>
81+
<span class="messageAttachmentMeta">{lang}wcf.attachment.file.info{/lang}</span>
82+
</a>
83+
{/if}
84+
{/foreach}
85+
{/content}
86+
</div>
87+
</section>
88+
{/hascontent}
89+
{/if}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{if !$tags|empty}
2+
<section class="entry_tags" aria-label="{lang}wcf.tagging.tags{/lang}">
3+
<ul class="tagList">
4+
{foreach from=$tags item=tag}
5+
<li>
6+
<a href="{link controller='Tagged' object=$tag objectType=$objectType}{/link}" class="tag">
7+
{icon name='tag'}
8+
{$tag->name}
9+
</a>
10+
</li>
11+
{/foreach}
12+
</ul>
13+
</section>
14+
{/if}

com.woltlab.wcf/templates/shared_listView.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="listView">
2-
{if $view->isSortable() || $view->isFilterable()}
2+
{if $view->isSortable() || $view->isFilterable() || $view->hasBulkInteractions()}
33
<div class="listView__header">
44
{if $view->isFilterable()}
55
<div class="listView__filters" id="{$view->getID()}_filters">

com.woltlab.wcf/templates/shared_standaloneInteractionButton.tpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
document.getElementById('{unsafe:$containerID|encodeJS}'),
2222
'{unsafe:$providerClassName|encodeJS}',
2323
'{unsafe:$objectID|encodeJS}',
24-
'{unsafe:$redirectUrl|encodeJS}'
24+
'{unsafe:$redirectUrl|encodeJS}',
25+
'{unsafe:$reloadHeaderEndpoint|encodeJS}'
2526
);
2627
});
2728
</script>

com.woltlab.wcf/templates/taggedArticleList.tpl

Lines changed: 0 additions & 3 deletions
This file was deleted.

ts/WoltLabSuite/Core/Bootstrap.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ function initA11y() {
6565
document
6666
.querySelectorAll("article:not([aria-label]):not([aria-labelledby]):not([role])")
6767
.forEach((element: HTMLElement) => {
68-
element.setAttribute("role", "presentation");
68+
if (!element.querySelector("h1, h2, h3")) {
69+
element.setAttribute("role", "presentation");
70+
}
6971
});
7072
}
7173

ts/WoltLabSuite/Core/Component/Interaction/StandaloneButton.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,33 @@
77
* @since 6.2
88
*/
99

10+
import { getObject } from "WoltLabSuite/Core/Api/GetObject";
1011
import { getContextMenuOptions } from "WoltLabSuite/Core/Api/Interactions/GetContextMenuOptions";
1112
import UiDropdownSimple from "WoltLabSuite/Core/Ui/Dropdown/Simple";
1213

14+
interface HeaderContent {
15+
template: string;
16+
}
17+
1318
export class StandaloneButton {
1419
#container: HTMLElement;
1520
#providerClassName: string;
1621
#objectId: string | number;
1722
#redirectUrl: string;
23+
#reloadHeaderEndpoint: string;
1824

19-
constructor(container: HTMLElement, providerClassName: string, objectId: string | number, redirectUrl: string) {
25+
constructor(
26+
container: HTMLElement,
27+
providerClassName: string,
28+
objectId: string | number,
29+
redirectUrl: string,
30+
reloadHeaderEndpoint: string,
31+
) {
2032
this.#container = container;
2133
this.#providerClassName = providerClassName;
2234
this.#objectId = objectId;
2335
this.#redirectUrl = redirectUrl;
36+
this.#reloadHeaderEndpoint = reloadHeaderEndpoint;
2437

2538
this.#initInteractions();
2639
this.#initEventListeners();
@@ -39,6 +52,24 @@ export class StandaloneButton {
3952
this.#initInteractions();
4053
}
4154

55+
async #refreshHeader(): Promise<void> {
56+
if (!this.#reloadHeaderEndpoint) {
57+
return;
58+
}
59+
60+
const header = document.querySelector(".contentHeaderTitle");
61+
if (!header) {
62+
return;
63+
}
64+
65+
const result = await getObject<HeaderContent>(`${window.WSC_RPC_API_URL}${this.#reloadHeaderEndpoint}`);
66+
if (!result.ok) {
67+
return;
68+
}
69+
70+
header.outerHTML = result.value.template;
71+
}
72+
4273
#getDropdownMenu(): HTMLElement | undefined {
4374
const button = this.#container.querySelector<HTMLButtonElement>(".dropdownToggle");
4475
if (!button) {
@@ -71,10 +102,12 @@ export class StandaloneButton {
71102
#initEventListeners(): void {
72103
this.#container.addEventListener("interaction:invalidate", () => {
73104
void this.#refreshContextMenu();
105+
void this.#refreshHeader();
74106
});
75107

76108
this.#container.addEventListener("interaction:invalidate-all", () => {
77109
void this.#refreshContextMenu();
110+
void this.#refreshHeader();
78111
});
79112

80113
this.#container.addEventListener("interaction:remove", () => {

0 commit comments

Comments
 (0)