FIX: uses popper for cards
This commit is contained in:
parent
fda834d01c
commit
73488f2f33
|
@ -2,12 +2,13 @@ import { alias, match } from "@ember/object/computed";
|
||||||
import { schedule, throttle } from "@ember/runloop";
|
import { schedule, throttle } from "@ember/runloop";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import Mixin from "@ember/object/mixin";
|
import Mixin from "@ember/object/mixin";
|
||||||
import afterTransition from "discourse/lib/after-transition";
|
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
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";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import discourseLater from "discourse-common/lib/later";
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
import { createPopper } from "@popperjs/core";
|
||||||
|
import { headerOffset } from "discourse/lib/offset-calculator";
|
||||||
|
|
||||||
const DEFAULT_SELECTOR = "#main-outlet";
|
const DEFAULT_SELECTOR = "#main-outlet";
|
||||||
|
|
||||||
|
@ -36,9 +37,10 @@ export default Mixin.create({
|
||||||
loading: null,
|
loading: null,
|
||||||
cardTarget: null,
|
cardTarget: null,
|
||||||
post: null,
|
post: null,
|
||||||
isFixed: false,
|
|
||||||
isDocked: false,
|
isDocked: false,
|
||||||
|
|
||||||
|
_popperReference: null,
|
||||||
|
|
||||||
_show(username, target, event) {
|
_show(username, target, event) {
|
||||||
// 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) {
|
||||||
|
@ -56,12 +58,6 @@ export default Mixin.create({
|
||||||
|
|
||||||
this.appEvents.trigger("card:show", username, target, event);
|
this.appEvents.trigger("card:show", username, target, event);
|
||||||
|
|
||||||
const currentUsername = this.username;
|
|
||||||
if (username === currentUsername || this.loading === username) {
|
|
||||||
this._positionCard($(target));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const closestArticle = target.closest("article");
|
const closestArticle = target.closest("article");
|
||||||
const postId = closestArticle?.dataset?.postId || null;
|
const postId = closestArticle?.dataset?.postId || null;
|
||||||
const wasVisible = this.visible;
|
const wasVisible = this.visible;
|
||||||
|
@ -88,6 +84,7 @@ export default Mixin.create({
|
||||||
this.appEvents.trigger("user-card:show", { username });
|
this.appEvents.trigger("user-card:show", { username });
|
||||||
this._showCallback(username, $(target)).then((user) => {
|
this._showCallback(username, $(target)).then((user) => {
|
||||||
this.appEvents.trigger("user-card:after-show", { user });
|
this.appEvents.trigger("user-card:after-show", { user });
|
||||||
|
this._positionCard($(target), event);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -100,7 +97,7 @@ export default Mixin.create({
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
afterTransition($(this.element), this._hide);
|
|
||||||
const id = this.elementId;
|
const id = this.elementId;
|
||||||
const triggeringLinkClass = this.triggeringLinkClass;
|
const triggeringLinkClass = this.triggeringLinkClass;
|
||||||
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
|
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
|
||||||
|
@ -168,7 +165,7 @@ export default Mixin.create({
|
||||||
},
|
},
|
||||||
|
|
||||||
_topicHeaderTrigger(username, target) {
|
_topicHeaderTrigger(username, target) {
|
||||||
this.setProperties({ isFixed: true, isDocked: true });
|
this.setProperties({ isDocked: true });
|
||||||
return this._show(username, target);
|
return this._show(username, target);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -188,104 +185,75 @@ export default Mixin.create({
|
||||||
},
|
},
|
||||||
|
|
||||||
_previewClick($target) {
|
_previewClick($target) {
|
||||||
this.set("isFixed", true);
|
|
||||||
return this._show($target.text().replace(/^@/, ""), $target);
|
return this._show($target.text().replace(/^@/, ""), $target);
|
||||||
},
|
},
|
||||||
|
|
||||||
_positionCard(target) {
|
_positionCard(target, event) {
|
||||||
const rtl = $("html").css("direction") === "rtl";
|
this._popperReference?.destroy();
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const width = $(this.element).width();
|
|
||||||
const height = 175;
|
|
||||||
const isFixed = this.isFixed;
|
|
||||||
const isDocked = this.isDocked;
|
|
||||||
|
|
||||||
let verticalAdjustments = 0;
|
|
||||||
|
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
if (target) {
|
if (!target) {
|
||||||
if (!this.site.mobileView) {
|
return;
|
||||||
let position = target.offset();
|
}
|
||||||
if (target.parents(".d-header").length > 0) {
|
|
||||||
position.top = target.position().top;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position) {
|
if (this.site.desktopView) {
|
||||||
position.bottom = "unset";
|
const avatarOverflowSize = 44;
|
||||||
|
this._popperReference = createPopper(target[0], this.element, {
|
||||||
|
placement: "right",
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
options: {
|
||||||
|
padding: {
|
||||||
|
top: headerOffset() + avatarOverflowSize,
|
||||||
|
right: 10,
|
||||||
|
bottom: 10,
|
||||||
|
left: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: "eventListeners", enabled: false },
|
||||||
|
{ name: "offset", options: { offset: [10, 10] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.querySelector(".card-cloak")?.classList.remove("hidden");
|
||||||
|
this._popperReference = createPopper(target[0], this.element, {
|
||||||
|
modifiers: [
|
||||||
|
{ name: "eventListeners", enabled: false },
|
||||||
|
{
|
||||||
|
name: "computeStyles",
|
||||||
|
enabled: true,
|
||||||
|
fn({ state }) {
|
||||||
|
// mimics our modal top of the screen positioning
|
||||||
|
state.styles.popper = {
|
||||||
|
...state.styles.popper,
|
||||||
|
position: "fixed",
|
||||||
|
left: `${
|
||||||
|
(window.innerWidth - state.rects.popper.width) / 2
|
||||||
|
}px`,
|
||||||
|
top: "10%",
|
||||||
|
transform: "translateY(-10%)",
|
||||||
|
};
|
||||||
|
|
||||||
if (rtl) {
|
return state;
|
||||||
// The site direction is rtl
|
},
|
||||||
position.right = $(window).width() - position.left + 10;
|
},
|
||||||
position.left = "auto";
|
],
|
||||||
let overage = $(window).width() - 50 - (position.right + width);
|
});
|
||||||
if (overage < 0) {
|
}
|
||||||
position.right += overage;
|
|
||||||
position.top += target.height() + 48;
|
|
||||||
verticalAdjustments += target.height() + 48;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The site direction is ltr
|
|
||||||
position.left += target.width() + 10;
|
|
||||||
|
|
||||||
let overage = $(window).width() - 50 - (position.left + width);
|
this.element.classList.toggle("docked-card", this.isDocked);
|
||||||
if (overage < 0) {
|
|
||||||
position.left += overage;
|
|
||||||
position.top += target.height() + 48;
|
|
||||||
verticalAdjustments += target.height() + 48;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It looks better to have the card aligned slightly higher
|
// After the card is shown, focus on the first link
|
||||||
position.top -= 24;
|
//
|
||||||
|
// note: we DO NOT use afterRender here cause _positionCard may
|
||||||
if (isFixed) {
|
// run afterwards, if we allowed this to happen the usercard
|
||||||
position.top -= $("html").scrollTop();
|
// may be offscreen and we may scroll all the way to it on focus
|
||||||
//if content is fixed and will be cut off on the bottom, display it above...
|
if (event?.pointerId === -1) {
|
||||||
if (
|
discourseLater(() => {
|
||||||
position.top + height + verticalAdjustments >
|
this.element.querySelector("a")?.focus();
|
||||||
$(window).height() - 50
|
}, 350);
|
||||||
) {
|
|
||||||
position.bottom =
|
|
||||||
$(window).height() -
|
|
||||||
(target.offset().top - $("html").scrollTop());
|
|
||||||
if (verticalAdjustments > 0) {
|
|
||||||
position.bottom += 48;
|
|
||||||
}
|
|
||||||
position.top = "unset";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatarOverflowSize = 44;
|
|
||||||
if (isDocked && position.top < avatarOverflowSize) {
|
|
||||||
position.top = avatarOverflowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this.element).css(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.site.mobileView) {
|
|
||||||
$(".card-cloak").removeClass("hidden");
|
|
||||||
let position = target.offset();
|
|
||||||
position.top = "10%"; // match modal behaviour
|
|
||||||
position.left = 0;
|
|
||||||
$(this.element).css(position);
|
|
||||||
}
|
|
||||||
$(this.element).toggleClass("docked-card", isDocked);
|
|
||||||
|
|
||||||
// After the card is shown, focus on the first link
|
|
||||||
//
|
|
||||||
// note: we DO NOT use afterRender here cause _positionCard may
|
|
||||||
// run afterwards, if we allowed this to happen the usercard
|
|
||||||
// may be offscreen and we may scroll all the way to it on focus
|
|
||||||
if (event.pointerId === -1) {
|
|
||||||
discourseLater(() => {
|
|
||||||
const firstLink = this.element.querySelector("a");
|
|
||||||
firstLink && firstLink.focus();
|
|
||||||
}, 350);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -307,7 +275,6 @@ export default Mixin.create({
|
||||||
loading: null,
|
loading: null,
|
||||||
cardTarget: null,
|
cardTarget: null,
|
||||||
post: null,
|
post: null,
|
||||||
isFixed: false,
|
|
||||||
isDocked: false,
|
isDocked: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,8 @@ $avatar_margin: -50px; // negative margin makes avatars extend above cards
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
background: var(--secondary) center center;
|
background: var(--secondary) center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
transition: opacity 0.2s, transform 0.2s;
|
|
||||||
opacity: 0;
|
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
@include transform(scale(0.9));
|
|
||||||
&.show {
|
|
||||||
opacity: 1;
|
|
||||||
@include transform(scale(1));
|
|
||||||
}
|
|
||||||
.card-content {
|
.card-content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: rgba(var(--secondary-rgb), 0.85);
|
background: rgba(var(--secondary-rgb), 0.85);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// shared styles for user and group cards
|
// shared styles for user and group cards
|
||||||
.user-card,
|
.user-card,
|
||||||
.group-card {
|
.group-card {
|
||||||
position: absolute;
|
|
||||||
z-index: z("usercard");
|
z-index: z("usercard");
|
||||||
&.fixed {
|
&.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -3,7 +3,6 @@ $avatar_width: 120px;
|
||||||
// shared styles for user and group cards
|
// shared styles for user and group cards
|
||||||
.user-card,
|
.user-card,
|
||||||
.group-card {
|
.group-card {
|
||||||
position: fixed;
|
|
||||||
// mobile cards should always be on top of everything - 1102
|
// mobile cards should always be on top of everything - 1102
|
||||||
z-index: z("mobile-composer") + 2;
|
z-index: z("mobile-composer") + 2;
|
||||||
max-width: 95vw;
|
max-width: 95vw;
|
||||||
|
|
Loading…
Reference in New Issue