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, { export default Component.extend(CardContentsBase, CleansUp, {
elementId: "group-card", elementId: "group-card",
triggeringLinkClass: "mention-group", mentionSelector: "a.mention-group",
classNames: ["no-bg", "group-card"], classNames: ["no-bg", "group-card"],
classNameBindings: [ classNameBindings: [
"visible:show", "visible:show",

View File

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

View File

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

View File

@ -37,6 +37,7 @@ import DiscourseBanner from "discourse/components/discourse-banner";
import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
import Sharing from "discourse/lib/sharing"; import Sharing from "discourse/lib/sharing";
import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options"; 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 { addCategorySortCriteria } from "discourse/components/edit-category-settings";
import { addDecorator } from "discourse/widgets/post-cooked"; import { addDecorator } from "discourse/widgets/post-cooked";
import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable"; import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable";
@ -1084,6 +1085,15 @@ class PluginApi {
addCategorySortCriteria(criteria); 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. * 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 { inject as service } from "@ember/service";
import { wantsNewWindow } from "discourse/lib/intercept-click"; import { wantsNewWindow } from "discourse/lib/intercept-click";
let _cardClickListenerSelectors = ["#main-outlet"];
export function addCardClickListenerSelector(selector) {
_cardClickListenerSelectors.push(selector);
}
export default Mixin.create({ export default Mixin.create({
router: service(), router: service(),
@ -26,7 +31,7 @@ export default Mixin.create({
isFixed: false, isFixed: false,
isDocked: false, isDocked: false,
_show(username, $target) { _show(username, target) {
// No user card for anon // No user card for anon
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
return false; return false;
@ -35,9 +40,9 @@ export default Mixin.create({
username = escapeExpression(username.toString()); username = escapeExpression(username.toString());
// Don't show if nested // Don't show if nested
if ($target.parents(".card-content").length) { if (target.closest(".card-content")) {
this._close(); this._close();
DiscourseURL.routeTo($target.attr("href")); DiscourseURL.routeTo(target.href);
return false; return false;
} }
@ -46,10 +51,10 @@ export default Mixin.create({
return; 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 wasVisible = this.visible;
const previousTarget = this.cardTarget; const previousTarget = this.cardTarget;
const target = $target[0];
if (wasVisible) { if (wasVisible) {
this._close(); this._close();
@ -69,7 +74,7 @@ export default Mixin.create({
post, post,
}); });
this._showCallback(username, $target); this._showCallback(username, $(target));
// We bind scrolling on mobile after cards are shown to hide them if user scrolls // We bind scrolling on mobile after cards are shown to hide them if user scrolls
if (this.site.mobileView) { if (this.site.mobileView) {
@ -85,15 +90,12 @@ export default Mixin.create({
const id = this.elementId; const id = this.elementId;
const triggeringLinkClass = this.triggeringLinkClass; const triggeringLinkClass = this.triggeringLinkClass;
const clickOutsideEventName = `mousedown.outside-${id}`; const clickOutsideEventName = `mousedown.outside-${id}`;
const clickDataExpand = `click.discourse-${id}`;
const clickMention = `click.discourse-${id}-${triggeringLinkClass}`;
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`; const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
const mobileScrollEvent = "scroll.mobile-card-cloak"; const mobileScrollEvent = "scroll.mobile-card-cloak";
this.setProperties({ this.setProperties({
boundCardClickHandler: this._cardClickHandler.bind(this),
clickOutsideEventName, clickOutsideEventName,
clickDataExpand,
clickMention,
previewClickEvent, previewClickEvent,
mobileScrollEvent, mobileScrollEvent,
}); });
@ -117,20 +119,10 @@ export default Mixin.create({
return true; return true;
}); });
$("#main-outlet").on(clickDataExpand, `[data-${id}]`, (e) => { _cardClickListenerSelectors.forEach((selector) => {
if (wantsNewWindow(e)) { document
return; .querySelector(selector)
} .addEventListener("click", this.boundCardClickHandler);
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);
}); });
this.appEvents.on(previewClickEvent, this, "_previewClick"); 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) { _topicHeaderTrigger(username, $target) {
this.setProperties({ isFixed: true, isDocked: true }); this.setProperties({ isFixed: true, isDocked: true });
return this._show(username, $target); return this._show(username, $target);
@ -302,12 +329,14 @@ export default Mixin.create({
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
const clickOutsideEventName = this.clickOutsideEventName; const clickOutsideEventName = this.clickOutsideEventName;
const clickDataExpand = this.clickDataExpand;
const clickMention = this.clickMention;
const previewClickEvent = this.previewClickEvent; const previewClickEvent = this.previewClickEvent;
$("html").off(clickOutsideEventName); $("html").off(clickOutsideEventName);
$("#main").off(clickDataExpand).off(clickMention); _cardClickListenerSelectors.forEach((selector) => {
document
.querySelector(selector)
.removeEventListener("click", this.boundCardClickHandler);
});
this.appEvents.off(previewClickEvent, this, "_previewClick"); this.appEvents.off(previewClickEvent, this, "_previewClick");