DEV: Plugin API to add card listener elements (#13887)

This commit is contained in:
Mark VanLandingham 2021-07-29 14:25:10 -05:00 committed by GitHub
parent a049b8f596
commit 91456ad2cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 30 deletions

View File

@ -11,7 +11,7 @@ const maxMembersToDisplay = 10;
export default Component.extend(CardContentsBase, CleansUp, {
elementId: "group-card",
triggeringLinkClass: "mention-group",
mentionSelector: "a.mention-group",
classNames: ["no-bg", "group-card"],
classNameBindings: [
"visible:show",

View File

@ -16,7 +16,9 @@ import { prioritizeNameInUx } from "discourse/lib/settings";
export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
elementId: "user-card",
classNames: "user-card",
triggeringLinkClass: "mention",
avatarSelector: "[data-user-card]",
avatarDataAttrKey: "userCard",
mentionSelector: "a.mention",
classNameBindings: [
"visible:show",
"showBadges",

View File

@ -2,7 +2,8 @@ import DiscourseURL from "discourse/lib/url";
export function wantsNewWindow(e) {
return (
e.isDefaultPrevented() ||
e.defaultPrevented ||
(e.isDefaultPrevernted && e.isDefaultPrevented()) ||
e.shiftKey ||
e.metaKey ||
e.ctrlKey ||

View File

@ -37,6 +37,7 @@ import DiscourseBanner from "discourse/components/discourse-banner";
import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
import Sharing from "discourse/lib/sharing";
import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options";
import { addCardClickListenerSelector } from "discourse/mixins/card-contents-base";
import { addCategorySortCriteria } from "discourse/components/edit-category-settings";
import { addDecorator } from "discourse/widgets/post-cooked";
import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable";
@ -1084,6 +1085,15 @@ class PluginApi {
addCategorySortCriteria(criteria);
}
/**
* Card contents mixin will add a listener to elements matching this selector
* that will open card contents when a mention of div with the correct data attribute
* is clicked
*/
addCardClickListenerSelector(selector) {
addCardClickListenerSelector(selector);
}
/**
* Registers a renderer that overrides the display of category links.
*

View File

@ -8,6 +8,11 @@ import headerOutletHeights from "discourse/lib/header-outlet-height";
import { inject as service } from "@ember/service";
import { wantsNewWindow } from "discourse/lib/intercept-click";
let _cardClickListenerSelectors = ["#main-outlet"];
export function addCardClickListenerSelector(selector) {
_cardClickListenerSelectors.push(selector);
}
export default Mixin.create({
router: service(),
@ -26,7 +31,7 @@ export default Mixin.create({
isFixed: false,
isDocked: false,
_show(username, $target) {
_show(username, target) {
// No user card for anon
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
return false;
@ -35,9 +40,9 @@ export default Mixin.create({
username = escapeExpression(username.toString());
// Don't show if nested
if ($target.parents(".card-content").length) {
if (target.closest(".card-content")) {
this._close();
DiscourseURL.routeTo($target.attr("href"));
DiscourseURL.routeTo(target.href);
return false;
}
@ -46,10 +51,10 @@ export default Mixin.create({
return;
}
const postId = $target.parents("article").data("post-id");
const closestArticle = target.closest("article");
const postId = closestArticle ? closestArticle.dataset["post-id"] : null;
const wasVisible = this.visible;
const previousTarget = this.cardTarget;
const target = $target[0];
if (wasVisible) {
this._close();
@ -69,7 +74,7 @@ export default Mixin.create({
post,
});
this._showCallback(username, $target);
this._showCallback(username, $(target));
// We bind scrolling on mobile after cards are shown to hide them if user scrolls
if (this.site.mobileView) {
@ -85,15 +90,12 @@ export default Mixin.create({
const id = this.elementId;
const triggeringLinkClass = this.triggeringLinkClass;
const clickOutsideEventName = `mousedown.outside-${id}`;
const clickDataExpand = `click.discourse-${id}`;
const clickMention = `click.discourse-${id}-${triggeringLinkClass}`;
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
const mobileScrollEvent = "scroll.mobile-card-cloak";
this.setProperties({
boundCardClickHandler: this._cardClickHandler.bind(this),
clickOutsideEventName,
clickDataExpand,
clickMention,
previewClickEvent,
mobileScrollEvent,
});
@ -117,20 +119,10 @@ export default Mixin.create({
return true;
});
$("#main-outlet").on(clickDataExpand, `[data-${id}]`, (e) => {
if (wantsNewWindow(e)) {
return;
}
const $target = $(e.currentTarget);
return this._show($target.data(id), $target);
});
$("#main-outlet").on(clickMention, `a.${triggeringLinkClass}`, (e) => {
if (wantsNewWindow(e)) {
return;
}
const $target = $(e.currentTarget);
return this._show($target.text().replace(/^@/, ""), $target);
_cardClickListenerSelectors.forEach((selector) => {
document
.querySelector(selector)
.addEventListener("click", this.boundCardClickHandler);
});
this.appEvents.on(previewClickEvent, this, "_previewClick");
@ -142,6 +134,41 @@ export default Mixin.create({
);
},
_cardClickHandler(event) {
if (this.avatarSelector) {
let matched = this._showCardOnClick(
event,
this.avatarSelector,
(el) => el.dataset[this.avatarDataAttrKey]
);
if (matched) {
return; // Don't need to check for mention click; it's an avatar click
}
}
// Mention click
this._showCardOnClick(event, this.mentionSelector, (el) =>
el.innerText.replace(/^@/, "")
);
},
_showCardOnClick(event, selector, transformText) {
let matchingEl = event.target.closest(selector);
if (matchingEl) {
if (wantsNewWindow(event)) {
return true;
}
event.preventDefault();
event.stopPropagation();
return this._show(transformText(matchingEl), matchingEl);
}
{
return false;
}
},
_topicHeaderTrigger(username, $target) {
this.setProperties({ isFixed: true, isDocked: true });
return this._show(username, $target);
@ -302,12 +329,14 @@ export default Mixin.create({
willDestroyElement() {
this._super(...arguments);
const clickOutsideEventName = this.clickOutsideEventName;
const clickDataExpand = this.clickDataExpand;
const clickMention = this.clickMention;
const previewClickEvent = this.previewClickEvent;
$("html").off(clickOutsideEventName);
$("#main").off(clickDataExpand).off(clickMention);
_cardClickListenerSelectors.forEach((selector) => {
document
.querySelector(selector)
.removeEventListener("click", this.boundCardClickHandler);
});
this.appEvents.off(previewClickEvent, this, "_previewClick");