client: detect isMobile on server

This commit is contained in:
fantasticit 2022-05-22 22:09:26 +08:00
parent 6941a5a226
commit db55765e69
15 changed files with 76 additions and 33 deletions

View File

@ -14,6 +14,7 @@ import { useDocumentDetail } from 'data/document';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event'; import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event';
import { useDocumentStyle } from 'hooks/use-document-style'; import { useDocumentStyle } from 'hooks/use-document-style';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useWindowSize } from 'hooks/use-window-size'; import { useWindowSize } from 'hooks/use-window-size';
import { SecureDocumentIllustration } from 'illustrations/secure-document'; import { SecureDocumentIllustration } from 'illustrations/secure-document';
import Router from 'next/router'; import Router from 'next/router';
@ -29,7 +30,8 @@ interface IProps {
} }
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => { export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
const { width: windowWith, isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const { width: windowWith } = useWindowSize();
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;

View File

@ -14,6 +14,7 @@ import { useDocumentDetail } from 'data/document';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
import { triggerJoinUser } from 'event'; import { triggerJoinUser } from 'event';
import { useDocumentStyle } from 'hooks/use-document-style'; import { useDocumentStyle } from 'hooks/use-document-style';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useWindowSize } from 'hooks/use-window-size'; import { useWindowSize } from 'hooks/use-window-size';
import Router from 'next/router'; import Router from 'next/router';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
@ -44,8 +45,9 @@ interface IProps {
} }
export const DocumentReader: React.FC<IProps> = ({ documentId }) => { export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
const { isMobile } = IsOnMobile.useHook();
const [container, setContainer] = useState<HTMLDivElement>(); const [container, setContainer] = useState<HTMLDivElement>();
const { width: windowWidth, isMobile } = useWindowSize(); const { width: windowWidth } = useWindowSize();
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;

View File

@ -1,4 +1,3 @@
import { IconArticle } from '@douyinfe/semi-icons';
import { import {
BackTop, BackTop,
Breadcrumb, Breadcrumb,
@ -6,7 +5,6 @@ import {
Form, Form,
Layout, Layout,
Nav, Nav,
Popover,
Skeleton, Skeleton,
Space, Space,
Typography, Typography,
@ -22,7 +20,7 @@ import { Theme } from 'components/theme';
import { User } from 'components/user'; import { User } from 'components/user';
import { usePublicDocument } from 'data/document'; import { usePublicDocument } from 'data/document';
import { useDocumentStyle } from 'hooks/use-document-style'; 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 Link from 'next/link';
import React, { useCallback, useMemo, useRef } from 'react'; import React, { useCallback, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
@ -44,7 +42,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
const $form = useRef<FormApi>(); const $form = useRef<FormApi>();
const { data, loading, error, query } = usePublicDocument(documentId); const { data, loading, error, query } = usePublicDocument(documentId);
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth; return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
}, [width]); }, [width]);

View File

@ -1,8 +1,8 @@
import { IconArticle } from '@douyinfe/semi-icons'; import { IconArticle } from '@douyinfe/semi-icons';
import { Button, Popover, Radio, RadioGroup, Slider, Typography } from '@douyinfe/semi-ui'; import { Button, Popover, Radio, RadioGroup, Slider, Typography } from '@douyinfe/semi-ui';
import { useDocumentStyle } from 'hooks/use-document-style'; import { useDocumentStyle } from 'hooks/use-document-style';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React from 'react'; import React from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -10,7 +10,7 @@ import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;
export const DocumentStyle = () => { export const DocumentStyle = () => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const { width, fontSize, setWidth, setFontSize } = useDocumentStyle(); const { width, fontSize, setWidth, setFontSize } = useDocumentStyle();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);

View File

@ -1,7 +1,7 @@
import { Popover, SideSheet, Typography } from '@douyinfe/semi-ui'; import { Popover, SideSheet, Typography } from '@douyinfe/semi-ui';
import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache'; import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ACTIVITIES, EXPRESSIONES, GESTURES, OBJECTS, SKY_WEATHER, SYMBOLS } from './constants'; import { ACTIVITIES, EXPRESSIONES, GESTURES, OBJECTS, SKY_WEATHER, SYMBOLS } from './constants';
@ -43,7 +43,7 @@ interface IProps {
} }
export const EmojiPicker: React.FC<IProps> = ({ onSelectEmoji, children }) => { export const EmojiPicker: React.FC<IProps> = ({ onSelectEmoji, children }) => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const [recentUsed, setRecentUsed] = useState([]); const [recentUsed, setRecentUsed] = useState([]);
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const renderedList = useMemo( const renderedList = useMemo(

View File

@ -4,8 +4,8 @@ import { Empty } from 'components/empty';
import { IconMessage } from 'components/icons/IconMessage'; import { IconMessage } from 'components/icons/IconMessage';
import { useAllMessages, useReadMessages, useUnreadMessages } from 'data/message'; import { useAllMessages, useReadMessages, useUnreadMessages } from 'data/message';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import { EmptyBoxIllustration } from 'illustrations/empty-box'; import { EmptyBoxIllustration } from 'illustrations/empty-box';
import Link from 'next/link'; import Link from 'next/link';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
@ -87,7 +87,7 @@ const MessagesRender = ({ messageData, loading, error, onClick = null, page = 1,
}; };
const MessageBox = () => { const MessageBox = () => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const { data: allMsgs, loading: allLoading, error: allError, page: allPage, setPage: allSetPage } = useAllMessages(); const { data: allMsgs, loading: allLoading, error: allError, page: allPage, setPage: allSetPage } = useAllMessages();
const { const {

View File

@ -2,9 +2,9 @@ import { IconChevronDown, IconPlus } from '@douyinfe/semi-icons';
import { Button, Dropdown } from '@douyinfe/semi-ui'; import { Button, Dropdown } from '@douyinfe/semi-ui';
import { DocumentCreator } from 'components/document/create'; import { DocumentCreator } from 'components/document/create';
import { WikiCreator } from 'components/wiki/create'; import { WikiCreator } from 'components/wiki/create';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useQuery } from 'hooks/use-query'; import { useQuery } from 'hooks/use-query';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React from 'react'; import React from 'react';
interface IProps { interface IProps {
@ -12,7 +12,7 @@ interface IProps {
} }
export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => { export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>(); const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false); const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);

View File

@ -1,9 +1,23 @@
export function isIOS() { const getUserAgent = (ua = null) => {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; if (!ua) {
return /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream; 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() { export function isAndroid(ua = null) {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; const userAgent = getUserAgent(ua);
return /android/i.test(userAgent); 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);
} }

View File

@ -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);

View File

@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
interface Size { interface Size {
width: number | undefined; width: number | undefined;
height: number | undefined; height: number | undefined;
isMobile: boolean;
} }
const PC_MOBILE_CRITICAL_WIDTH = 765; const PC_MOBILE_CRITICAL_WIDTH = 765;
@ -12,7 +11,6 @@ export function useWindowSize(): Size {
const [windowSize, setWindowSize] = useState<Size>({ const [windowSize, setWindowSize] = useState<Size>({
width: undefined, width: undefined,
height: undefined, height: undefined,
isMobile: false,
}); });
useEffect(() => { useEffect(() => {
@ -20,7 +18,6 @@ export function useWindowSize(): Size {
setWindowSize({ setWindowSize({
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,
isMobile: window.innerWidth <= PC_MOBILE_CRITICAL_WIDTH,
}); });
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);

View File

@ -6,6 +6,7 @@ import { Search } from 'components/search';
import { Theme } from 'components/theme'; import { Theme } from 'components/theme';
import { User } from 'components/user'; import { User } from 'components/user';
import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator'; import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size'; import { useWindowSize } from 'hooks/use-window-size';
import Router, { useRouter } from 'next/router'; import Router, { useRouter } from 'next/router';
@ -67,7 +68,8 @@ const menus = [
export const RouterHeader: React.FC = () => { export const RouterHeader: React.FC = () => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const { width, isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const { width } = useWindowSize();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false); const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false); const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false); const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);

View File

@ -3,12 +3,18 @@ import 'viewerjs/dist/viewer.css';
import 'styles/globals.scss'; import 'styles/globals.scss';
import 'tiptap/core/styles/index.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 { Theme } from 'hooks/use-theme';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; 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 ( return (
<> <>
<Head> <Head>
@ -40,10 +46,20 @@ function MyApp({ Component, pageProps }: AppProps) {
))} ))}
</Head> </Head>
<Theme.Provider> <Theme.Provider>
<Component {...pageProps} /> <IsOnMobile.Provider initialState={isMobile}>
<Component {...pageProps} />
</IsOnMobile.Provider>
</Theme.Provider> </Theme.Provider>
</> </>
); );
} }
MyApp.getInitialProps = async (appContext) => {
const request = appContext?.ctx?.req;
return {
isMobile: isMobile(request?.headers['user-agent']),
};
};
export default MyApp; export default MyApp;

View File

@ -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 { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styles from './style.module.scss'; import styles from './style.module.scss';
@ -85,7 +85,7 @@ export const ColorPicker: React.FC<{
onSetColor: (arg: string) => void; onSetColor: (arg: string) => void;
disabled?: boolean; disabled?: boolean;
}> = ({ children, title = '颜色管理', onSetColor, disabled = false }) => { }> = ({ children, title = '颜色管理', onSetColor, disabled = false }) => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const content = useMemo( const content = useMemo(

View File

@ -6,8 +6,8 @@ import { LogoName } from 'components/logo';
import { getRandomColor } from 'helpers/color'; import { getRandomColor } from 'helpers/color';
import { isAndroid, isIOS } from 'helpers/env'; import { isAndroid, isIOS } from 'helpers/env';
import { useNetwork } from 'hooks/use-network'; import { useNetwork } from 'hooks/use-network';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import { Collaboration } from 'tiptap/core/extensions/collaboration'; import { Collaboration } from 'tiptap/core/extensions/collaboration';
import { CollaborationCursor } from 'tiptap/core/extensions/collaboration-cursor'; 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 { hocuspocusProvider, editable, user, onTitleUpdate, status, menubar, renderInEditorPortal } = props;
const $headerContainer = useRef<HTMLDivElement>(); const $headerContainer = useRef<HTMLDivElement>();
const $mainContainer = useRef<HTMLDivElement>(); const $mainContainer = useRef<HTMLDivElement>();
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const { online } = useNetwork(); const { online } = useNetwork();
const [created, toggleCreated] = useToggle(false); const [created, toggleCreated] = useToggle(false);
const editor = useEditor( const editor = useEditor(

View File

@ -1,8 +1,8 @@
import { Button, Input, Popover, SideSheet, Space, Typography } from '@douyinfe/semi-ui'; import { Button, Input, Popover, SideSheet, Space, Typography } from '@douyinfe/semi-ui';
import { IconSearchReplace } from 'components/icons'; import { IconSearchReplace } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { SearchNReplace } from 'tiptap/core/extensions/search'; import { SearchNReplace } from 'tiptap/core/extensions/search';
import { Editor } from 'tiptap/editor'; import { Editor } from 'tiptap/editor';
@ -10,7 +10,7 @@ import { Editor } from 'tiptap/editor';
const { Text } = Typography; const { Text } = Typography;
export const Search: React.FC<{ editor: Editor }> = ({ editor }) => { export const Search: React.FC<{ editor: Editor }> = ({ editor }) => {
const { isMobile } = useWindowSize(); const { isMobile } = IsOnMobile.useHook();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const [currentIndex, setCurrentIndex] = useState(-1); const [currentIndex, setCurrentIndex] = useState(-1);
const [results, setResults] = useState([]); const [results, setResults] = useState([]);