DEV: convert TopicMapExpanded widget to glimmer component (#26027)

* DEV: add topic-map-expanded glimmer component

* DEV: remove topic-map-expanded widgets from topic-map

* DEV: add noreferrer for _blank target and add table grouping to template

* DEV: negate base styling of tbody element so expanded topic map retains current look

* DEV: pass in title to TopicParticipants as internationalized string instead of renderable HTML and set TRUNCATED_LINKS_LIMIT constant
This commit is contained in:
Kelv 2024-03-06 08:38:35 +08:00 committed by GitHub
parent b3dce458d2
commit 6b46b9ab78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 159 additions and 191 deletions

View File

@ -0,0 +1,125 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import DButton from "discourse/components/d-button";
import TopicParticipants from "discourse/components/topic-map/topic-participants";
import replaceEmoji from "discourse/helpers/replace-emoji";
import i18n from "discourse-common/helpers/i18n";
import and from "truth-helpers/helpers/and";
import lt from "truth-helpers/helpers/lt";
import not from "truth-helpers/helpers/not";
const TRUNCATED_LINKS_LIMIT = 5;
export default class TopicMapExpanded extends Component {
@tracked allLinksShown = false;
@action
showAllLinks() {
this.allLinksShown = true;
}
get linksToShow() {
return this.allLinksShown
? this.args.postAttrs.topicLinks
: this.args.postAttrs.topicLinks.slice(0, TRUNCATED_LINKS_LIMIT);
}
<template>
{{#if @postAttrs.participants}}
<section class="avatars">
<TopicParticipants
@title={{i18n "topic_map.participants_title"}}
@userFilters={{@postAttrs.userFilters}}
@participants={{@postAttrs.participants}}
/>
</section>
{{/if}}
{{#if @postAttrs.topicLinks}}
<section class="links">
<h3>{{i18n "topic_map.links_title"}}</h3>
<table class="topic-links">
<tbody>
{{#each this.linksToShow as |link|}}
<tr>
<td>
<span
class="badge badge-notification clicks"
title={{i18n "topic_map.clicks" count=link.clicks}}
>
{{link.clicks}}
</span>
</td>
<td>
<TopicMapLink
@attachment={{link.attachment}}
@title={{link.title}}
@rootDomain={{link.root_domain}}
@url={{link.url}}
@userId={{link.user_id}}
/>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{#if
(and
(not this.allLinksShown)
(lt TRUNCATED_LINKS_LIMIT @postAttrs.topicLinks.length)
)
}}
<div class="link-summary">
<span>
<DButton
@action={{this.showAllLinks}}
@title="topic_map.links_shown"
@icon="chevron-down"
class="btn-flat"
/>
</span>
</div>
{{/if}}
</section>
{{/if}}
</template>
}
class TopicMapLink extends Component {
get linkClasses() {
return this.args.attachment
? "topic-link track-link attachment"
: "topic-link track-link";
}
get truncatedContent() {
const truncateLength = 85;
const content = this.args.title || this.args.url;
return content.length > truncateLength
? `${content.slice(0, truncateLength).trim()}...`
: content;
}
<template>
<a
class={{this.linkClasses}}
href={{@url}}
title={{@url}}
data-user-id={{@userId}}
data-ignore-post-id="true"
target="_blank"
rel="nofollow ugc noopener noreferrer"
>
{{#if @title}}
{{replaceEmoji this.truncatedContent}}
{{else}}
{{this.truncatedContent}}
{{/if}}
</a>
{{#if (and @title @rootDomain)}}
<span class="domain">
{{@rootDomain}}
</span>
{{/if}}
</template>
}

View File

@ -2,11 +2,12 @@ import Component from "@glimmer/component";
import TopicParticipant from "discourse/components/topic-map/topic-participant";
export default class TopicParticipants extends Component {
// prettier-ignore
toggledUsers = new Set(this.args.userFilters);
<template>
{{@title}}
{{#if @title}}
<h3>{{@title}}</h3>
{{/if}}
{{#each @participants as |participant|}}
<TopicParticipant
@participant={{participant}}

View File

@ -1,155 +1,6 @@
import { htmlSafe } from "@ember/template";
import { hbs } from "ember-cli-htmlbars";
import { h } from "virtual-dom";
import { replaceEmoji } from "discourse/widgets/emoji";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import { createWidget } from "discourse/widgets/widget";
import I18n from "discourse-i18n";
const LINKS_SHOWN = 5;
function renderParticipants(wrapperElement, title, userFilters, participants) {
return new RenderGlimmer(
this,
wrapperElement,
hbs`<TopicMap::TopicParticipants
@title={{@data.title}}
@participants={{@data.participants}}
@userFilters={{@data.userFilters}}
/>`,
{
title,
userFilters,
participants,
}
);
}
createWidget("topic-map-show-links", {
tagName: "div.link-summary",
html() {
return h(
"span",
this.attach("button", {
title: "topic_map.links_shown",
icon: "chevron-down",
action: "showLinks",
className: "btn btn-flat",
})
);
},
showLinks() {
this.sendWidgetAction("showAllLinks");
},
});
createWidget("topic-map-link", {
tagName: "a.topic-link.track-link",
buildClasses(attrs) {
if (attrs.attachment) {
return "attachment";
}
},
buildAttributes(attrs) {
return {
href: attrs.url,
target: "_blank",
"data-user-id": attrs.user_id,
"data-ignore-post-id": "true",
title: attrs.url,
rel: "nofollow ugc noopener",
};
},
html(attrs) {
let content = attrs.title || attrs.url;
const truncateLength = 85;
if (content.length > truncateLength) {
content = `${content.slice(0, truncateLength).trim()}...`;
}
return attrs.title ? replaceEmoji(content) : content;
},
});
createWidget("topic-map-expanded", {
tagName: "section.topic-map-expanded#topic-map-expanded",
buildKey: (attrs) => `topic-map-expanded-${attrs.id}`,
defaultState() {
return { allLinksShown: false };
},
html(attrs, state) {
let avatars;
if (attrs.participants && attrs.participants.length > 0) {
avatars = renderParticipants.call(
this,
"section.avatars",
htmlSafe(`<h3>${I18n.t("topic_map.participants_title")}</h3>`),
attrs.userFilters,
attrs.participants
);
}
const result = [avatars];
if (attrs.topicLinks) {
const toShow = state.allLinksShown
? attrs.topicLinks
: attrs.topicLinks.slice(0, LINKS_SHOWN);
const links = toShow.map((l) => {
let host = "";
if (l.title && l.title.length) {
const rootDomain = l.root_domain;
if (rootDomain && rootDomain.length) {
host = h("span.domain", rootDomain);
}
}
return h("tr", [
h(
"td",
h(
"span.badge.badge-notification.clicks",
{
attributes: {
title: I18n.t("topic_map.clicks", { count: l.clicks }),
},
},
l.clicks.toString()
)
),
h("td", [this.attach("topic-map-link", l), " ", host]),
]);
});
const showAllLinksContent = [
h("h3", I18n.t("topic_map.links_title")),
h("table.topic-links", links),
];
if (!state.allLinksShown && links.length < attrs.topicLinks.length) {
showAllLinksContent.push(this.attach("topic-map-show-links"));
}
const section = h("section.links", showAllLinksContent);
result.push(section);
}
return result;
},
showAllLinks() {
this.state.allLinksShown = true;
},
});
export default createWidget("topic-map", {
tagName: "div.topic-map",
@ -163,7 +14,7 @@ export default createWidget("topic-map", {
const contents = [this.buildTopicMapSummary(attrs, state)];
if (!state.collapsed) {
contents.push(this.attach("topic-map-expanded", attrs));
contents.push(this.buildTopicMapExpanded(attrs));
}
if (attrs.hasTopRepliesSummary || attrs.summarizable) {
@ -203,6 +54,19 @@ export default createWidget("topic-map", {
);
},
buildTopicMapExpanded(attrs) {
return new RenderGlimmer(
this,
"section.topic-map-expanded",
hbs`<TopicMap::TopicMapExpanded
@postAttrs={{@data.postAttrs}}
/>`,
{
postAttrs: attrs,
}
);
},
buildSummaryBox(attrs) {
return new RenderGlimmer(
this,

View File

@ -787,18 +787,10 @@ module("Integration | Component | Widget | post", function (hooks) {
});
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
assert.ok(
!exists("li.avatars a.poster"),
"shows no participants when collapsed"
);
assert.dom("li.avatars a.poster").doesNotExist();
await click("nav.buttons button");
assert.strictEqual(
count(".topic-map-expanded a.poster"),
2,
"shows all when expanded"
);
assert.dom(".topic-map-expanded a.poster").exists({ count: 2 });
});
test("topic map - participants", async function (assert) {
@ -815,21 +807,12 @@ module("Integration | Component | Widget | post", function (hooks) {
});
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
assert.strictEqual(
count("li.avatars a.poster"),
3,
"limits to three participants"
);
assert.dom("li.avatars a.poster").exists({ count: 3 });
await click("nav.buttons button");
assert.ok(!exists("li.avatars a.poster"));
assert.strictEqual(
count(".topic-map-expanded a.poster"),
4,
"shows all when expanded"
);
assert.strictEqual(count("a.poster.toggled"), 2, "two are toggled");
assert.dom("li.avatars a.poster").doesNotExist();
assert.dom(".topic-map-expanded a.poster").exists({ count: 4 });
assert.dom("a.poster.toggled").exists({ count: 2 });
});
test("topic map - links", async function (assert) {
@ -847,26 +830,18 @@ module("Integration | Component | Widget | post", function (hooks) {
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
assert.strictEqual(count(".topic-map"), 1);
assert.strictEqual(count(".map.map-collapsed"), 1);
assert.ok(!exists(".topic-map-expanded"));
assert.dom(".topic-map").exists({ count: 1 });
assert.dom(".map.map-collapsed").exists({ count: 1 });
assert.dom(".topic-map-expanded").doesNotExist();
await click("nav.buttons button");
assert.ok(!exists(".map.map-collapsed"));
assert.strictEqual(count(".topic-map .d-icon-chevron-up"), 1);
assert.strictEqual(count(".topic-map-expanded"), 1);
assert.strictEqual(
count(".topic-map-expanded .topic-link"),
5,
"it limits the links displayed"
);
assert.dom(".map.map-collapsed").doesNotExist();
assert.dom(".topic-map .d-icon-chevron-up").exists({ count: 1 });
assert.dom(".topic-map-expanded").exists({ count: 1 });
assert.dom(".topic-map-expanded .topic-link").exists({ count: 5 });
await click(".link-summary button");
assert.strictEqual(
count(".topic-map-expanded .topic-link"),
6,
"all links now shown"
);
assert.dom(".topic-map-expanded .topic-link").exists({ count: 6 });
});
test("topic map - no summary", async function (assert) {

View File

@ -751,6 +751,9 @@ aside.quote {
}
.topic-links {
tbody {
border: none;
}
tr {
border: none;
}