mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
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:
parent
b3dce458d2
commit
6b46b9ab78
@ -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>
|
||||||
|
}
|
@ -2,11 +2,12 @@ import Component from "@glimmer/component";
|
|||||||
import TopicParticipant from "discourse/components/topic-map/topic-participant";
|
import TopicParticipant from "discourse/components/topic-map/topic-participant";
|
||||||
|
|
||||||
export default class TopicParticipants extends Component {
|
export default class TopicParticipants extends Component {
|
||||||
// prettier-ignore
|
|
||||||
toggledUsers = new Set(this.args.userFilters);
|
toggledUsers = new Set(this.args.userFilters);
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{@title}}
|
{{#if @title}}
|
||||||
|
<h3>{{@title}}</h3>
|
||||||
|
{{/if}}
|
||||||
{{#each @participants as |participant|}}
|
{{#each @participants as |participant|}}
|
||||||
<TopicParticipant
|
<TopicParticipant
|
||||||
@participant={{participant}}
|
@participant={{participant}}
|
||||||
|
@ -1,155 +1,6 @@
|
|||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
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 RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
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", {
|
export default createWidget("topic-map", {
|
||||||
tagName: "div.topic-map",
|
tagName: "div.topic-map",
|
||||||
@ -163,7 +14,7 @@ export default createWidget("topic-map", {
|
|||||||
const contents = [this.buildTopicMapSummary(attrs, state)];
|
const contents = [this.buildTopicMapSummary(attrs, state)];
|
||||||
|
|
||||||
if (!state.collapsed) {
|
if (!state.collapsed) {
|
||||||
contents.push(this.attach("topic-map-expanded", attrs));
|
contents.push(this.buildTopicMapExpanded(attrs));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.hasTopRepliesSummary || attrs.summarizable) {
|
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) {
|
buildSummaryBox(attrs) {
|
||||||
return new RenderGlimmer(
|
return new RenderGlimmer(
|
||||||
this,
|
this,
|
||||||
|
@ -787,18 +787,10 @@ module("Integration | Component | Widget | post", function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
|
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
|
||||||
|
assert.dom("li.avatars a.poster").doesNotExist();
|
||||||
assert.ok(
|
|
||||||
!exists("li.avatars a.poster"),
|
|
||||||
"shows no participants when collapsed"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click("nav.buttons button");
|
await click("nav.buttons button");
|
||||||
assert.strictEqual(
|
assert.dom(".topic-map-expanded a.poster").exists({ count: 2 });
|
||||||
count(".topic-map-expanded a.poster"),
|
|
||||||
2,
|
|
||||||
"shows all when expanded"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("topic map - participants", async function (assert) {
|
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}} />`);
|
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
|
||||||
|
assert.dom("li.avatars a.poster").exists({ count: 3 });
|
||||||
assert.strictEqual(
|
|
||||||
count("li.avatars a.poster"),
|
|
||||||
3,
|
|
||||||
"limits to three participants"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click("nav.buttons button");
|
await click("nav.buttons button");
|
||||||
assert.ok(!exists("li.avatars a.poster"));
|
assert.dom("li.avatars a.poster").doesNotExist();
|
||||||
assert.strictEqual(
|
assert.dom(".topic-map-expanded a.poster").exists({ count: 4 });
|
||||||
count(".topic-map-expanded a.poster"),
|
assert.dom("a.poster.toggled").exists({ count: 2 });
|
||||||
4,
|
|
||||||
"shows all when expanded"
|
|
||||||
);
|
|
||||||
assert.strictEqual(count("a.poster.toggled"), 2, "two are toggled");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("topic map - links", async function (assert) {
|
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}} />`);
|
await render(hbs`<MountWidget @widget="post" @args={{this.args}} />`);
|
||||||
|
|
||||||
assert.strictEqual(count(".topic-map"), 1);
|
assert.dom(".topic-map").exists({ count: 1 });
|
||||||
assert.strictEqual(count(".map.map-collapsed"), 1);
|
assert.dom(".map.map-collapsed").exists({ count: 1 });
|
||||||
assert.ok(!exists(".topic-map-expanded"));
|
assert.dom(".topic-map-expanded").doesNotExist();
|
||||||
|
|
||||||
await click("nav.buttons button");
|
await click("nav.buttons button");
|
||||||
assert.ok(!exists(".map.map-collapsed"));
|
assert.dom(".map.map-collapsed").doesNotExist();
|
||||||
assert.strictEqual(count(".topic-map .d-icon-chevron-up"), 1);
|
assert.dom(".topic-map .d-icon-chevron-up").exists({ count: 1 });
|
||||||
assert.strictEqual(count(".topic-map-expanded"), 1);
|
assert.dom(".topic-map-expanded").exists({ count: 1 });
|
||||||
assert.strictEqual(
|
assert.dom(".topic-map-expanded .topic-link").exists({ count: 5 });
|
||||||
count(".topic-map-expanded .topic-link"),
|
|
||||||
5,
|
|
||||||
"it limits the links displayed"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click(".link-summary button");
|
await click(".link-summary button");
|
||||||
assert.strictEqual(
|
assert.dom(".topic-map-expanded .topic-link").exists({ count: 6 });
|
||||||
count(".topic-map-expanded .topic-link"),
|
|
||||||
6,
|
|
||||||
"all links now shown"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("topic map - no summary", async function (assert) {
|
test("topic map - no summary", async function (assert) {
|
||||||
|
@ -751,6 +751,9 @@ aside.quote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topic-links {
|
.topic-links {
|
||||||
|
tbody {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
tr {
|
tr {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user