From db55765e696789d207974abc5ee4d45390b7f790 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 22 May 2022 22:09:26 +0800 Subject: [PATCH] client: detect isMobile on server --- .../src/components/document/editor/index.tsx | 4 ++- .../src/components/document/reader/index.tsx | 4 ++- .../document/reader/public/index.tsx | 6 ++--- .../src/components/document/style/index.tsx | 4 +-- .../src/components/emoji-picker/index.tsx | 4 +-- .../client/src/components/message/index.tsx | 4 +-- .../wiki-or-document-creator/index.tsx | 4 +-- packages/client/src/helpers/env.ts | 26 ++++++++++++++----- packages/client/src/hooks/use-on-mobile.tsx | 12 +++++++++ packages/client/src/hooks/use-window-size.tsx | 3 --- .../src/layouts/router-header/index.tsx | 4 ++- packages/client/src/pages/_app.tsx | 20 ++++++++++++-- .../tiptap/components/color-picker/index.tsx | 6 ++--- .../collaboration/collaboration/editor.tsx | 4 +-- .../src/tiptap/editor/menus/search/index.tsx | 4 +-- 15 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 packages/client/src/hooks/use-on-mobile.tsx diff --git a/packages/client/src/components/document/editor/index.tsx b/packages/client/src/components/document/editor/index.tsx index 56881e31..8e6c0795 100644 --- a/packages/client/src/components/document/editor/index.tsx +++ b/packages/client/src/components/document/editor/index.tsx @@ -14,6 +14,7 @@ import { useDocumentDetail } from 'data/document'; import { useUser } from 'data/user'; import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event'; import { useDocumentStyle } from 'hooks/use-document-style'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useWindowSize } from 'hooks/use-window-size'; import { SecureDocumentIllustration } from 'illustrations/secure-document'; import Router from 'next/router'; @@ -29,7 +30,8 @@ interface IProps { } export const DocumentEditor: React.FC = ({ documentId }) => { - const { width: windowWith, isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); + const { width: windowWith } = useWindowSize(); const { width, fontSize } = useDocumentStyle(); const editorWrapClassNames = useMemo(() => { return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; diff --git a/packages/client/src/components/document/reader/index.tsx b/packages/client/src/components/document/reader/index.tsx index 0af7f510..f97cddcc 100644 --- a/packages/client/src/components/document/reader/index.tsx +++ b/packages/client/src/components/document/reader/index.tsx @@ -14,6 +14,7 @@ import { useDocumentDetail } from 'data/document'; import { useUser } from 'data/user'; import { triggerJoinUser } from 'event'; import { useDocumentStyle } from 'hooks/use-document-style'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useWindowSize } from 'hooks/use-window-size'; import Router from 'next/router'; import React, { useCallback, useMemo, useState } from 'react'; @@ -44,8 +45,9 @@ interface IProps { } export const DocumentReader: React.FC = ({ documentId }) => { + const { isMobile } = IsOnMobile.useHook(); const [container, setContainer] = useState(); - const { width: windowWidth, isMobile } = useWindowSize(); + const { width: windowWidth } = useWindowSize(); const { width, fontSize } = useDocumentStyle(); const editorWrapClassNames = useMemo(() => { return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; diff --git a/packages/client/src/components/document/reader/public/index.tsx b/packages/client/src/components/document/reader/public/index.tsx index 4438ea74..f71c4561 100644 --- a/packages/client/src/components/document/reader/public/index.tsx +++ b/packages/client/src/components/document/reader/public/index.tsx @@ -1,4 +1,3 @@ -import { IconArticle } from '@douyinfe/semi-icons'; import { BackTop, Breadcrumb, @@ -6,7 +5,6 @@ import { Form, Layout, Nav, - Popover, Skeleton, Space, Typography, @@ -22,7 +20,7 @@ import { Theme } from 'components/theme'; import { User } from 'components/user'; import { usePublicDocument } from 'data/document'; import { useDocumentStyle } from 'hooks/use-document-style'; -import { useWindowSize } from 'hooks/use-window-size'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import Link from 'next/link'; import React, { useCallback, useMemo, useRef } from 'react'; import { createPortal } from 'react-dom'; @@ -44,7 +42,7 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = const $form = useRef(); const { data, loading, error, query } = usePublicDocument(documentId); const { width, fontSize } = useDocumentStyle(); - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const editorWrapClassNames = useMemo(() => { return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; }, [width]); diff --git a/packages/client/src/components/document/style/index.tsx b/packages/client/src/components/document/style/index.tsx index c3a638cc..117b2ee4 100644 --- a/packages/client/src/components/document/style/index.tsx +++ b/packages/client/src/components/document/style/index.tsx @@ -1,8 +1,8 @@ import { IconArticle } from '@douyinfe/semi-icons'; import { Button, Popover, Radio, RadioGroup, Slider, Typography } from '@douyinfe/semi-ui'; import { useDocumentStyle } from 'hooks/use-document-style'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React from 'react'; import styles from './index.module.scss'; @@ -10,7 +10,7 @@ import styles from './index.module.scss'; const { Text } = Typography; export const DocumentStyle = () => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const { width, fontSize, setWidth, setFontSize } = useDocumentStyle(); const [visible, toggleVisible] = useToggle(false); diff --git a/packages/client/src/components/emoji-picker/index.tsx b/packages/client/src/components/emoji-picker/index.tsx index b00e327b..2b0dd034 100644 --- a/packages/client/src/components/emoji-picker/index.tsx +++ b/packages/client/src/components/emoji-picker/index.tsx @@ -1,7 +1,7 @@ import { Popover, SideSheet, Typography } from '@douyinfe/semi-ui'; import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ACTIVITIES, EXPRESSIONES, GESTURES, OBJECTS, SKY_WEATHER, SYMBOLS } from './constants'; @@ -43,7 +43,7 @@ interface IProps { } export const EmojiPicker: React.FC = ({ onSelectEmoji, children }) => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const [recentUsed, setRecentUsed] = useState([]); const [visible, toggleVisible] = useToggle(false); const renderedList = useMemo( diff --git a/packages/client/src/components/message/index.tsx b/packages/client/src/components/message/index.tsx index 36b52b7f..d7fe5c98 100644 --- a/packages/client/src/components/message/index.tsx +++ b/packages/client/src/components/message/index.tsx @@ -4,8 +4,8 @@ import { Empty } from 'components/empty'; import { IconMessage } from 'components/icons/IconMessage'; import { useAllMessages, useReadMessages, useUnreadMessages } from 'data/message'; import { useUser } from 'data/user'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import { EmptyBoxIllustration } from 'illustrations/empty-box'; import Link from 'next/link'; import React, { useCallback, useEffect } from 'react'; @@ -87,7 +87,7 @@ const MessagesRender = ({ messageData, loading, error, onClick = null, page = 1, }; const MessageBox = () => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const [visible, toggleVisible] = useToggle(false); const { data: allMsgs, loading: allLoading, error: allError, page: allPage, setPage: allSetPage } = useAllMessages(); const { diff --git a/packages/client/src/components/wiki-or-document-creator/index.tsx b/packages/client/src/components/wiki-or-document-creator/index.tsx index 1b399122..e26caf3c 100644 --- a/packages/client/src/components/wiki-or-document-creator/index.tsx +++ b/packages/client/src/components/wiki-or-document-creator/index.tsx @@ -2,9 +2,9 @@ import { IconChevronDown, IconPlus } from '@douyinfe/semi-icons'; import { Button, Dropdown } from '@douyinfe/semi-ui'; import { DocumentCreator } from 'components/document/create'; import { WikiCreator } from 'components/wiki/create'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useQuery } from 'hooks/use-query'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React from 'react'; interface IProps { @@ -12,7 +12,7 @@ interface IProps { } export const WikiOrDocumentCreator: React.FC = ({ onCreateDocument, children }) => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>(); const [dropdownVisible, toggleDropdownVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false); diff --git a/packages/client/src/helpers/env.ts b/packages/client/src/helpers/env.ts index 658fb7d2..99f1a34b 100644 --- a/packages/client/src/helpers/env.ts +++ b/packages/client/src/helpers/env.ts @@ -1,9 +1,23 @@ -export function isIOS() { - const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; - return /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream; +const getUserAgent = (ua = null) => { + if (!ua) { + if (typeof window !== 'undefined') { + ua = navigator.userAgent || navigator.vendor || (window as any).opera; + } + } + return ua; +}; + +export function isIOS(ua = null) { + const userAgent = getUserAgent(ua); + return userAgent && /iPad|iPhone|iPod/.test(userAgent); } -export function isAndroid() { - const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; - return /android/i.test(userAgent); +export function isAndroid(ua = null) { + const userAgent = getUserAgent(ua); + return userAgent && /Android/i.test(userAgent); +} + +export function isMobile(ua = null) { + const userAgent = getUserAgent(ua); + return userAgent && /(iPhone|iPod|iPad|Android|BlackBerry)/i.test(userAgent); } diff --git a/packages/client/src/hooks/use-on-mobile.tsx b/packages/client/src/hooks/use-on-mobile.tsx new file mode 100644 index 00000000..04d20195 --- /dev/null +++ b/packages/client/src/hooks/use-on-mobile.tsx @@ -0,0 +1,12 @@ +import { createGlobalHook } from './create-global-hook'; +import { useToggle } from './use-toggle'; + +const useOnMobile = (defaultIsMobile) => { + const [isMobile, toggleIsMobile] = useToggle(defaultIsMobile); + return { + isMobile, + toggleIsMobile, + }; +}; + +export const IsOnMobile = createGlobalHook<{ isMobile?: boolean; toggle: () => void }, boolean>(useOnMobile); diff --git a/packages/client/src/hooks/use-window-size.tsx b/packages/client/src/hooks/use-window-size.tsx index 05eb971a..316e5977 100644 --- a/packages/client/src/hooks/use-window-size.tsx +++ b/packages/client/src/hooks/use-window-size.tsx @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'; interface Size { width: number | undefined; height: number | undefined; - isMobile: boolean; } const PC_MOBILE_CRITICAL_WIDTH = 765; @@ -12,7 +11,6 @@ export function useWindowSize(): Size { const [windowSize, setWindowSize] = useState({ width: undefined, height: undefined, - isMobile: false, }); useEffect(() => { @@ -20,7 +18,6 @@ export function useWindowSize(): Size { setWindowSize({ width: window.innerWidth, height: window.innerHeight, - isMobile: window.innerWidth <= PC_MOBILE_CRITICAL_WIDTH, }); } window.addEventListener('resize', handleResize); diff --git a/packages/client/src/layouts/router-header/index.tsx b/packages/client/src/layouts/router-header/index.tsx index a333f115..c960faaa 100644 --- a/packages/client/src/layouts/router-header/index.tsx +++ b/packages/client/src/layouts/router-header/index.tsx @@ -6,6 +6,7 @@ import { Search } from 'components/search'; import { Theme } from 'components/theme'; import { User } from 'components/user'; import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; import { useWindowSize } from 'hooks/use-window-size'; import Router, { useRouter } from 'next/router'; @@ -67,7 +68,8 @@ const menus = [ export const RouterHeader: React.FC = () => { const { pathname } = useRouter(); - const { width, isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); + const { width } = useWindowSize(); const [dropdownVisible, toggleDropdownVisible] = useToggle(false); const [recentModalVisible, toggleRecentModalVisible] = useToggle(false); const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false); diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx index cd361159..2e5c1f4f 100644 --- a/packages/client/src/pages/_app.tsx +++ b/packages/client/src/pages/_app.tsx @@ -3,12 +3,18 @@ import 'viewerjs/dist/viewer.css'; import 'styles/globals.scss'; import 'tiptap/core/styles/index.scss'; +import { isMobile } from 'helpers/env'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { Theme } from 'hooks/use-theme'; import type { AppProps } from 'next/app'; import Head from 'next/head'; import React from 'react'; -function MyApp({ Component, pageProps }: AppProps) { +type P = AppProps<{ isMobile?: boolean }>; + +function MyApp(props: AppProps & { isMobile?: boolean }) { + const { Component, pageProps, isMobile } = props; + return ( <> @@ -40,10 +46,20 @@ function MyApp({ Component, pageProps }: AppProps) { ))} - + + + ); } +MyApp.getInitialProps = async (appContext) => { + const request = appContext?.ctx?.req; + + return { + isMobile: isMobile(request?.headers['user-agent']), + }; +}; + export default MyApp; diff --git a/packages/client/src/tiptap/components/color-picker/index.tsx b/packages/client/src/tiptap/components/color-picker/index.tsx index 2b475cb1..8bcf22e0 100644 --- a/packages/client/src/tiptap/components/color-picker/index.tsx +++ b/packages/client/src/tiptap/components/color-picker/index.tsx @@ -1,6 +1,6 @@ -import { Col, Dropdown, Modal, Row, SideSheet, Typography } from '@douyinfe/semi-ui'; +import { Dropdown, SideSheet, Typography } from '@douyinfe/semi-ui'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React, { useMemo } from 'react'; import styles from './style.module.scss'; @@ -85,7 +85,7 @@ export const ColorPicker: React.FC<{ onSetColor: (arg: string) => void; disabled?: boolean; }> = ({ children, title = '颜色管理', onSetColor, disabled = false }) => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const [visible, toggleVisible] = useToggle(false); const content = useMemo( diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx index 43edd522..48fa0bcf 100644 --- a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx +++ b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx @@ -6,8 +6,8 @@ import { LogoName } from 'components/logo'; import { getRandomColor } from 'helpers/color'; import { isAndroid, isIOS } from 'helpers/env'; import { useNetwork } from 'hooks/use-network'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { Collaboration } from 'tiptap/core/extensions/collaboration'; import { CollaborationCursor } from 'tiptap/core/extensions/collaboration-cursor'; @@ -30,7 +30,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => { const { hocuspocusProvider, editable, user, onTitleUpdate, status, menubar, renderInEditorPortal } = props; const $headerContainer = useRef(); const $mainContainer = useRef(); - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const { online } = useNetwork(); const [created, toggleCreated] = useToggle(false); const editor = useEditor( diff --git a/packages/client/src/tiptap/editor/menus/search/index.tsx b/packages/client/src/tiptap/editor/menus/search/index.tsx index dd725cf8..b4516475 100644 --- a/packages/client/src/tiptap/editor/menus/search/index.tsx +++ b/packages/client/src/tiptap/editor/menus/search/index.tsx @@ -1,8 +1,8 @@ import { Button, Input, Popover, SideSheet, Space, Typography } from '@douyinfe/semi-ui'; import { IconSearchReplace } from 'components/icons'; import { Tooltip } from 'components/tooltip'; +import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import React, { useCallback, useEffect, useState } from 'react'; import { SearchNReplace } from 'tiptap/core/extensions/search'; import { Editor } from 'tiptap/editor'; @@ -10,7 +10,7 @@ import { Editor } from 'tiptap/editor'; const { Text } = Typography; export const Search: React.FC<{ editor: Editor }> = ({ editor }) => { - const { isMobile } = useWindowSize(); + const { isMobile } = IsOnMobile.useHook(); const [visible, toggleVisible] = useToggle(false); const [currentIndex, setCurrentIndex] = useState(-1); const [results, setResults] = useState([]);