DEV: replace transformed-post POJO with topic model in topic-map components (#26629)

* DEV: replace postAttrs dependencies in topic-map component by passing in topicDetails and postStream to topic-map to ensure state changes are passed properly down to child components
This commit is contained in:
Kelv 2024-04-18 10:04:38 +08:00 committed by GitHub
parent c5dd50aa02
commit edbd44e737
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 145 additions and 100 deletions

View File

@ -1,9 +1,9 @@
<div class="summary-box__container"> <div class="summary-box__container">
{{#if @postAttrs.hasTopRepliesSummary}} {{#if this.topRepliesSummaryEnabled}}
<p>{{html-safe this.topRepliesSummaryInfo}}</p> <p>{{html-safe this.topRepliesSummaryInfo}}</p>
{{/if}} {{/if}}
<div class="summarization-buttons"> <div class="summarization-buttons">
{{#if @postAttrs.summarizable}} {{#if @topic.summarizable}}
{{#if this.summary.showSummaryBox}} {{#if this.summary.showSummaryBox}}
<DButton <DButton
@action={{@collapseSummary}} @action={{@collapseSummary}}
@ -24,13 +24,9 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if @postAttrs.hasTopRepliesSummary}} {{#if this.topRepliesSummaryEnabled}}
<DButton <DButton
@action={{if @action={{if @postStream.summary @cancelFilter @showTopReplies}}
@postAttrs.topicSummaryEnabled
@cancelFilter
@showTopReplies
}}
@translatedTitle={{this.topRepliesTitle}} @translatedTitle={{this.topRepliesTitle}}
@translatedLabel={{this.topRepliesLabel}} @translatedLabel={{this.topRepliesLabel}}
@icon={{this.topRepliesIcon}} @icon={{this.topRepliesIcon}}

View File

@ -8,7 +8,7 @@ export default class SummaryBox extends Component {
@service siteSettings; @service siteSettings;
get summary() { get summary() {
return this.args.postAttrs.summary; return this.args.postStream.topicSummary;
} }
get generateSummaryTitle() { get generateSummaryTitle() {
@ -27,7 +27,7 @@ export default class SummaryBox extends Component {
let outdatedText = I18n.t("summary.outdated"); let outdatedText = I18n.t("summary.outdated");
if ( if (
!this.args.postAttrs.hasTopRepliesSummary && !this.topRepliesSummaryEnabled &&
this.summary.newPostsSinceSummary > 0 this.summary.newPostsSinceSummary > 0
) { ) {
outdatedText += " "; outdatedText += " ";
@ -40,29 +40,29 @@ export default class SummaryBox extends Component {
} }
get topRepliesSummaryEnabled() { get topRepliesSummaryEnabled() {
return this.args.postAttrs.topicSummaryEnabled; return this.args.topic.has_summary;
} }
get topRepliesSummaryInfo() { get topRepliesSummaryInfo() {
if (this.args.postAttrs.topicSummaryEnabled) { if (this.topRepliesSummaryEnabled) {
return I18n.t("summary.enabled_description"); return I18n.t("summary.enabled_description");
} }
const wordCount = this.args.postAttrs.topicWordCount; const wordCount = this.args.topic.word_count;
if (wordCount && this.siteSettings.read_time_word_count > 0) { if (wordCount && this.siteSettings.read_time_word_count > 0) {
const readingTime = Math.ceil( const readingTime = Math.ceil(
Math.max( Math.max(
wordCount / this.siteSettings.read_time_word_count, wordCount / this.siteSettings.read_time_word_count,
(this.args.postAttrs.topicPostsCount * MIN_POST_READ_TIME) / 60 (this.args.topic.posts_count * MIN_POST_READ_TIME) / 60
) )
); );
return I18n.messageFormat("summary.description_time_MF", { return I18n.messageFormat("summary.description_time_MF", {
replyCount: this.args.postAttrs.topicReplyCount, replyCount: this.args.topic.replyCount,
readingTime, readingTime,
}); });
} }
return I18n.t("summary.description", { return I18n.t("summary.description", {
count: this.args.postAttrs.topicReplyCount, count: this.args.topic.replyCount,
}); });
} }

View File

@ -9,7 +9,11 @@ import concatClass from "discourse/helpers/concat-class";
import or from "truth-helpers/helpers/or"; import or from "truth-helpers/helpers/or";
export default class TopicMap extends Component { export default class TopicMap extends Component {
@tracked collapsed = !this.args.postAttrs.hasTopRepliesSummary; @tracked collapsed = !this.args.model.has_summary;
get userFilters() {
return this.args.postStream.userFilters || [];
}
@action @action
toggleMap() { toggleMap() {
@ -19,9 +23,11 @@ export default class TopicMap extends Component {
<template> <template>
<section class={{concatClass "map" (if this.collapsed "map-collapsed")}}> <section class={{concatClass "map" (if this.collapsed "map-collapsed")}}>
<TopicMapSummary <TopicMapSummary
@postAttrs={{@postAttrs}} @topic={{@model}}
@topicDetails={{@topicDetails}}
@toggleMap={{this.toggleMap}} @toggleMap={{this.toggleMap}}
@collapsed={{this.collapsed}} @collapsed={{this.collapsed}}
@userFilters={{this.userFilters}}
/> />
</section> </section>
{{#unless this.collapsed}} {{#unless this.collapsed}}
@ -29,13 +35,17 @@ export default class TopicMap extends Component {
class="topic-map-expanded" class="topic-map-expanded"
id="topic-map-expanded__aria-controls" id="topic-map-expanded__aria-controls"
> >
<TopicMapExpanded @postAttrs={{@postAttrs}} /> <TopicMapExpanded
@topicDetails={{@topicDetails}}
@userFilters={{this.userFilters}}
/>
</section> </section>
{{/unless}} {{/unless}}
{{#if (or @postAttrs.hasTopRepliesSummary @postAttrs.summarizable)}} {{#if (or @model.has_summary @model.summarizable)}}
<section class="information toggle-summary"> <section class="information toggle-summary">
<SummaryBox <SummaryBox
@postAttrs={{@postAttrs}} @topic={{@model}}
@postStream={{@postStream}}
@cancelFilter={{@cancelFilter}} @cancelFilter={{@cancelFilter}}
@showTopReplies={{@showTopReplies}} @showTopReplies={{@showTopReplies}}
@collapseSummary={{@collapseSummary}} @collapseSummary={{@collapseSummary}}
@ -43,10 +53,10 @@ export default class TopicMap extends Component {
/> />
</section> </section>
{{/if}} {{/if}}
{{#if @postAttrs.showPMMap}} {{#if @showPMMap}}
<section class="information private-message-map"> <section class="information private-message-map">
<PrivateMessageMap <PrivateMessageMap
@postAttrs={{@postAttrs}} @topicDetails={{@topicDetails}}
@showInvite={{@showInvite}} @showInvite={{@showInvite}}
@removeAllowedGroup={{@removeAllowedGroup}} @removeAllowedGroup={{@removeAllowedGroup}}
@removeAllowedUser={{@removeAllowedUser}} @removeAllowedUser={{@removeAllowedUser}}

View File

@ -17,19 +17,19 @@ export default class PrivateMessageMap extends Component {
get participantsClasses() { get participantsClasses() {
return !this.isEditing && return !this.isEditing &&
this.site.mobileView && this.site.mobileView &&
this.args.postAttrs.allowedGroups.length > 4 this.args.topicDetails.allowed_groups.length > 4
? "participants hide-names" ? "participants hide-names"
: "participants"; : "participants";
} }
get canInvite() { get canInvite() {
return this.args.postAttrs.canInvite; return this.args.topicDetails.can_invite_to;
} }
get canRemove() { get canRemove() {
return ( return (
this.args.postAttrs.canRemoveAllowedUsers || this.args.topicDetails.can_remove_allowed_users ||
this.args.postAttrs.canRemoveSelfId this.args.topicDetails.can_remove_self_id
); );
} }
@ -58,20 +58,20 @@ export default class PrivateMessageMap extends Component {
<template> <template>
<div class={{this.participantsClasses}}> <div class={{this.participantsClasses}}>
{{#each @postAttrs.allowedGroups as |group|}} {{#each @topicDetails.allowed_groups as |group|}}
<PmMapUserGroup <PmMapUserGroup
@model={{group}} @model={{group}}
@isEditing={{this.isEditing}} @isEditing={{this.isEditing}}
@canRemoveAllowedUsers={{@postAttrs.canRemoveAllowedUsers}} @canRemoveAllowedUsers={{@topicDetails.can_remove_allowed_users}}
@removeAllowedGroup={{@removeAllowedGroup}} @removeAllowedGroup={{@removeAllowedGroup}}
/> />
{{/each}} {{/each}}
{{#each @postAttrs.allowedUsers as |user|}} {{#each @topicDetails.allowed_users as |user|}}
<PmMapUser <PmMapUser
@model={{user}} @model={{user}}
@isEditing={{this.isEditing}} @isEditing={{this.isEditing}}
@canRemoveAllowedUsers={{@postAttrs.canRemoveAllowedUsers}} @canRemoveAllowedUsers={{@topicDetails.can_remove_allowed_users}}
@canRemoveSelfId={{@postAttrs.canRemoveSelfId}} @canRemoveSelfId={{@topicDetails.can_remove_self_id}}
@removeAllowedUser={{@removeAllowedUser}} @removeAllowedUser={{@removeAllowedUser}}
/> />
{{/each}} {{/each}}

View File

@ -14,6 +14,14 @@ const TRUNCATED_LINKS_LIMIT = 5;
export default class TopicMapExpanded extends Component { export default class TopicMapExpanded extends Component {
@tracked allLinksShown = false; @tracked allLinksShown = false;
get topicLinks() {
return this.args.topicDetails.links;
}
get participants() {
return this.args.topicDetails.participants;
}
@action @action
showAllLinks() { showAllLinks() {
this.allLinksShown = true; this.allLinksShown = true;
@ -21,21 +29,21 @@ export default class TopicMapExpanded extends Component {
get linksToShow() { get linksToShow() {
return this.allLinksShown return this.allLinksShown
? this.args.postAttrs.topicLinks ? this.topicLinks
: this.args.postAttrs.topicLinks.slice(0, TRUNCATED_LINKS_LIMIT); : this.topicLinks.slice(0, TRUNCATED_LINKS_LIMIT);
} }
<template> <template>
{{#if @postAttrs.participants}} {{#if this.participants}}
<section class="avatars"> <section class="avatars">
<TopicParticipants <TopicParticipants
@title={{i18n "topic_map.participants_title"}} @title={{i18n "topic_map.participants_title"}}
@userFilters={{@postAttrs.userFilters}} @userFilters={{@userFilters}}
@participants={{@postAttrs.participants}} @participants={{this.participants}}
/> />
</section> </section>
{{/if}} {{/if}}
{{#if @postAttrs.topicLinks}} {{#if this.topicLinks}}
<section class="links"> <section class="links">
<h3>{{i18n "topic_map.links_title"}}</h3> <h3>{{i18n "topic_map.links_title"}}</h3>
<table class="topic-links"> <table class="topic-links">
@ -66,7 +74,7 @@ export default class TopicMapExpanded extends Component {
{{#if {{#if
(and (and
(not this.allLinksShown) (not this.allLinksShown)
(lt TRUNCATED_LINKS_LIMIT @postAttrs.topicLinks.length) (lt TRUNCATED_LINKS_LIMIT this.topicLinks.length)
) )
}} }}
<div class="link-summary"> <div class="link-summary">

View File

@ -10,6 +10,18 @@ import i18n from "discourse-common/helpers/i18n";
import { avatarImg } from "discourse-common/lib/avatar-utils"; import { avatarImg } from "discourse-common/lib/avatar-utils";
export default class TopicMapSummary extends Component { export default class TopicMapSummary extends Component {
get linksCount() {
return this.args.topicDetails.links?.length ?? 0;
}
get createdByUsername() {
return this.args.topicDetails.created_by?.username;
}
get lastPosterUsername() {
return this.args.topicDetails.last_poster?.username;
}
get toggleMapButton() { get toggleMapButton() {
return { return {
title: this.args.collapsed title: this.args.collapsed
@ -25,20 +37,20 @@ export default class TopicMapSummary extends Component {
get shouldShowParticipants() { get shouldShowParticipants() {
return ( return (
this.args.collapsed && this.args.collapsed &&
this.args.postAttrs.topicPostsCount > 2 && this.args.topic.posts_count > 2 &&
this.args.postAttrs.participants && this.args.topicDetails.participants &&
this.args.postAttrs.participants.length > 0 this.args.topicDetails.participants.length > 0
); );
} }
get createdByAvatar() { get createdByAvatar() {
return htmlSafe( return htmlSafe(
avatarImg({ avatarImg({
avatarTemplate: this.args.postAttrs.createdByAvatarTemplate, avatarTemplate: this.args.topicDetails.created_by?.avatar_template,
size: "tiny", size: "tiny",
title: title:
this.args.postAttrs.createdByName || this.args.topicDetails.created_by?.name ||
this.args.postAttrs.createdByUsername, this.args.topicDetails.created_by?.username,
}) })
); );
} }
@ -46,11 +58,11 @@ export default class TopicMapSummary extends Component {
get lastPostAvatar() { get lastPostAvatar() {
return htmlSafe( return htmlSafe(
avatarImg({ avatarImg({
avatarTemplate: this.args.postAttrs.lastPostAvatarTemplate, avatarTemplate: this.args.topicDetails.last_poster?.avatar_template,
size: "tiny", size: "tiny",
title: title:
this.args.postAttrs.lastPostName || this.args.topicDetails.last_poster?.name ||
this.args.postAttrs.lastPostUsername, this.args.topicDetails.last_poster?.username,
}) })
); );
} }
@ -72,71 +84,67 @@ export default class TopicMapSummary extends Component {
<div class="topic-map-post created-at"> <div class="topic-map-post created-at">
<a <a
class="trigger-user-card" class="trigger-user-card"
data-user-card={{@postAttrs.createdByUsername}} data-user-card={{this.createdByUsername}}
title={{@postAttrs.createdByUsername}} title={{this.createdByUsername}}
aria-hidden="true" aria-hidden="true"
/> />
{{this.createdByAvatar}} {{this.createdByAvatar}}
<RelativeDate @date={{@postAttrs.topicCreatedAt}} /> <RelativeDate @date={{@topic.created_at}} />
</div> </div>
</li> </li>
<li class="last-reply"> <li class="last-reply">
<a href={{@postAttrs.lastPostUrl}}> <a href={{@topic.lastPostUrl}}>
<h4 role="presentation">{{i18n "last_reply_lowercase"}}</h4> <h4 role="presentation">{{i18n "last_reply_lowercase"}}</h4>
<div class="topic-map-post last-reply"> <div class="topic-map-post last-reply">
<a <a
class="trigger-user-card" class="trigger-user-card"
data-user-card={{@postAttrs.lastPostUsername}} data-user-card={{this.lastPosterUsername}}
title={{@postAttrs.lastPostUsername}} title={{this.lastPosterUsername}}
aria-hidden="true" aria-hidden="true"
/> />
{{this.lastPostAvatar}} {{this.lastPostAvatar}}
<RelativeDate @date={{@postAttrs.lastPostAt}} /> <RelativeDate @date={{@topic.last_posted_at}} />
</div> </div>
</a> </a>
</li> </li>
<li class="replies"> <li class="replies">
{{number @postAttrs.topicReplyCount noTitle="true"}} {{number @topic.replyCount noTitle="true"}}
<h4 role="presentation">{{i18n <h4 role="presentation">{{i18n
"replies_lowercase" "replies_lowercase"
count=@postAttrs.topicReplyCount count=@topic.replyCount
}}</h4> }}</h4>
</li> </li>
<li class="secondary views"> <li class="secondary views">
{{number {{number @topic.views noTitle="true" class=@topic.viewsHeat}}
@postAttrs.topicViews
noTitle="true"
class=@postAttrs.topicViewsHeat
}}
<h4 role="presentation">{{i18n <h4 role="presentation">{{i18n
"views_lowercase" "views_lowercase"
count=@postAttrs.topicViews count=@topic.views
}}</h4> }}</h4>
</li> </li>
{{#if (gt @postAttrs.participantCount 0)}} {{#if (gt @topic.participant_count 0)}}
<li class="secondary users"> <li class="secondary users">
{{number @postAttrs.participantCount noTitle="true"}} {{number @topic.participant_count noTitle="true"}}
<h4 role="presentation">{{i18n <h4 role="presentation">{{i18n
"users_lowercase" "users_lowercase"
count=@postAttrs.participantCount count=@topic.participant_count
}}</h4> }}</h4>
</li> </li>
{{/if}} {{/if}}
{{#if (gt @postAttrs.topicLikeCount 0)}} {{#if (gt @topic.like_count 0)}}
<li class="secondary likes"> <li class="secondary likes">
{{number @postAttrs.topicLikeCount noTitle="true"}} {{number @topic.like_count noTitle="true"}}
<h4 role="presentation">{{i18n <h4 role="presentation">{{i18n
"likes_lowercase" "likes_lowercase"
count=@postAttrs.topicLikeCount count=@topic.like_count
}}</h4> }}</h4>
</li> </li>
{{/if}} {{/if}}
{{#if (gt @postAttrs.topicLinkCount 0)}} {{#if (gt this.linksCount 0)}}
<li class="secondary links"> <li class="secondary links">
{{number @postAttrs.topicLinkCount noTitle="true"}} {{number this.linksCount noTitle="true"}}
<h4 role="presentation">{{i18n <h4 role="presentation">{{i18n
"links_lowercase" "links_lowercase"
count=@postAttrs.topicLinkCount count=this.linksCount
}}</h4> }}</h4>
</li> </li>
{{/if}} {{/if}}
@ -144,8 +152,8 @@ export default class TopicMapSummary extends Component {
{{#if this.shouldShowParticipants}} {{#if this.shouldShowParticipants}}
<li class="avatars"> <li class="avatars">
<TopicParticipants <TopicParticipants
@participants={{slice 0 3 @postAttrs.participants}} @participants={{slice 0 3 @topicDetails.participants}}
@userFilters={{@postAttrs.userFilters}} @userFilters={{@userFilters}}
/> />
</li> </li>
{{/if}} {{/if}}

View File

@ -755,7 +755,10 @@ createWidget("post-body", {
this, this,
"div.topic-map", "div.topic-map",
hbs`<TopicMap hbs`<TopicMap
@postAttrs={{@data.postAttrs}} @model={{@data.model}}
@topicDetails={{@data.topicDetails}}
@postStream={{@data.postStream}}
@showPMMap={{@data.showPMMap}}
@cancelFilter={{@data.cancelFilter}} @cancelFilter={{@data.cancelFilter}}
@showTopReplies={{@data.showTopReplies}} @showTopReplies={{@data.showTopReplies}}
@collapseSummary={{@data.collapseSummary}} @collapseSummary={{@data.collapseSummary}}
@ -765,7 +768,10 @@ createWidget("post-body", {
@removeAllowedUser={{@data.removeAllowedUser}} @removeAllowedUser={{@data.removeAllowedUser}}
/>`, />`,
{ {
postAttrs: attrs, model: attrs.topic,
topicDetails: attrs.topic.get("details"),
postStream: attrs.topic.postStream,
showPMMap: attrs.showPMMap,
cancelFilter: () => this.sendWidgetAction("cancelFilter"), cancelFilter: () => this.sendWidgetAction("cancelFilter"),
showTopReplies: () => this.sendWidgetAction("showTopReplies"), showTopReplies: () => this.sendWidgetAction("showTopReplies"),
collapseSummary: () => this.sendWidgetAction("collapseSummary"), collapseSummary: () => this.sendWidgetAction("collapseSummary"),

View File

@ -780,10 +780,16 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("topic map - few posts", async function (assert) { test("topic map - few posts", async function (assert) {
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", { id: 123 });
topic.details.set("participants", [
{ username: "eviltrout" },
{ username: "codinghorror" },
]);
this.set("args", { this.set("args", {
topic,
showTopicMap: true, showTopicMap: true,
topicPostsCount: 2, topicPostsCount: 2,
participants: [{ username: "eviltrout" }, { username: "codinghorror" }],
}); });
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`); await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
@ -794,16 +800,19 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("topic map - participants", async function (assert) { test("topic map - participants", async function (assert) {
this.set("args", { const store = getOwner(this).lookup("service:store");
showTopicMap: true, const topic = store.createRecord("topic", { id: 123, posts_count: 10 });
topicPostsCount: 10, topic.postStream.setProperties({ userFilters: ["sam", "codinghorror"] });
participants: [ topic.details.set("participants", [
{ username: "eviltrout" }, { username: "eviltrout" },
{ username: "codinghorror" }, { username: "codinghorror" },
{ username: "sam" }, { username: "sam" },
{ username: "ZogStrIP" }, { username: "ZogStrIP" },
], ]);
userFilters: ["sam", "codinghorror"],
this.set("args", {
topic,
showTopicMap: true,
}); });
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`); await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
@ -816,17 +825,17 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("topic map - links", async function (assert) { test("topic map - links", async function (assert) {
this.set("args", { const store = getOwner(this).lookup("service:store");
showTopicMap: true, const topic = store.createRecord("topic", { id: 123 });
topicLinks: [ topic.details.set("links", [
{ url: "http://link1.example.com", clicks: 0 }, { url: "http://link1.example.com", clicks: 0 },
{ url: "http://link2.example.com", clicks: 0 }, { url: "http://link2.example.com", clicks: 0 },
{ url: "http://link3.example.com", clicks: 0 }, { url: "http://link3.example.com", clicks: 0 },
{ url: "http://link4.example.com", clicks: 0 }, { url: "http://link4.example.com", clicks: 0 },
{ url: "http://link5.example.com", clicks: 0 }, { url: "http://link5.example.com", clicks: 0 },
{ url: "http://link6.example.com", clicks: 0 }, { url: "http://link6.example.com", clicks: 0 },
], ]);
}); this.set("args", { topic, showTopicMap: true });
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`); await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
@ -845,7 +854,9 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("topic map - no summary", async function (assert) { test("topic map - no summary", async function (assert) {
this.set("args", { showTopicMap: true }); const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", { id: 123 });
this.set("args", { topic, showTopicMap: true });
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`); await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
@ -853,7 +864,9 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("topic map - has top replies summary", async function (assert) { test("topic map - has top replies summary", async function (assert) {
this.set("args", { showTopicMap: true, hasTopRepliesSummary: true }); const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", { id: 123, has_summary: true });
this.set("args", { topic, showTopicMap: true });
this.set("showTopReplies", () => (this.summaryToggled = true)); this.set("showTopReplies", () => (this.summaryToggled = true));
await render( await render(
@ -867,11 +880,15 @@ module("Integration | Component | Widget | post", function (hooks) {
}); });
test("pm map", async function (assert) { test("pm map", async function (assert) {
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", { id: 123 });
topic.details.set("allowed_users", [
EmberObject.create({ username: "eviltrout" }),
]);
this.set("args", { this.set("args", {
topic,
showTopicMap: true, showTopicMap: true,
showPMMap: true, showPMMap: true,
allowedGroups: [],
allowedUsers: [EmberObject.create({ username: "eviltrout" })],
}); });
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`); await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);