Change links in multi-column mode so tabs are open in single-column mode (#25893)
This commit is contained in:
		
							parent
							
								
									41f65edb21
								
							
						
					
					
						commit
						5fad7bd58a
					
				
							
								
								
									
										23
									
								
								app/javascript/mastodon/components/router.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/javascript/mastodon/components/router.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| import type { PropsWithChildren } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import type { History } from 'history'; | ||||
| import { createBrowserHistory } from 'history'; | ||||
| import { Router as OriginalRouter } from 'react-router'; | ||||
| 
 | ||||
| import { layoutFromWindow } from 'mastodon/is_mobile'; | ||||
| 
 | ||||
| const browserHistory = createBrowserHistory(); | ||||
| const originalPush = browserHistory.push.bind(browserHistory); | ||||
| 
 | ||||
| browserHistory.push = (path: string, state: History.LocationState) => { | ||||
|   if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { | ||||
|     originalPush(`/deck${path}`, state); | ||||
|   } else { | ||||
|     originalPush(path, state); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const Router: React.FC<PropsWithChildren> = ({ children }) => { | ||||
|   return <OriginalRouter history={browserHistory}>{children}</OriginalRouter>; | ||||
| }; | ||||
| @ -2,7 +2,7 @@ import PropTypes from 'prop-types'; | ||||
| import { PureComponent } from 'react'; | ||||
| 
 | ||||
| import { Helmet } from 'react-helmet'; | ||||
| import { BrowserRouter, Route } from 'react-router-dom'; | ||||
| import { Route } from 'react-router-dom'; | ||||
| 
 | ||||
| import { Provider as ReduxProvider } from 'react-redux'; | ||||
| 
 | ||||
| @ -12,6 +12,7 @@ import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis'; | ||||
| import { hydrateStore } from 'mastodon/actions/store'; | ||||
| import { connectUserStream } from 'mastodon/actions/streaming'; | ||||
| import ErrorBoundary from 'mastodon/components/error_boundary'; | ||||
| import { Router } from 'mastodon/components/router'; | ||||
| import UI from 'mastodon/features/ui'; | ||||
| import initialState, { title as siteTitle } from 'mastodon/initial_state'; | ||||
| import { IntlProvider } from 'mastodon/locales'; | ||||
| @ -75,11 +76,11 @@ export default class Mastodon extends PureComponent { | ||||
|       <IntlProvider> | ||||
|         <ReduxProvider store={store}> | ||||
|           <ErrorBoundary> | ||||
|             <BrowserRouter> | ||||
|             <Router> | ||||
|               <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> | ||||
|                 <Route path='/' component={UI} /> | ||||
|               </ScrollContext> | ||||
|             </BrowserRouter> | ||||
|             </Router> | ||||
| 
 | ||||
|             <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} /> | ||||
|           </ErrorBoundary> | ||||
|  | ||||
| @ -8,6 +8,7 @@ import { Link } from 'react-router-dom'; | ||||
| import { WordmarkLogo } from 'mastodon/components/logo'; | ||||
| import NavigationPortal from 'mastodon/components/navigation_portal'; | ||||
| import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; | ||||
| import { transientSingleColumn } from 'mastodon/is_mobile'; | ||||
| 
 | ||||
| import ColumnLink from './column_link'; | ||||
| import DisabledAccountBanner from './disabled_account_banner'; | ||||
| @ -29,6 +30,7 @@ const messages = defineMessages({ | ||||
|   followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' }, | ||||
|   about: { id: 'navigation_bar.about', defaultMessage: 'About' }, | ||||
|   search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, | ||||
|   advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' }, | ||||
| }); | ||||
| 
 | ||||
| class NavigationPanel extends Component { | ||||
| @ -54,6 +56,12 @@ class NavigationPanel extends Component { | ||||
|       <div className='navigation-panel'> | ||||
|         <div className='navigation-panel__logo'> | ||||
|           <Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link> | ||||
| 
 | ||||
|           {transientSingleColumn && ( | ||||
|             <a href={`/deck${location.pathname}`} className='button button--block'> | ||||
|               {intl.formatMessage(messages.advancedInterface)} | ||||
|             </a> | ||||
|           )} | ||||
|           <hr /> | ||||
|         </div> | ||||
| 
 | ||||
|  | ||||
| @ -126,11 +126,11 @@ class SwitchingColumnsArea extends PureComponent { | ||||
|   static propTypes = { | ||||
|     children: PropTypes.node, | ||||
|     location: PropTypes.object, | ||||
|     mobile: PropTypes.bool, | ||||
|     singleColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   UNSAFE_componentWillMount () { | ||||
|     if (this.props.mobile) { | ||||
|     if (this.props.singleColumn) { | ||||
|       document.body.classList.toggle('layout-single-column', true); | ||||
|       document.body.classList.toggle('layout-multiple-columns', false); | ||||
|     } else { | ||||
| @ -144,9 +144,9 @@ class SwitchingColumnsArea extends PureComponent { | ||||
|       this.node.handleChildrenContentChange(); | ||||
|     } | ||||
| 
 | ||||
|     if (prevProps.mobile !== this.props.mobile) { | ||||
|       document.body.classList.toggle('layout-single-column', this.props.mobile); | ||||
|       document.body.classList.toggle('layout-multiple-columns', !this.props.mobile); | ||||
|     if (prevProps.singleColumn !== this.props.singleColumn) { | ||||
|       document.body.classList.toggle('layout-single-column', this.props.singleColumn); | ||||
|       document.body.classList.toggle('layout-multiple-columns', !this.props.singleColumn); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -157,16 +157,17 @@ class SwitchingColumnsArea extends PureComponent { | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { children, mobile } = this.props; | ||||
|     const { children, singleColumn } = this.props; | ||||
|     const { signedIn } = this.context.identity; | ||||
|     const pathName = this.props.location.pathname; | ||||
| 
 | ||||
|     let redirect; | ||||
| 
 | ||||
|     if (signedIn) { | ||||
|       if (mobile) { | ||||
|       if (singleColumn) { | ||||
|         redirect = <Redirect from='/' to='/home' exact />; | ||||
|       } else { | ||||
|         redirect = <Redirect from='/' to='/getting-started' exact />; | ||||
|         redirect = <Redirect from='/' to='/deck/getting-started' exact />; | ||||
|       } | ||||
|     } else if (singleUserMode && owner && initialState?.accounts[owner]) { | ||||
|       redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />; | ||||
| @ -177,10 +178,13 @@ class SwitchingColumnsArea extends PureComponent { | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}> | ||||
|       <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}> | ||||
|         <WrappedSwitch> | ||||
|           {redirect} | ||||
| 
 | ||||
|           {singleColumn ? <Redirect from='/deck' to='/home' exact /> : null} | ||||
|           {singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null} | ||||
| 
 | ||||
|           <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> | ||||
|           <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> | ||||
|           <WrappedRoute path='/about' component={About} content={children} /> | ||||
| @ -573,7 +577,7 @@ class UI extends PureComponent { | ||||
|         <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}> | ||||
|           <Header /> | ||||
| 
 | ||||
|           <SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}> | ||||
|           <SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}> | ||||
|             {children} | ||||
|           </SwitchingColumnsArea> | ||||
| 
 | ||||
|  | ||||
| @ -11,13 +11,21 @@ import BundleContainer from '../containers/bundle_container'; | ||||
| 
 | ||||
| // Small wrapper to pass multiColumn to the route components | ||||
| export class WrappedSwitch extends PureComponent { | ||||
|   static contextTypes = { | ||||
|     router: PropTypes.object, | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { multiColumn, children } = this.props; | ||||
|     const { location } = this.context.router.route; | ||||
| 
 | ||||
|     const decklessLocation = multiColumn && location.pathname.startsWith('/deck') | ||||
|       ? {...location, pathname: location.pathname.slice(5)} | ||||
|       : location; | ||||
| 
 | ||||
|     return ( | ||||
|       <Switch> | ||||
|         {Children.map(children, child => cloneElement(child, { multiColumn }))} | ||||
|       <Switch location={decklessLocation}> | ||||
|         {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)} | ||||
|       </Switch> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -94,6 +94,13 @@ const element = document.getElementById('initial-state'); | ||||
| /** @type {InitialState | undefined} */ | ||||
| const initialState = element?.textContent && JSON.parse(element.textContent); | ||||
| 
 | ||||
| /** @type {string} */ | ||||
| const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? ''; | ||||
| /** @type {boolean} */ | ||||
| export const hasMultiColumnPath = initialPath === '/' | ||||
|   || initialPath === '/getting-started' | ||||
|   || initialPath.startsWith('/deck'); | ||||
| 
 | ||||
| /** | ||||
|  * @template {keyof InitialStateMeta} K | ||||
|  * @param {K} prop | ||||
|  | ||||
| @ -1,19 +1,21 @@ | ||||
| import { supportsPassiveEvents } from 'detect-passive-events'; | ||||
| 
 | ||||
| import { forceSingleColumn } from './initial_state'; | ||||
| import { forceSingleColumn, hasMultiColumnPath } from './initial_state'; | ||||
| 
 | ||||
| const LAYOUT_BREAKPOINT = 630; | ||||
| 
 | ||||
| export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT; | ||||
| 
 | ||||
| export const transientSingleColumn = !forceSingleColumn && !hasMultiColumnPath; | ||||
| 
 | ||||
| export type LayoutType = 'mobile' | 'single-column' | 'multi-column'; | ||||
| export const layoutFromWindow = (): LayoutType => { | ||||
|   if (isMobile(window.innerWidth)) { | ||||
|     return 'mobile'; | ||||
|   } else if (forceSingleColumn) { | ||||
|     return 'single-column'; | ||||
|   } else { | ||||
|   } else if (!forceSingleColumn && !transientSingleColumn) { | ||||
|     return 'multi-column'; | ||||
|   } else { | ||||
|     return 'single-column'; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -385,6 +385,7 @@ | ||||
|   "mute_modal.hide_notifications": "Hide notifications from this user?", | ||||
|   "mute_modal.indefinite": "Indefinite", | ||||
|   "navigation_bar.about": "About", | ||||
|   "navigation_bar.advanced_interface": "Open in advanced web interface", | ||||
|   "navigation_bar.blocks": "Blocked users", | ||||
|   "navigation_bar.bookmarks": "Bookmarks", | ||||
|   "navigation_bar.community_timeline": "Local timeline", | ||||
|  | ||||
| @ -368,6 +368,7 @@ | ||||
|   "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?", | ||||
|   "mute_modal.indefinite": "Indéfinie", | ||||
|   "navigation_bar.about": "À propos", | ||||
|   "navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée", | ||||
|   "navigation_bar.blocks": "Comptes bloqués", | ||||
|   "navigation_bar.bookmarks": "Marque-pages", | ||||
|   "navigation_bar.community_timeline": "Fil public local", | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
|     = preload_pack_asset 'features/compose.js', crossorigin: 'anonymous' | ||||
|     = preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous' | ||||
|     = preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous' | ||||
|     %meta{ name: 'initialPath', content: request.path } | ||||
| 
 | ||||
|   %meta{ name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key } | ||||
| 
 | ||||
|  | ||||
| @ -30,6 +30,7 @@ Rails.application.routes.draw do | ||||
|     /mutes | ||||
|     /followed_tags | ||||
|     /statuses/(*any) | ||||
|     /deck/(*any) | ||||
|   ).freeze | ||||
| 
 | ||||
|   root 'home#index' | ||||
|  | ||||
| @ -106,6 +106,7 @@ | ||||
|     "react-overlays": "^5.2.1", | ||||
|     "react-redux": "^8.0.4", | ||||
|     "react-redux-loading-bar": "^5.0.4", | ||||
|     "react-router": "^4.3.1", | ||||
|     "react-router-dom": "^4.1.1", | ||||
|     "react-router-scroll-4": "^1.0.0-beta.1", | ||||
|     "react-select": "^5.7.3", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user