diff --git a/src/app/templates/client/Client.jsx b/src/app/templates/client/Client.jsx index e9be6b16eb..89c114a645 100644 --- a/src/app/templates/client/Client.jsx +++ b/src/app/templates/client/Client.jsx @@ -3,6 +3,7 @@ import './Client.scss'; import { initHotkeys } from '../../../client/event/hotkeys'; import { initRoomListListener } from '../../../client/event/roomList'; +import { handleUriFragmentChange, destructUrlHandling } from '../../../util/uriFragments'; import Text from '../../atoms/text/Text'; import Spinner from '../../atoms/spinner/Spinner'; @@ -80,8 +81,13 @@ function Client() { initHotkeys(); initRoomListListener(initMatrix.roomList); changeLoading(false); + handleUriFragmentChange(); }); initMatrix.init(); + + return () => { + destructUrlHandling(); + }; }, []); if (isLoading) { diff --git a/src/util/uriFragments.js b/src/util/uriFragments.js new file mode 100644 index 0000000000..51202c4e9f --- /dev/null +++ b/src/util/uriFragments.js @@ -0,0 +1,87 @@ +import { + selectRoom, selectSpace, openJoinAlias, +} from '../client/action/navigation'; + +import navigation from '../client/state/navigation'; +import cons from '../client/state/cons'; + +const listeners = []; + +/** + * Old `window.location.hash` value + * @type {string} + */ +let oldHash; + +export function handleUriFragmentChange() { + // If there is no fragment, there is no need to proceed here + if (!window.location.hash.startsWith('/#') && !window.location.hash.startsWith('#')) return; + + // Avoid falling into loops of self-triggering + // by detecting hashchange events which are not actually a change + if (window.location.hash === oldHash) { + return; + } + oldHash = window.location.hash; + + // Room must be selected AFTER client finished loading + const pieces = window.location.hash.split('/'); + // a[0] always is # + // if no trailing '/' would be used for hash we would have to remove it + // relevant array items start at index 1 + + // /join/ + if (pieces[1] === 'join') { + openJoinAlias(pieces[2]); + return; + } + + // /... + if (pieces.length >= 2) { + if (pieces[1] === 'room' || pieces[1][0] !== '!') pieces[1] = cons.tabs.HOME; + + selectSpace(pieces[1]); + } + + // /// + if (pieces.length >= 3) { + if (pieces[2][0] !== '!') selectRoom(null); + else if (pieces[3] && pieces[3][0] === '$') selectRoom(pieces[2], pieces[3]); + else selectRoom(pieces[2]); + } +} + +listeners.push( + window.addEventListener('hashchange', handleUriFragmentChange), + + navigation.on(cons.events.navigation.ROOM_SELECTED, (selectedRoom, _previousRoom, eventId) => { + const pieces = window.location.hash.split('/'); + + if (_previousRoom === selectedRoom) { + return; + } + + if (!eventId) pieces.length = 3; + else pieces[3] = eventId; + if (!selectedRoom) pieces.length = 2; + else pieces[2] = selectedRoom; + if (pieces[1] === 'join') pieces[1] = cons.tabs.HOME; + window.location.hash = pieces.join('/'); + }), + + navigation.on(cons.events.navigation.SPACE_SELECTED, (spaceSelected) => { + const pieces = window.location.hash.split('/'); + pieces[1] = spaceSelected ?? 'room'; + window.location.hash = pieces.join('/'); + }), + + navigation.on(cons.actions.navigation.OPEN_NAVIGATION, () => { + const pieces = window.location.hash.split('/'); + pieces.length = 2; + window.location.hash = pieces.join('/'); + }), +); + +export function destructUrlHandling() { + listeners.forEach((l) => navigation.removeListener(l)); +}