mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 11:32:46 +08:00
DEV: refactor topic-summary widget to topic-map-summary component (#25447)
* shift topic-summary widget to topic-map-summary component * remove relativeDate memoization which was causing bug where displayed date never updated
This commit is contained in:
parent
ec26dc51cd
commit
bfa3e056f1
|
@ -3,12 +3,7 @@ import { longDate, relativeAge } from "discourse/lib/formatter";
|
|||
|
||||
export default class RelativeDate extends Component {
|
||||
get datetime() {
|
||||
if (this.memoizedDatetime) {
|
||||
return this.memoizedDatetime;
|
||||
}
|
||||
|
||||
this.memoizedDatetime = new Date(this.args.date);
|
||||
return this.memoizedDatetime;
|
||||
return new Date(this.args.date);
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import RelativeDate from "discourse/components/relative-date";
|
||||
import TopicParticipants from "discourse/components/topic-map/topic-participants";
|
||||
import number from "discourse/helpers/number";
|
||||
import slice from "discourse/helpers/slice";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import { avatarImg } from "discourse-common/lib/avatar-utils";
|
||||
import gt from "truth-helpers/helpers/gt";
|
||||
|
||||
export default class TopicMapSummary extends Component {
|
||||
get toggleMapButton() {
|
||||
return {
|
||||
title: this.args.collapsed
|
||||
? "topic.expand_details"
|
||||
: "topic.collapse_details",
|
||||
icon: this.args.collapsed ? "chevron-down" : "chevron-up",
|
||||
ariaExpanded: this.args.collapsed ? "false" : "true",
|
||||
ariaControls: "topic-map-expanded",
|
||||
action: this.args.toggleMap,
|
||||
};
|
||||
}
|
||||
|
||||
get shouldShowParticipants() {
|
||||
return (
|
||||
this.args.collapsed &&
|
||||
this.args.postAttrs.topicPostsCount > 2 &&
|
||||
this.args.postAttrs.participants &&
|
||||
this.args.postAttrs.participants.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
get createdByAvatar() {
|
||||
return htmlSafe(
|
||||
avatarImg({
|
||||
avatarTemplate: this.args.postAttrs.createdByAvatarTemplate,
|
||||
size: "tiny",
|
||||
title:
|
||||
this.args.postAttrs.createdByName ||
|
||||
this.args.postAttrs.createdByUsername,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get lastPostAvatar() {
|
||||
return htmlSafe(
|
||||
avatarImg({
|
||||
avatarTemplate: this.args.postAttrs.lastPostAvatarTemplate,
|
||||
size: "tiny",
|
||||
title:
|
||||
this.args.postAttrs.lastPostName ||
|
||||
this.args.postAttrs.lastPostUsername,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
<nav class="buttons">
|
||||
<DButton
|
||||
@icon={{this.toggleMapButton.icon}}
|
||||
@title={{this.toggleMapButton.title}}
|
||||
@ariaExpanded={{this.toggleMapButton.ariaExpanded}}
|
||||
@ariaControls={{this.toggleMapButton.ariaControls}}
|
||||
@action={{this.toggleMapButton.action}}
|
||||
class="btn"
|
||||
/>
|
||||
</nav>
|
||||
<ul>
|
||||
<li class="created-at">
|
||||
<h4 role="presentation">{{i18n "created_lowercase"}}</h4>
|
||||
<div class="topic-map-post created-at">
|
||||
<a
|
||||
class="trigger-user-card"
|
||||
data-user-card={{@postAttrs.createdByUsername}}
|
||||
title={{@postAttrs.createdByUsername}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{this.createdByAvatar}}
|
||||
<RelativeDate @date={{@postAttrs.topicCreatedAt}} />
|
||||
</div>
|
||||
</li>
|
||||
<li class="last-reply">
|
||||
<a href={{@postAttrs.lastPostUrl}}>
|
||||
<h4 role="presentation">{{i18n "last_reply_lowercase"}}</h4>
|
||||
<div class="topic-map-post last-reply">
|
||||
<a
|
||||
class="trigger-user-card"
|
||||
data-user-card={{@postAttrs.lastPostUsername}}
|
||||
title={{@postAttrs.lastPostUsername}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{this.lastPostAvatar}}
|
||||
<RelativeDate @date={{@postAttrs.lastPostAt}} />
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="replies">
|
||||
{{number @postAttrs.topicReplyCount noTitle="true"}}
|
||||
<h4 role="presentation">{{i18n
|
||||
"replies_lowercase"
|
||||
count=@postAttrs.topicReplyCount
|
||||
}}</h4>
|
||||
</li>
|
||||
<li class="secondary views">
|
||||
{{number
|
||||
@postAttrs.topicViews
|
||||
noTitle="true"
|
||||
class=@postAttrs.topicViewsHeat
|
||||
}}
|
||||
<h4 role="presentation">{{i18n
|
||||
"views_lowercase"
|
||||
count=@postAttrs.topicViews
|
||||
}}</h4>
|
||||
</li>
|
||||
{{#if (gt @postAttrs.participantCount 0)}}
|
||||
<li class="secondary users">
|
||||
{{number @postAttrs.participantCount noTitle="true"}}
|
||||
<h4 role="presentation">{{i18n
|
||||
"users_lowercase"
|
||||
count=@postAttrs.participantCount
|
||||
}}</h4>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (gt @postAttrs.topicLikeCount 0)}}
|
||||
<li class="secondary likes">
|
||||
{{number @postAttrs.topicLikeCount noTitle="true"}}
|
||||
<h4 role="presentation">{{i18n
|
||||
"likes_lowercase"
|
||||
count=@postAttrs.topicLikeCount
|
||||
}}</h4>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (gt @postAttrs.topicLinkCount 0)}}
|
||||
<li class="secondary links">
|
||||
{{number @postAttrs.topicLinkCount noTitle="true"}}
|
||||
<h4 role="presentation">{{i18n
|
||||
"links_lowercase"
|
||||
count=@postAttrs.topicLinkCount
|
||||
}}</h4>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.shouldShowParticipants}}
|
||||
<li class="avatars">
|
||||
<TopicParticipants
|
||||
@participants={{slice 0 3 @postAttrs.participants}}
|
||||
@userFilters={{@postAttrs.userFilters}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</template>
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
import { htmlSafe } from "@ember/template";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { h } from "virtual-dom";
|
||||
import { dateNode, numberNode } from "discourse/helpers/node";
|
||||
import { replaceEmoji } from "discourse/widgets/emoji";
|
||||
import { avatarFor } from "discourse/widgets/post";
|
||||
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
@ -46,170 +44,6 @@ createWidget("topic-map-show-links", {
|
|||
},
|
||||
});
|
||||
|
||||
createWidget("topic-map-summary", {
|
||||
tagName: "section.map",
|
||||
|
||||
buildClasses(attrs, state) {
|
||||
if (state.collapsed) {
|
||||
return "map-collapsed";
|
||||
}
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
const contents = [];
|
||||
contents.push(
|
||||
h("li.created-at", [
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("created_lowercase")
|
||||
),
|
||||
h("div.topic-map-post.created-at", [
|
||||
avatarFor("tiny", {
|
||||
username: attrs.createdByUsername,
|
||||
template: attrs.createdByAvatarTemplate,
|
||||
name: attrs.createdByName,
|
||||
}),
|
||||
dateNode(attrs.topicCreatedAt),
|
||||
]),
|
||||
])
|
||||
);
|
||||
contents.push(
|
||||
h(
|
||||
"li.last-reply",
|
||||
h("a", { attributes: { href: attrs.lastPostUrl } }, [
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("last_reply_lowercase")
|
||||
),
|
||||
h("div.topic-map-post.last-reply", [
|
||||
avatarFor("tiny", {
|
||||
username: attrs.lastPostUsername,
|
||||
template: attrs.lastPostAvatarTemplate,
|
||||
name: attrs.lastPostName,
|
||||
}),
|
||||
dateNode(attrs.lastPostAt),
|
||||
]),
|
||||
])
|
||||
)
|
||||
);
|
||||
contents.push(
|
||||
h("li.replies", [
|
||||
numberNode(attrs.topicReplyCount),
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("replies_lowercase", {
|
||||
count: attrs.topicReplyCount,
|
||||
}).toString()
|
||||
),
|
||||
])
|
||||
);
|
||||
contents.push(
|
||||
h("li.secondary.views", [
|
||||
numberNode(attrs.topicViews, { className: attrs.topicViewsHeat }),
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("views_lowercase", { count: attrs.topicViews }).toString()
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
if (attrs.participantCount > 0) {
|
||||
contents.push(
|
||||
h("li.secondary.users", [
|
||||
numberNode(attrs.participantCount),
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("users_lowercase", {
|
||||
count: attrs.participantCount,
|
||||
}).toString()
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (attrs.topicLikeCount) {
|
||||
contents.push(
|
||||
h("li.secondary.likes", [
|
||||
numberNode(attrs.topicLikeCount),
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("likes_lowercase", {
|
||||
count: attrs.topicLikeCount,
|
||||
}).toString()
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (attrs.topicLinkLength > 0) {
|
||||
contents.push(
|
||||
h("li.secondary.links", [
|
||||
numberNode(attrs.topicLinkLength),
|
||||
h(
|
||||
"h4",
|
||||
{
|
||||
attributes: { role: "presentation" },
|
||||
},
|
||||
I18n.t("links_lowercase", {
|
||||
count: attrs.topicLinkLength,
|
||||
}).toString()
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
state.collapsed &&
|
||||
attrs.topicPostsCount > 2 &&
|
||||
attrs.participants &&
|
||||
attrs.participants.length > 0
|
||||
) {
|
||||
const participants = renderParticipants.call(
|
||||
this,
|
||||
"li.avatars",
|
||||
"",
|
||||
attrs.userFilters,
|
||||
attrs.participants.slice(0, 3)
|
||||
);
|
||||
contents.push(participants);
|
||||
}
|
||||
|
||||
const nav = h(
|
||||
"nav.buttons",
|
||||
this.attach("button", {
|
||||
title: state.collapsed
|
||||
? "topic.expand_details"
|
||||
: "topic.collapse_details",
|
||||
icon: state.collapsed ? "chevron-down" : "chevron-up",
|
||||
ariaExpanded: state.collapsed ? "false" : "true",
|
||||
ariaControls: "topic-map-expanded",
|
||||
action: "toggleMap",
|
||||
className: "btn",
|
||||
})
|
||||
);
|
||||
|
||||
return [nav, h("ul", contents)];
|
||||
},
|
||||
});
|
||||
|
||||
createWidget("topic-map-link", {
|
||||
tagName: "a.topic-link.track-link",
|
||||
|
||||
|
@ -326,7 +160,7 @@ export default createWidget("topic-map", {
|
|||
},
|
||||
|
||||
html(attrs, state) {
|
||||
const contents = [this.attach("topic-map-summary", attrs, { state })];
|
||||
const contents = [this.buildTopicMapSummary(attrs, state)];
|
||||
|
||||
if (!state.collapsed) {
|
||||
contents.push(this.attach("topic-map-expanded", attrs));
|
||||
|
@ -344,6 +178,29 @@ export default createWidget("topic-map", {
|
|||
|
||||
toggleMap() {
|
||||
this.state.collapsed = !this.state.collapsed;
|
||||
this.scheduleRerender();
|
||||
},
|
||||
|
||||
buildTopicMapSummary(attrs, state) {
|
||||
const { collapsed } = state;
|
||||
const wrapperClass = collapsed
|
||||
? "section.map.map-collapsed"
|
||||
: "section.map";
|
||||
|
||||
return new RenderGlimmer(
|
||||
this,
|
||||
wrapperClass,
|
||||
hbs`<TopicMap::TopicMapSummary
|
||||
@postAttrs={{@data.postAttrs}}
|
||||
@toggleMap={{@data.toggleMap}}
|
||||
@collapsed={{@data.collapsed}}
|
||||
/>`,
|
||||
{
|
||||
toggleMap: this.toggleMap.bind(this),
|
||||
postAttrs: attrs,
|
||||
collapsed,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
buildSummaryBox(attrs) {
|
||||
|
|
|
@ -661,29 +661,23 @@ acceptance("Topic stats update automatically", function () {
|
|||
test("Likes count updates automatically", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
const likesDisplay = query("#post_1 .topic-map .likes .number");
|
||||
const oldLikes = likesDisplay.textContent;
|
||||
|
||||
const likesCountSelectors = "#post_1 .topic-map .likes .number";
|
||||
const oldLikesCount = query(likesCountSelectors).textContent;
|
||||
const likesChangedFixture = {
|
||||
id: 280,
|
||||
type: "stats",
|
||||
like_count: 999,
|
||||
like_count: parseInt(oldLikesCount, 10) + 42,
|
||||
};
|
||||
const expectedLikesCount = likesChangedFixture.like_count.toString();
|
||||
|
||||
// simulate the topic like_count being changed
|
||||
await publishToMessageBus("/topic/280", likesChangedFixture);
|
||||
|
||||
const newLikes = likesDisplay.textContent;
|
||||
|
||||
assert.dom(likesCountSelectors).hasText(expectedLikesCount);
|
||||
assert.notEqual(
|
||||
oldLikes,
|
||||
newLikes,
|
||||
"it updates the like count on the topic stats"
|
||||
);
|
||||
assert.equal(
|
||||
newLikes,
|
||||
likesChangedFixture.like_count,
|
||||
"it updates the like count with the expected value"
|
||||
oldLikesCount,
|
||||
expectedLikesCount,
|
||||
"it updates the likes count on the topic stats"
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -703,83 +697,70 @@ acceptance("Topic stats update automatically", function () {
|
|||
test("Replies count updates automatically", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
const repliesDisplay = query("#post_1 .topic-map .replies .number");
|
||||
const oldReplies = repliesDisplay.textContent;
|
||||
const repliesCountSelectors = "#post_1 .topic-map .replies .number";
|
||||
const oldRepliesCount = query(repliesCountSelectors).textContent;
|
||||
const expectedRepliesCount = (
|
||||
postsChangedFixture.posts_count - 1
|
||||
).toString();
|
||||
|
||||
// simulate the topic posts_count being changed
|
||||
await publishToMessageBus("/topic/280", postsChangedFixture);
|
||||
|
||||
const newLikes = repliesDisplay.textContent;
|
||||
|
||||
assert.dom(repliesCountSelectors).hasText(expectedRepliesCount);
|
||||
assert.notEqual(
|
||||
oldReplies,
|
||||
newLikes,
|
||||
oldRepliesCount,
|
||||
expectedRepliesCount,
|
||||
"it updates the replies count on the topic stats"
|
||||
);
|
||||
assert.equal(
|
||||
newLikes,
|
||||
postsChangedFixture.posts_count - 1, // replies = posts_count - 1
|
||||
"it updates the replies count with the expected value"
|
||||
);
|
||||
});
|
||||
|
||||
test("Last replier avatar updates automatically", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
const avatarSelectors = "#post_1 .topic-map .last-reply .avatar";
|
||||
const avatarImg = query(avatarSelectors);
|
||||
|
||||
const avatarImg = query("#post_1 .topic-map .last-reply .avatar");
|
||||
const oldAvatarTitle = avatarImg.title;
|
||||
const oldAvatarSrc = avatarImg.src;
|
||||
const expectedAvatarTitle = postsChangedFixture.last_poster.name;
|
||||
const expectedAvatarSrc = postsChangedFixture.last_poster.avatar_template;
|
||||
|
||||
// simulate the topic posts_count being changed
|
||||
await publishToMessageBus("/topic/280", postsChangedFixture);
|
||||
|
||||
const newAvatarTitle = avatarImg.title;
|
||||
const newAvatarSrc = avatarImg.src;
|
||||
|
||||
assert.dom(avatarSelectors).hasAttribute("title", expectedAvatarTitle);
|
||||
assert.notEqual(
|
||||
oldAvatarTitle,
|
||||
newAvatarTitle,
|
||||
expectedAvatarTitle,
|
||||
"it updates the last poster avatar title on the topic stats"
|
||||
);
|
||||
assert.equal(
|
||||
newAvatarTitle,
|
||||
postsChangedFixture.last_poster.name,
|
||||
"it updates the last poster avatar title with the expected value"
|
||||
);
|
||||
|
||||
assert.dom(avatarSelectors).hasAttribute("src", expectedAvatarSrc);
|
||||
assert.notEqual(
|
||||
oldAvatarSrc,
|
||||
newAvatarSrc,
|
||||
expectedAvatarSrc,
|
||||
"it updates the last poster avatar src on the topic stats"
|
||||
);
|
||||
assert.equal(
|
||||
newAvatarSrc,
|
||||
`${document.location.origin}${postsChangedFixture.last_poster.avatar_template}`,
|
||||
"it updates the last poster avatar src with the expected value"
|
||||
);
|
||||
});
|
||||
|
||||
test("Last replied at updates automatically", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
const lastRepliedAtDisplay = query(
|
||||
"#post_1 .topic-map .last-reply .relative-date"
|
||||
);
|
||||
const lastRepliedAtSelectors =
|
||||
"#post_1 .topic-map .last-reply .relative-date";
|
||||
const lastRepliedAtDisplay = query(lastRepliedAtSelectors);
|
||||
const oldTime = lastRepliedAtDisplay.dataset.time;
|
||||
const expectedTime = Date.parse(
|
||||
postsChangedFixture.last_posted_at
|
||||
).toString();
|
||||
|
||||
// simulate the topic posts_count being changed
|
||||
await publishToMessageBus("/topic/280", postsChangedFixture);
|
||||
|
||||
const newTime = lastRepliedAtDisplay.dataset.time;
|
||||
|
||||
assert.dom(lastRepliedAtSelectors).hasAttribute("data-time", expectedTime);
|
||||
assert.notEqual(
|
||||
oldTime,
|
||||
newTime,
|
||||
expectedTime,
|
||||
"it updates the last posted time on the topic stats"
|
||||
);
|
||||
assert.equal(
|
||||
newTime,
|
||||
new Date(postsChangedFixture.last_posted_at).getTime(),
|
||||
"it updates the last posted time with the expected value"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user