When narrow screen is enable and hamburgerVisible is set to true, transition to wide screen is breaking user-menu button. We need to reset hamburgerVisible and domClean is a great way to achieve it.
528 lines
16 KiB
JavaScript
528 lines
16 KiB
JavaScript
import PanEvents, {
|
|
SWIPE_DISTANCE_THRESHOLD,
|
|
SWIPE_VELOCITY_THRESHOLD,
|
|
} from "discourse/mixins/pan-events";
|
|
import { cancel, schedule } from "@ember/runloop";
|
|
import discourseLater from "discourse-common/lib/later";
|
|
import Docking from "discourse/mixins/docking";
|
|
import MountWidget from "discourse/components/mount-widget";
|
|
import ItsATrap from "@discourse/itsatrap";
|
|
import RerenderOnDoNotDisturbChange from "discourse/mixins/rerender-on-do-not-disturb-change";
|
|
import { observes } from "discourse-common/utils/decorators";
|
|
import { topicTitleDecorators } from "discourse/components/topic-title";
|
|
|
|
const SiteHeaderComponent = MountWidget.extend(
|
|
Docking,
|
|
PanEvents,
|
|
RerenderOnDoNotDisturbChange,
|
|
{
|
|
widget: "header",
|
|
docAt: null,
|
|
dockedHeader: null,
|
|
_animate: false,
|
|
_isPanning: false,
|
|
_panMenuOrigin: "right",
|
|
_panMenuOffset: 0,
|
|
_scheduledRemoveAnimate: null,
|
|
_topic: null,
|
|
_itsatrap: null,
|
|
|
|
@observes(
|
|
"currentUser.unread_notifications",
|
|
"currentUser.unread_high_priority_notifications",
|
|
"currentUser.all_unread_notifications_count",
|
|
"currentUser.reviewable_count", // TODO: remove this when redesigned_user_menu_enabled is removed
|
|
"currentUser.unseen_reviewable_count",
|
|
"session.defaultColorSchemeIsDark",
|
|
"session.darkModeAvailable"
|
|
)
|
|
notificationsChanged() {
|
|
this.queueRerender();
|
|
},
|
|
|
|
@observes("site.narrowDesktopView")
|
|
narrowDesktopViewChanged() {
|
|
this.eventDispatched("dom:clean", "header");
|
|
if (
|
|
this.siteSettings.enable_experimental_sidebar_hamburger &&
|
|
(!this.sidebarEnabled || this.site.narrowDesktopView)
|
|
) {
|
|
this.appEvents.on(
|
|
"sidebar-hamburger-dropdown:rendered",
|
|
this,
|
|
"_animateMenu"
|
|
);
|
|
}
|
|
},
|
|
|
|
_animateOpening(panel) {
|
|
const headerCloak = document.querySelector(".header-cloak");
|
|
panel.classList.add("animate");
|
|
headerCloak.classList.add("animate");
|
|
this._scheduledRemoveAnimate = discourseLater(() => {
|
|
panel.classList.remove("animate");
|
|
headerCloak.classList.remove("animate");
|
|
}, 200);
|
|
panel.style.setProperty("--offset", 0);
|
|
headerCloak.style.setProperty("--opacity", 0.5);
|
|
this._panMenuOffset = 0;
|
|
},
|
|
|
|
_animateClosing(panel, menuOrigin) {
|
|
const windowWidth = document.body.offsetWidth;
|
|
this._animate = true;
|
|
const headerCloak = document.querySelector(".header-cloak");
|
|
panel.classList.add("animate");
|
|
headerCloak.classList.add("animate");
|
|
const offsetDirection = menuOrigin === "left" ? -1 : 1;
|
|
panel.style.setProperty("--offset", `${offsetDirection * windowWidth}px`);
|
|
headerCloak.style.setProperty("--opacity", 0);
|
|
this._scheduledRemoveAnimate = discourseLater(() => {
|
|
panel.classList.remove("animate");
|
|
headerCloak.classList.remove("animate");
|
|
schedule("afterRender", () => {
|
|
this.eventDispatched("dom:clean", "header");
|
|
this._panMenuOffset = 0;
|
|
});
|
|
}, 200);
|
|
},
|
|
|
|
_isRTL() {
|
|
return document.querySelector("html").classList["direction"] === "rtl";
|
|
},
|
|
|
|
_leftMenuClass() {
|
|
return this._isRTL() ? "user-menu" : "hamburger-panel";
|
|
},
|
|
|
|
_handlePanDone(event) {
|
|
const menuPanels = document.querySelectorAll(".menu-panel");
|
|
const menuOrigin = this._panMenuOrigin;
|
|
menuPanels.forEach((panel) => {
|
|
panel.classList.remove("moving");
|
|
if (this._shouldMenuClose(event, menuOrigin)) {
|
|
this._animateClosing(panel, menuOrigin);
|
|
} else {
|
|
this._animateOpening(panel);
|
|
}
|
|
});
|
|
},
|
|
|
|
_shouldMenuClose(e, menuOrigin) {
|
|
// menu should close after a pan either:
|
|
// if a user moved the panel closed past a threshold and away and is NOT swiping back open
|
|
// if a user swiped to close fast enough regardless of distance
|
|
if (menuOrigin === "right") {
|
|
return (
|
|
(e.deltaX > SWIPE_DISTANCE_THRESHOLD &&
|
|
e.velocityX > -SWIPE_VELOCITY_THRESHOLD) ||
|
|
e.velocityX > 0
|
|
);
|
|
} else {
|
|
return (
|
|
(e.deltaX < -SWIPE_DISTANCE_THRESHOLD &&
|
|
e.velocityX < SWIPE_VELOCITY_THRESHOLD) ||
|
|
e.velocityX < 0
|
|
);
|
|
}
|
|
},
|
|
|
|
panStart(e) {
|
|
const center = e.center;
|
|
const panOverValidElement = document
|
|
.elementsFromPoint(center.x, center.y)
|
|
.some(
|
|
(ele) =>
|
|
ele.classList.contains("panel-body") ||
|
|
ele.classList.contains("header-cloak")
|
|
);
|
|
if (
|
|
panOverValidElement &&
|
|
(e.direction === "left" || e.direction === "right")
|
|
) {
|
|
e.originalEvent.preventDefault();
|
|
this._isPanning = true;
|
|
const panel = document.querySelector(".menu-panel");
|
|
if (panel) {
|
|
panel.classList.add("moving");
|
|
}
|
|
} else {
|
|
this._isPanning = false;
|
|
}
|
|
},
|
|
|
|
panEnd(e) {
|
|
if (!this._isPanning) {
|
|
return;
|
|
}
|
|
this._isPanning = false;
|
|
this._handlePanDone(e);
|
|
},
|
|
|
|
panMove(e) {
|
|
if (!this._isPanning) {
|
|
return;
|
|
}
|
|
const panel = document.querySelector(".menu-panel");
|
|
const headerCloak = document.querySelector(".header-cloak");
|
|
if (this._panMenuOrigin === "right") {
|
|
const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
|
|
panel.style.setProperty("--offset", `${-pxClosed}px`);
|
|
headerCloak.style.setProperty(
|
|
"--opacity",
|
|
Math.min(0.5, (300 + pxClosed) / 600)
|
|
);
|
|
} else {
|
|
const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
|
|
panel.style.setProperty("--offset", `${pxClosed}px`);
|
|
headerCloak.style.setProperty(
|
|
"--opacity",
|
|
Math.min(0.5, (300 + pxClosed) / 600)
|
|
);
|
|
}
|
|
},
|
|
|
|
dockCheck() {
|
|
const header = document.querySelector("header.d-header");
|
|
|
|
if (this.docAt === null) {
|
|
if (!header) {
|
|
return;
|
|
}
|
|
this.docAt = header.offsetTop;
|
|
}
|
|
|
|
const main = document.querySelector(".ember-application");
|
|
const offsetTop = main ? main.offsetTop : 0;
|
|
const offset = window.pageYOffset - offsetTop;
|
|
if (offset >= this.docAt) {
|
|
if (!this.dockedHeader) {
|
|
document.body.classList.add("docked");
|
|
this.dockedHeader = true;
|
|
}
|
|
} else {
|
|
if (this.dockedHeader) {
|
|
document.body.classList.remove("docked");
|
|
this.dockedHeader = false;
|
|
}
|
|
}
|
|
},
|
|
|
|
setTopic(topic) {
|
|
this.eventDispatched("dom:clean", "header");
|
|
this._topic = topic;
|
|
this.queueRerender();
|
|
},
|
|
|
|
willRender() {
|
|
if (this.get("currentUser.staff")) {
|
|
document.body.classList.add("staff");
|
|
}
|
|
},
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
this._resizeDiscourseMenuPanel = () => this.afterRender();
|
|
window.addEventListener("resize", this._resizeDiscourseMenuPanel);
|
|
|
|
this.appEvents.on("header:show-topic", this, "setTopic");
|
|
this.appEvents.on("header:hide-topic", this, "setTopic");
|
|
|
|
if (this.currentUser?.redesigned_user_menu_enabled) {
|
|
this.appEvents.on("user-menu:rendered", this, "_animateMenu");
|
|
}
|
|
|
|
if (
|
|
this.siteSettings.enable_experimental_sidebar_hamburger &&
|
|
(!this.sidebarEnabled || this.site.narrowDesktopView)
|
|
) {
|
|
this.appEvents.on(
|
|
"sidebar-hamburger-dropdown:rendered",
|
|
this,
|
|
"_animateMenu"
|
|
);
|
|
}
|
|
|
|
this.dispatch("notifications:changed", "user-notifications");
|
|
this.dispatch("header:keyboard-trigger", "header");
|
|
this.dispatch("user-menu:navigation", "user-menu");
|
|
|
|
this.appEvents.on("dom:clean", this, "_cleanDom");
|
|
|
|
if (this.currentUser) {
|
|
this.currentUser.on("status-changed", this, "queueRerender");
|
|
}
|
|
|
|
const header = document.querySelector("header.d-header");
|
|
this._itsatrap = new ItsATrap(header);
|
|
const dirs = this.currentUser?.redesigned_user_menu_enabled
|
|
? ["up", "down"]
|
|
: ["right", "left"];
|
|
this._itsatrap.bind(dirs, (e) => this._handleArrowKeysNav(e));
|
|
},
|
|
|
|
_handleArrowKeysNav(event) {
|
|
if (this.currentUser?.redesigned_user_menu_enabled) {
|
|
const activeTab = document.querySelector(
|
|
".menu-tabs-container .btn.active"
|
|
);
|
|
if (activeTab) {
|
|
let activeTabNumber = Number(
|
|
document.activeElement.dataset.tabNumber ||
|
|
activeTab.dataset.tabNumber
|
|
);
|
|
const maxTabNumber =
|
|
document.querySelectorAll(".menu-tabs-container .btn").length - 1;
|
|
const isNext = event.key === "ArrowDown";
|
|
let nextTab = isNext ? activeTabNumber + 1 : activeTabNumber - 1;
|
|
if (isNext && nextTab > maxTabNumber) {
|
|
nextTab = 0;
|
|
}
|
|
if (!isNext && nextTab < 0) {
|
|
nextTab = maxTabNumber;
|
|
}
|
|
event.preventDefault();
|
|
document
|
|
.querySelector(
|
|
`.menu-tabs-container .btn[data-tab-number='${nextTab}']`
|
|
)
|
|
.focus();
|
|
}
|
|
} else {
|
|
const activeTab = document.querySelector(".glyphs .menu-link.active");
|
|
|
|
if (activeTab) {
|
|
let focusedTab = document.activeElement;
|
|
if (!focusedTab.dataset.tabNumber) {
|
|
focusedTab = activeTab;
|
|
}
|
|
|
|
this.appEvents.trigger("user-menu:navigation", {
|
|
key: event.key,
|
|
tabNumber: Number(focusedTab.dataset.tabNumber),
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
_cleanDom() {
|
|
// For performance, only trigger a re-render if any menu panels are visible
|
|
if (this.element.querySelector(".menu-panel")) {
|
|
this.eventDispatched("dom:clean", "header");
|
|
}
|
|
},
|
|
|
|
willDestroyElement() {
|
|
this._super(...arguments);
|
|
|
|
window.removeEventListener("resize", this._resizeDiscourseMenuPanel);
|
|
|
|
this.appEvents.off("header:show-topic", this, "setTopic");
|
|
this.appEvents.off("header:hide-topic", this, "setTopic");
|
|
this.appEvents.off("dom:clean", this, "_cleanDom");
|
|
if (this.currentUser?.redesigned_user_menu_enabled) {
|
|
this.appEvents.off("user-menu:rendered", this, "_animateMenu");
|
|
}
|
|
|
|
if (
|
|
this.siteSettings.enable_experimental_sidebar_hamburger &&
|
|
!this.sidebarEnabled
|
|
) {
|
|
this.appEvents.off(
|
|
"sidebar-hamburger-dropdown:rendered",
|
|
this,
|
|
"_animateMenu"
|
|
);
|
|
}
|
|
|
|
if (this.currentUser) {
|
|
this.currentUser.off("status-changed", this, "queueRerender");
|
|
}
|
|
|
|
cancel(this._scheduledRemoveAnimate);
|
|
|
|
this._itsatrap?.destroy();
|
|
this._itsatrap = null;
|
|
},
|
|
|
|
buildArgs() {
|
|
return {
|
|
topic: this._topic,
|
|
canSignUp: this.canSignUp,
|
|
sidebarEnabled: this.sidebarEnabled,
|
|
showSidebar: this.showSidebar,
|
|
};
|
|
},
|
|
|
|
afterRender() {
|
|
const headerTitle = document.querySelector(".header-title .topic-link");
|
|
if (headerTitle && this._topic) {
|
|
topicTitleDecorators.forEach((cb) =>
|
|
cb(this._topic, headerTitle, "header-title")
|
|
);
|
|
}
|
|
this._animateMenu();
|
|
},
|
|
|
|
_animateMenu() {
|
|
const menuPanels = document.querySelectorAll(".menu-panel");
|
|
|
|
if (menuPanels.length === 0) {
|
|
this._animate = this.site.mobileView || this.site.narrowDesktopView;
|
|
return;
|
|
}
|
|
|
|
const windowWidth = document.body.offsetWidth;
|
|
const viewMode =
|
|
this.site.mobileView || this.site.narrowDesktopView
|
|
? "slide-in"
|
|
: "drop-down";
|
|
|
|
menuPanels.forEach((panel) => {
|
|
const headerCloak = document.querySelector(".header-cloak");
|
|
let width = parseInt(panel.getAttribute("data-max-width"), 10) || 300;
|
|
if (windowWidth - width < 50) {
|
|
width = windowWidth - 50;
|
|
}
|
|
if (this._panMenuOffset) {
|
|
this._panMenuOffset = -width;
|
|
}
|
|
|
|
panel.classList.remove("drop-down");
|
|
panel.classList.remove("slide-in");
|
|
panel.classList.add(viewMode);
|
|
if (this._animate || this._panMenuOffset !== 0) {
|
|
if (
|
|
(this.site.mobileView || this.site.narrowDesktopView) &&
|
|
panel.parentElement.classList.contains(this._leftMenuClass())
|
|
) {
|
|
this._panMenuOrigin = "left";
|
|
panel.style.setProperty("--offset", `${-windowWidth}px`);
|
|
} else {
|
|
this._panMenuOrigin = "right";
|
|
panel.style.setProperty("--offset", `${windowWidth}px`);
|
|
}
|
|
headerCloak.style.setProperty("--opacity", 0);
|
|
}
|
|
|
|
const panelBody = panel.querySelector(".panel-body");
|
|
|
|
// We use a mutationObserver to check for style changes, so it's important
|
|
// we don't set it if it doesn't change. Same goes for the panelBody!
|
|
|
|
if (!this.site.mobileView && !this.site.narrowDesktopView) {
|
|
const buttonPanel = document.querySelectorAll("header ul.icons");
|
|
if (buttonPanel.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// These values need to be set here, not in the css file - this is to deal with the
|
|
// possibility of the window being resized and the menu changing from .slide-in to .drop-down.
|
|
if (panel.style.top !== "100%" || panel.style.height !== "auto") {
|
|
panel.style.setProperty("top", "100%");
|
|
panel.style.setProperty("height", "auto");
|
|
}
|
|
} else {
|
|
headerCloak.style.display = "block";
|
|
|
|
const menuTop = headerTop();
|
|
|
|
const winHeightOffset = this.currentUser?.redesigned_user_menu_enabled
|
|
? 0
|
|
: 16;
|
|
let initialWinHeight = window.innerHeight;
|
|
const winHeight = initialWinHeight - winHeightOffset;
|
|
|
|
let height = winHeight - menuTop;
|
|
|
|
const isIPadApp = document.body.classList.contains("footer-nav-ipad"),
|
|
heightProp = isIPadApp ? "max-height" : "height",
|
|
iPadOffset = 10;
|
|
|
|
if (isIPadApp) {
|
|
height = winHeight - menuTop - iPadOffset;
|
|
}
|
|
|
|
if (panelBody.style.height !== "100%") {
|
|
panelBody.style.setProperty("height", "100%");
|
|
}
|
|
if (
|
|
panel.style.top !== `${menuTop}px` ||
|
|
panel.style[heightProp] !== `${height}px`
|
|
) {
|
|
panel.style.top = `${menuTop}px`;
|
|
panel.style.setProperty(heightProp, `${height}px`);
|
|
if (headerCloak) {
|
|
headerCloak.style.top = `${menuTop}px`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: remove the if condition when redesigned_user_menu_enabled is
|
|
// removed
|
|
if (!panel.classList.contains("revamped")) {
|
|
panel.style.setProperty("width", `${width}px`);
|
|
}
|
|
if (this._animate) {
|
|
this._animateOpening(panel);
|
|
}
|
|
this._animate = false;
|
|
});
|
|
},
|
|
}
|
|
);
|
|
|
|
export default SiteHeaderComponent.extend({
|
|
classNames: ["d-header-wrap"],
|
|
classNameBindings: ["site.mobileView::drop-down-mode"],
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
|
|
this._resizeObserver = null;
|
|
},
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
|
|
this.appEvents.on("site-header:force-refresh", this, "queueRerender");
|
|
|
|
const header = document.querySelector(".d-header-wrap");
|
|
if (header) {
|
|
schedule("afterRender", () => {
|
|
document.documentElement.style.setProperty(
|
|
"--header-offset",
|
|
`${header.offsetHeight}px`
|
|
);
|
|
});
|
|
}
|
|
|
|
if ("ResizeObserver" in window) {
|
|
this._resizeObserver = new ResizeObserver((entries) => {
|
|
for (let entry of entries) {
|
|
if (entry.contentRect) {
|
|
document.documentElement.style.setProperty(
|
|
"--header-offset",
|
|
entry.contentRect.height + "px"
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
this._resizeObserver.observe(header);
|
|
}
|
|
},
|
|
|
|
willDestroyElement() {
|
|
this._super(...arguments);
|
|
|
|
this._resizeObserver?.disconnect();
|
|
this.appEvents.off("site-header:force-refresh", this, "queueRerender");
|
|
},
|
|
});
|
|
|
|
export function headerTop() {
|
|
const header = document.querySelector("header.d-header");
|
|
return header.offsetTop ? header.offsetTop : 0;
|
|
}
|