From 12379571cb56ae9c8dcff8e8c91cde58af468d11 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Wed, 4 May 2022 17:20:06 +0800 Subject: [PATCH] feat: imporve editor mobile ux --- packages/client/src/helpers/debounce.tsx | 7 +- packages/client/src/helpers/env.ts | 9 +++ packages/client/src/helpers/lru-cache.ts | 20 ++--- packages/client/src/services/http-client.tsx | 2 +- .../core/extensions/scroll-into-view.ts | 33 ++++++++ .../tiptap/core/wrappers/emoji-list/index.tsx | 4 +- .../core/wrappers/mention-list/index.tsx | 4 +- .../core/wrappers/menu-list/index.module.scss | 5 -- .../tiptap/core/wrappers/menu-list/index.tsx | 4 +- .../collaboration/collaboration/editor.tsx | 78 ++++++++++++++++++- .../collaboration/index.module.scss | 4 + .../collaboration/collaboration/index.tsx | 2 +- .../src/tiptap/editor/collaboration/kit.ts | 3 +- .../client/src/tiptap/editor/comment/kit.ts | 2 + .../tiptap/editor/menus/countdown/service.ts | 2 +- 15 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 packages/client/src/helpers/env.ts create mode 100644 packages/client/src/tiptap/core/extensions/scroll-into-view.ts diff --git a/packages/client/src/helpers/debounce.tsx b/packages/client/src/helpers/debounce.tsx index 1f60927..213db2c 100644 --- a/packages/client/src/helpers/debounce.tsx +++ b/packages/client/src/helpers/debounce.tsx @@ -1,9 +1,12 @@ -export function debounce(func, timeout = 300) { +export function debounce(func, timeout = 200) { let timer; return (...args) => { + if (!timer) { + func.apply(this, args); + } clearTimeout(timer); timer = setTimeout(() => { - func.apply(this, args); + timer = undefined; }, timeout); }; } diff --git a/packages/client/src/helpers/env.ts b/packages/client/src/helpers/env.ts new file mode 100644 index 0000000..658fb7d --- /dev/null +++ b/packages/client/src/helpers/env.ts @@ -0,0 +1,9 @@ +export function isIOS() { + const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; + return /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream; +} + +export function isAndroid() { + const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; + return /android/i.test(userAgent); +} diff --git a/packages/client/src/helpers/lru-cache.ts b/packages/client/src/helpers/lru-cache.ts index 29394e6..f1d50a2 100644 --- a/packages/client/src/helpers/lru-cache.ts +++ b/packages/client/src/helpers/lru-cache.ts @@ -114,24 +114,16 @@ export class LRUCache { } } -const USED_STORAGE_KEYS = []; +const CacheMap = new Map(); export const createKeysLocalStorageLRUCache = (storageKey, capacity) => { const lruCache = new LRUCache(capacity); - if (USED_STORAGE_KEYS.includes(storageKey)) { - // @ts-ignore - if (module.hot) { - console.error(`Storage Key ${storageKey} has been used!`); - return; - } else { - throw new Error(`Storage Key ${storageKey} has been used!`); - } + if (CacheMap.has(storageKey)) { + return CacheMap.get(setStorage); } - USED_STORAGE_KEYS.push(storageKey); - - return { + const manager = { syncFromStorage() { const data = getStorage(storageKey) || []; data @@ -152,4 +144,8 @@ export const createKeysLocalStorageLRUCache = (storageKey, capacity) => { return key ? lruCache.get(key) : lruCache.keys(); }, }; + + CacheMap.set(storageKey, manager); + + return manager; }; diff --git a/packages/client/src/services/http-client.tsx b/packages/client/src/services/http-client.tsx index 6b4c799..eb63f2e 100644 --- a/packages/client/src/services/http-client.tsx +++ b/packages/client/src/services/http-client.tsx @@ -3,7 +3,7 @@ import { Toast } from '@douyinfe/semi-ui'; import Router from 'next/router'; export const HttpClient = axios.create({ - baseURL: process.env.SERVER_API_URL, + baseURL: 'http://192.168.31.124:5001/api' || process.env.SERVER_API_URL, timeout: 60000, }); diff --git a/packages/client/src/tiptap/core/extensions/scroll-into-view.ts b/packages/client/src/tiptap/core/extensions/scroll-into-view.ts new file mode 100644 index 0000000..a1ce650 --- /dev/null +++ b/packages/client/src/tiptap/core/extensions/scroll-into-view.ts @@ -0,0 +1,33 @@ +import { Extension } from '@tiptap/core'; +import { Plugin, PluginKey, Transaction } from 'prosemirror-state'; +import { debounce } from 'helpers/debounce'; + +export const scrollIntoViewPluginKey = new PluginKey('scrollIntoViewPlugin'); + +type TransactionWithScroll = Transaction & { scrolledIntoView: boolean }; + +export const ScrollIntoView = Extension.create({ + name: 'scrollIntoView', + addProseMirrorPlugins() { + const { editor } = this; + return [ + new Plugin({ + key: scrollIntoViewPluginKey, + appendTransaction: debounce((transactions, oldState, newState) => { + if (!transactions.length || !editor.isEditable) { + return; + } + const tr = transactions[0] as TransactionWithScroll; + if ( + (tr.docChanged || tr.storedMarksSet) && + !tr.scrolledIntoView && + tr.getMeta('scrollIntoView') !== false && + tr.getMeta('addToHistory') !== false + ) { + return newState.tr.scrollIntoView(); + } + }, 100), + }), + ]; + }, +}); diff --git a/packages/client/src/tiptap/core/wrappers/emoji-list/index.tsx b/packages/client/src/tiptap/core/wrappers/emoji-list/index.tsx index 052f179..ebd7a6d 100644 --- a/packages/client/src/tiptap/core/wrappers/emoji-list/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/emoji-list/index.tsx @@ -66,13 +66,13 @@ export const EmojiList: React.FC = forwardRef((props, ref) => {
{props.items.length ? ( props.items.map((item, index) => ( - + )) ) : (
没有找到结果
diff --git a/packages/client/src/tiptap/core/wrappers/mention-list/index.tsx b/packages/client/src/tiptap/core/wrappers/mention-list/index.tsx index eb3109f..4dadbd7 100644 --- a/packages/client/src/tiptap/core/wrappers/mention-list/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/mention-list/index.tsx @@ -66,13 +66,13 @@ export const MentionList: React.FC = forwardRef((props, ref) => {
{props.items.length ? ( props.items.map((item, index) => ( - + )) ) : (
没有找到结果
diff --git a/packages/client/src/tiptap/core/wrappers/menu-list/index.module.scss b/packages/client/src/tiptap/core/wrappers/menu-list/index.module.scss index 53648ea..88c3fd8 100644 --- a/packages/client/src/tiptap/core/wrappers/menu-list/index.module.scss +++ b/packages/client/src/tiptap/core/wrappers/menu-list/index.module.scss @@ -5,10 +5,6 @@ overflow-y: auto; border-radius: var(--border-radius); box-shadow: rgb(9 30 66 / 31%) 0 0 1px, rgb(9 30 66 / 25%) 0 4px 8px -2px; - - &::-webkit-scrollbar { - background-color: #fff !important; - } } .item { @@ -21,7 +17,6 @@ background-color: rgb(255 255 255); border: 0; border-radius: 0; - outline: none; flex: 0 0 auto; align-items: center; diff --git a/packages/client/src/tiptap/core/wrappers/menu-list/index.tsx b/packages/client/src/tiptap/core/wrappers/menu-list/index.tsx index 905b825..52a256f 100644 --- a/packages/client/src/tiptap/core/wrappers/menu-list/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/menu-list/index.tsx @@ -72,13 +72,13 @@ export const MenuList: React.FC = forwardRef((props, ref) => {
{props.items.length ? ( props.items.map((item, index) => ( - + )) ) : (
没有找到结果
diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx index 40206ff..b6ee7f4 100644 --- a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx +++ b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx @@ -2,7 +2,6 @@ import React, { useEffect, forwardRef, useImperativeHandle, useRef, useMemo } fr import { Toast, BackTop } from '@douyinfe/semi-ui'; import { HocuspocusProvider } from '@hocuspocus/provider'; import cls from 'classnames'; -import { debounce } from 'helpers/debounce'; import { useNetwork } from 'hooks/use-network'; import { useToggle } from 'hooks/use-toggle'; import { useWindowSize } from 'hooks/use-window-size'; @@ -11,6 +10,7 @@ import { Banner } from 'components/banner'; import { Collaboration } from 'tiptap/core/extensions/collaboration'; import { CollaborationCursor } from 'tiptap/core/extensions/collaboration-cursor'; import { getRandomColor } from 'helpers/color'; +import { isAndroid, isIOS } from 'helpers/env'; import { useEditor, EditorContent } from '../../react'; import { CollaborationKit } from '../kit'; import { MenuBar } from './menubar'; @@ -25,8 +25,30 @@ type IProps = Pick< status: ProviderStatus; }; +function scrollEditor(editor) { + try { + /** + * 修复移动端编辑问题 + */ + setTimeout(() => { + try { + const element = editor.options.element; + // 脏代码:这里使用 parentElement 是和布局有关的,需要根据实际情况修改 + const parentElement = element.parentNode as HTMLElement; + const nextScrollTop = element.scrollHeight; + parentElement.scrollTop = nextScrollTop; + } catch (e) { + // + } + }, 0); + } catch (e) { + // + } +} + 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 { online } = useNetwork(); @@ -49,16 +71,24 @@ export const EditorInstance = forwardRef((props: IProps, ref) => { }, }), ].filter(Boolean), - onTransaction: debounce(({ transaction }) => { + onTransaction({ transaction }) { try { const title = transaction.doc.content.firstChild.content.firstChild.textContent; onTitleUpdate(title); } catch (e) { // } - }, 50), + + if (editable) { + scrollEditor(this); + } + }, onCreate() { toggleCreated(true); + + if (editable) { + scrollEditor(this); + } }, onDestroy() {}, }, @@ -89,6 +119,46 @@ export const EditorInstance = forwardRef((props: IProps, ref) => { }; }, []); + // 监听键盘收起、打开 + useEffect(() => { + let cleanUp = () => {}; + const focusIn = () => { + setTimeout(() => { + if (!$headerContainer.current) return; + $headerContainer.current.classList.add(styles.keyUp); + $headerContainer.current.scrollIntoView(); + }, 200); + }; + const focusOut = () => { + if (!$headerContainer.current) return; + $headerContainer.current.classList.remove(styles.iOSKeyUp); + }; + + if (isIOS()) { + document.body.addEventListener('focusin', focusIn); + document.body.addEventListener('focusout', focusOut); + cleanUp = () => { + document.body.removeEventListener('focusin', focusIn); + document.body.removeEventListener('focusout', focusOut); + }; + } else if (isAndroid) { + const originalHeight = document.documentElement.clientHeight || document.body.clientHeight; + window.onresize = function () { + //键盘弹起与隐藏都会引起窗口的高度发生变化 + const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight; + if (resizeHeight < originalHeight) { + focusIn(); + } else { + focusOut(); + } + }; + } + + return () => { + cleanUp(); + }; + }, []); + return ( <> {(!online || status === 'disconnected') && ( @@ -102,7 +172,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => { )} {menubar && ( -
+
)} diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss b/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss index 2139856..7fa735a 100644 --- a/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss +++ b/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss @@ -34,6 +34,10 @@ justify-content: space-around; align-items: center; border-top: 1px solid var(--semi-color-border); + + &.keyUp { + position: absolute; + } } } diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/index.tsx b/packages/client/src/tiptap/editor/collaboration/collaboration/index.tsx index 71463f1..6140184 100644 --- a/packages/client/src/tiptap/editor/collaboration/collaboration/index.tsx +++ b/packages/client/src/tiptap/editor/collaboration/collaboration/index.tsx @@ -34,7 +34,7 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps, const hocuspocusProvider = useMemo(() => { return new HocuspocusProvider({ - url: process.env.COLLABORATION_API_URL, + url: 'ws://192.168.31.124:5003' || process.env.COLLABORATION_API_URL, name: documentId, token: (user && user.token) || 'read-public', parameters: { diff --git a/packages/client/src/tiptap/editor/collaboration/kit.ts b/packages/client/src/tiptap/editor/collaboration/kit.ts index c601e9d..2346d51 100644 --- a/packages/client/src/tiptap/editor/collaboration/kit.ts +++ b/packages/client/src/tiptap/editor/collaboration/kit.ts @@ -27,6 +27,7 @@ import { OrderedList } from 'tiptap/core/extensions/ordered-list'; import { Paragraph } from 'tiptap/core/extensions/paragraph'; import { Placeholder } from 'tiptap/core/extensions/placeholder'; import { SelectionExtension } from 'tiptap/core/extensions/selection'; +import { ScrollIntoView } from 'tiptap/core/extensions/scroll-into-view'; import { Strike } from 'tiptap/core/extensions/strike'; import { Subscript } from 'tiptap/core/extensions/subscript'; import { Superscript } from 'tiptap/core/extensions/superscript'; @@ -106,6 +107,7 @@ export const CollaborationKit = [ Loading, OrderedList, SelectionExtension, + ScrollIntoView, Strike, Subscript, Superscript, @@ -139,6 +141,5 @@ export const CollaborationKit = [ SearchNReplace, Status, Title, - // Document, DocumentWithTitle, ]; diff --git a/packages/client/src/tiptap/editor/comment/kit.ts b/packages/client/src/tiptap/editor/comment/kit.ts index e978818..4886b66 100644 --- a/packages/client/src/tiptap/editor/comment/kit.ts +++ b/packages/client/src/tiptap/editor/comment/kit.ts @@ -28,6 +28,7 @@ import { OrderedList } from 'tiptap/core/extensions/ordered-list'; import { Paragraph } from 'tiptap/core/extensions/paragraph'; import { Placeholder } from 'tiptap/core/extensions/placeholder'; import { Strike } from 'tiptap/core/extensions/strike'; +import { ScrollIntoView } from 'tiptap/core/extensions/scroll-into-view'; import { Subscript } from 'tiptap/core/extensions/subscript'; import { Superscript } from 'tiptap/core/extensions/superscript'; import { Table } from 'tiptap/core/extensions/table'; @@ -87,6 +88,7 @@ export const CommentKit = [ showOnlyWhenEditable: true, }), Strike, + ScrollIntoView, Subscript, Superscript, Table, diff --git a/packages/client/src/tiptap/editor/menus/countdown/service.ts b/packages/client/src/tiptap/editor/menus/countdown/service.ts index d001ff3..e02f404 100644 --- a/packages/client/src/tiptap/editor/menus/countdown/service.ts +++ b/packages/client/src/tiptap/editor/menus/countdown/service.ts @@ -6,5 +6,5 @@ import { triggerOpenCountSettingModal } from '../_event'; * @param editor */ export const createCountdown = (editor: Editor) => { - triggerOpenCountSettingModal(null); + triggerOpenCountSettingModal(editor, null); };