/** * Patches anchor links, giving them additional or alternate functionality. * @module Anchor * @memberof Builtin.Enhancements.Components */ import { getModule, FluxDispatcher } from '@vizality/webpack'; import { Regexes, Protocols } from '@vizality/constants'; import { findInReactTree } from '@vizality/util/react'; import { patch, unpatch } from '@vizality/patcher'; export const labels = [ 'Components', 'Anchor' ]; export default builtin => { const Anchor = getModule(m => m.default?.displayName === 'Anchor'); const { anchor } = getModule('anchor', 'anchorUnderlineOnHover'); const vizalityRegex = new RegExp(`${Regexes.DISCORD}/vizality`, 'i'); const vizalityProtocolRegex = new RegExp(`^(${Protocols.BASE})`, 'i'); const userRegex = new RegExp(`${Regexes.DISCORD}/users/`, 'i'); const discordRegex = new RegExp(`${Regexes.DISCORD}`, 'i'); patch('vz-attributes-anchors', Anchor, 'default', (_, res) => { try { const props = findInReactTree(res, r => r.className?.includes(anchor) && r.href); if (!props) { return; } /** * */ if (props.href?.startsWith('#')) { props.onClick = evt => { try { evt.preventDefault(); /** * This is bad, but currently it's the only way I really know how to do it. * We're adding a timeout here so that we can give the new route page elements * (topOfElement and scroller) time to load into the DOM. */ return setTimeout(() => { const topOfElement = document.querySelector(`#${props.href.substring(1)}`)?.offsetTop; const scroller = document.querySelector('.vz-dashboard-scroller'); /** * If the elements don't exist at the check, just load the page normally as if * there was no hash. */ if (!topOfElement || !scroller) { return; } /** * Try to set the scroller position to the hash element. */ return scroller.scroll({ top: topOfElement - 80, behavior: 'smooth' }); }, 250); } catch (err) { return builtin.error(builtin._labels.concat(labels), err); } }; } /** * Force Vizality route links to open within the app. */ if (vizalityRegex.test(props.href)) { const route = props.href?.replace(discordRegex, ''); props.onClick = evt => { try { evt.preventDefault(); vizality.api.routes.navigateTo(route); } catch (err) { return builtin.error(builtin._labels.concat(labels), err); } }; } /** * Force Vizality protocol links to open within the app. */ if (vizalityProtocolRegex.test(props.href)) { const route = props.href?.replace(vizalityProtocolRegex, ''); props.onClick = evt => { try { evt.preventDefault(); vizality.api.routes.navigateTo(`/vizality/${route}`); } catch (err) { return builtin.error(builtin._labels.concat(labels), err); } }; } /* * Force user links to open in user profile modals within the app. */ if (userRegex.test(props.href)) { const userId = props.href?.replace(userRegex, ''); props.onClick = async evt => { try { evt.preventDefault(); if (!userId) { return; } const user = await $discord.users.fetchUser(userId); if (!user) { return vizality.api.notifications.sendToast({ id: 'VIZALITY_USER_NOT_FOUND', header: 'User Not Found', content: 'That user was unable to be located.', icon: 'PersonRemove' }); } return FluxDispatcher.dispatch({ type: 'USER_PROFILE_MODAL_OPEN', userId }); } catch (err) { return builtin.error(builtin._labels.concat(labels), err); } }; } } catch (err) { return builtin.error(builtin._labels.concat(labels), err); } }); return () => unpatch('vz-attributes-anchors'); };