* Remove extraneous comment (see 9361d2 and 5c18f8) * Flexible, multiple reaction types * Fix reaction counts * Re-use svg for active state for now * Update yml, update spec * Possible temp fix for failing tests * Colorize reaction icon svgs * Reaction engagement above post title * Index reactions engagements (for logged-out) * Maybe readinglist is special * Try using crayons' dropdown as a drawer? * readinglist isn't really public now * feat: update the styles for the reaction drawer * Grey background highlight, turn off border/shadow * Read our feature flag docs, saw this was recommended * Missed fifth emoji: party/tada * Fix JS test errors * Update test with tada * Suppress flashing engagements when no public reactions * Liberate jump-to-comments from unicorn replacement * 'Add reaction' on tooltip * Don't show reaction emoji on index unless it's been used * rubocop * Update heart+ total count when toggling * Do not include 'readinglist' in drawer/public counts * Fix semi-public readinglist so that icon is badged for current user * Tweak heart-plus svg * Style tweak: border on active reaction * Show reacted icon on drawer trigger for 1.5 sec * Tweak styles for engagements bar * Style tweaks for multiple engagements (#index) * Trying to get size working through crayons/inline_svg * Sparkle hearts * Restore unicorn * Make heart-plus-active work when user has an active reaction * Try 'hoverdown' a dropdown that activates with hover * Long touch? * Tap *outside* the drawer to close * Mobile reaction drawer is also supposed to be columns * More reaction count cleanup * Final emoticons maybe? * Fix reaction bug when feature disabled * Remove readinglist from public reaction counts * Update specs for new reaction categories * Shuffle makes specs flaky * Order does not matter * rubocop * Update to preserve readinglist analytics * Shuffle makes specs flaky * Fix flickering images, remove icon highlight for now * Don't update total for readinglist * reactions_by_user_id * Try renaming this observer function * Try unid ids for SVGs * Remove local test file * Simplistic test for the unique svg transform * Fix javascript for current SVG * Signifcant string literals in this case, rubocop * Use the right expected output Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>
172 lines
5.5 KiB
JavaScript
172 lines
5.5 KiB
JavaScript
/* global isTouchDevice */
|
|
|
|
function closeHeaderMenu(memberMenu, menuNavButton) {
|
|
menuNavButton.setAttribute('aria-expanded', 'false');
|
|
memberMenu.classList.remove('desktop', 'showing');
|
|
delete memberMenu.dataset.clicked;
|
|
}
|
|
|
|
const firstItem = document.getElementById('first-nav-link');
|
|
|
|
function openHeaderMenu(memberMenu, menuNavButton) {
|
|
menuNavButton.setAttribute('aria-expanded', 'true');
|
|
memberMenu.classList.add('showing');
|
|
|
|
if (!firstItem) {
|
|
return;
|
|
}
|
|
|
|
// Focus on the first item in the menu
|
|
(function focusFirstItem() {
|
|
if (document.activeElement === firstItem) {
|
|
// The first element has focus
|
|
return;
|
|
}
|
|
|
|
firstItem.focus();
|
|
// requestAnimationFrame is faster and more reliable than setTimeout
|
|
// https://swizec.com/blog/how-to-wait-for-dom-elements-to-show-up-in-modern-browsers
|
|
window.requestAnimationFrame(focusFirstItem);
|
|
})();
|
|
}
|
|
|
|
/**
|
|
* Initializes the member navigation menu events.
|
|
*
|
|
* @param {HTMLElement} memberTopMenu The member menu in the top right navigation.
|
|
* @param {HTMLElement} menuNavButton The button to activate the member navigation menu.
|
|
*/
|
|
export function initializeMemberMenu(memberTopMenu, menuNavButton) {
|
|
// Typically using CSS for hovering for the menu is the way to go. But... since we use InstantClick for
|
|
// loading pages, the top header navigation never changes in terms of the DOM references.
|
|
// Because of this, we're using mouse events to mouseover/mouseout on the member's avatar
|
|
// to attach styles to get it to show the menu so that this works on desktop and mobile.
|
|
if (navigator.userAgent === 'DEV-Native-ios') {
|
|
document.body.classList.add('dev-ios-native-body');
|
|
}
|
|
const { classList } = memberTopMenu;
|
|
menuNavButton.addEventListener('click', (_event) => {
|
|
if (classList.contains('showing') && memberTopMenu.dataset.clicked) {
|
|
closeHeaderMenu(memberTopMenu, menuNavButton);
|
|
menuNavButton.focus();
|
|
} else {
|
|
openHeaderMenu(memberTopMenu, menuNavButton);
|
|
memberTopMenu.dataset.clicked = 'clicked';
|
|
}
|
|
});
|
|
|
|
if (isTouchDevice()) {
|
|
memberTopMenu.addEventListener('focus', (_event) => {
|
|
menuNavButton.setAttribute('aria-expanded', 'true');
|
|
});
|
|
} else {
|
|
memberTopMenu.addEventListener('keyup', (e) => {
|
|
if (e.key === 'Escape' && classList.contains('showing')) {
|
|
closeHeaderMenu(memberTopMenu, menuNavButton);
|
|
menuNavButton.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
memberTopMenu
|
|
.querySelector('.crayons-header__menu__dropdown')
|
|
.addEventListener('click', (event) => {
|
|
// There is a click event listener on the body and we do not want
|
|
// this click to be caught by it
|
|
event.stopPropagation();
|
|
|
|
// Close the menu if the user clicked or touched on mobile a link in the menu.
|
|
closeHeaderMenu(memberTopMenu, menuNavButton);
|
|
menuNavButton.focus();
|
|
});
|
|
|
|
document.addEventListener('click', (event) => {
|
|
if (event.target.closest('#member-menu-button') === menuNavButton) {
|
|
// The menu navigation button manages it's own click event.
|
|
return;
|
|
}
|
|
|
|
// Close the menu if the user clicked or touched on mobile a link in the menu.
|
|
closeHeaderMenu(memberTopMenu, menuNavButton);
|
|
});
|
|
|
|
const secondToLastNavLink = document.getElementById('second-last-nav-link');
|
|
|
|
document
|
|
.getElementById('last-nav-link')
|
|
.addEventListener('blur', (_event) => {
|
|
// When we tab out of the last link in the member menu, close
|
|
// the menu.
|
|
setTimeout(() => {
|
|
if (document.activeElement === secondToLastNavLink) {
|
|
return;
|
|
}
|
|
|
|
closeHeaderMenu(memberTopMenu, menuNavButton);
|
|
}, 10);
|
|
});
|
|
}
|
|
|
|
function toggleBurgerMenu() {
|
|
const { leftNavState = 'closed' } = document.body.dataset;
|
|
document.body.dataset.leftNavState =
|
|
leftNavState === 'open' ? 'closed' : 'open';
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to InstantClick
|
|
*
|
|
* @param {number} [waitTime=2000] The amount of time to wait
|
|
* until giving up waiting for InstantClick to exist
|
|
*
|
|
* @returns {Promise<object>} The global instance of InstantClick.
|
|
*/
|
|
export async function getInstantClick(waitTime = 2000) {
|
|
return new Promise((resolve, reject) => {
|
|
const failTimer = setTimeout(() => {
|
|
clearInterval(timer);
|
|
reject(new Error('Unable to resolve InstantClick'));
|
|
}, waitTime);
|
|
|
|
const timer = setInterval(() => {
|
|
if (typeof InstantClick !== 'undefined') {
|
|
clearTimeout(failTimer);
|
|
clearInterval(timer);
|
|
resolve(InstantClick);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initializes the hamburger menu for mobile navigation
|
|
*
|
|
* @param {HTMLElement[]} menuTriggers The menuTriggers include the hamburger to open the menu,
|
|
* the close icon to close the menu and the overlay to close the menu.
|
|
*/
|
|
export function initializeMobileMenu(menuTriggers) {
|
|
menuTriggers.forEach((trigger) => {
|
|
trigger.onclick = toggleBurgerMenu;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets the icon link visually for the current page if the current page
|
|
* is one of the main icon links of the top navigation.
|
|
*
|
|
* @param {string} currentPage
|
|
* @param {[string, HTMLElement][]} pageEntries
|
|
*/
|
|
export function setCurrentPageIconLink(currentPage, pageEntries) {
|
|
pageEntries
|
|
// Filter out nulls (means the user is logged out so most icons are not in the logged out view)
|
|
.filter(([, iconLink]) => iconLink)
|
|
.forEach(([page, iconLink]) => {
|
|
if (currentPage === page) {
|
|
iconLink.blur();
|
|
iconLink.setAttribute('aria-current', 'page');
|
|
} else {
|
|
iconLink.removeAttribute('aria-current');
|
|
}
|
|
});
|
|
}
|