router.tsx 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import type { PropsWithChildren } from 'react';
  2. import React from 'react';
  3. import { Router as OriginalRouter, useHistory } from 'react-router';
  4. import type {
  5. LocationDescriptor,
  6. LocationDescriptorObject,
  7. Path,
  8. } from 'history';
  9. import { createBrowserHistory } from 'history';
  10. import { layoutFromWindow } from 'mastodon/is_mobile';
  11. import { isDevelopment } from 'mastodon/utils/environment';
  12. interface MastodonLocationState {
  13. fromMastodon?: boolean;
  14. mastodonModalKey?: string;
  15. }
  16. type LocationState = MastodonLocationState | null | undefined;
  17. type HistoryPath = Path | LocationDescriptor<LocationState>;
  18. const browserHistory = createBrowserHistory<LocationState>();
  19. const originalPush = browserHistory.push.bind(browserHistory);
  20. const originalReplace = browserHistory.replace.bind(browserHistory);
  21. export function useAppHistory() {
  22. return useHistory<LocationState>();
  23. }
  24. function normalizePath(
  25. path: HistoryPath,
  26. state?: LocationState,
  27. ): LocationDescriptorObject<LocationState> {
  28. const location = typeof path === 'string' ? { pathname: path } : { ...path };
  29. if (location.state === undefined && state !== undefined) {
  30. location.state = state;
  31. } else if (
  32. location.state !== undefined &&
  33. state !== undefined &&
  34. isDevelopment()
  35. ) {
  36. // eslint-disable-next-line no-console
  37. console.log(
  38. 'You should avoid providing a 2nd state argument to push when the 1st argument is a location-like object that already has state; it is ignored',
  39. );
  40. }
  41. if (
  42. layoutFromWindow() === 'multi-column' &&
  43. !location.pathname?.startsWith('/deck')
  44. ) {
  45. location.pathname = `/deck${location.pathname}`;
  46. }
  47. return location;
  48. }
  49. browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => {
  50. const location = normalizePath(path, state);
  51. location.state = location.state ?? {};
  52. location.state.fromMastodon = true;
  53. originalPush(location);
  54. };
  55. browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => {
  56. const location = normalizePath(path, state);
  57. if (!location.pathname) return;
  58. if (browserHistory.location.state?.fromMastodon) {
  59. location.state = location.state ?? {};
  60. location.state.fromMastodon = true;
  61. }
  62. originalReplace(location);
  63. };
  64. export const Router: React.FC<PropsWithChildren> = ({ children }) => {
  65. return <OriginalRouter history={browserHistory}>{children}</OriginalRouter>;
  66. };