Skip to content

Commit af2b98e

Browse files
committed
refactor(mobile): centralize caret positioning and smart calc to set caret in right position
- Rename BigPencilBtn to EditFAB following Material Design naming conventions - Create useCaretPosition hook for centralized caret management: - getViewportBounds() - calculates safe viewport area accounting for keyboard - getTargetCaretPos() - determines optimal caret position - ensureCaretVisible() - handles iOS keyboard animation delays - useEditorFocusScroll() - auto-scroll caret on any editor focus - Create useEnableEditor hook for editor enable/focus operations - Merge scrollCaretIntoViewPlugin into useCaretPosition hook - Remove DOM manipulation hacks (dataset.doubleTap, programmatic clicks) - Decouple EditorContent from EditFAB - each handles its own behavior
1 parent ad35ce9 commit af2b98e

File tree

8 files changed

+360
-172
lines changed

8 files changed

+360
-172
lines changed

packages/webapp/src/components/TipTap/TipTap.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table
6565
import { Indent } from '@docs.plus/extension-indent'
6666

6767
import ChatCommentExtension from './extentions/ChatCommentExtension'
68-
import { createScrollCaretIntoViewPlugin } from './plugins/scrollCaretIntoViewPlugin'
6968
import { IOSCaretFix } from './plugins/iosCaretFixPlugin'
7069

7170
import { InlineCode } from '@docs.plus/extension-inline-code'
@@ -223,8 +222,6 @@ const Editor = ({
223222
showOnlyWhenEditable: false,
224223
placeholder: (data: any) => generatePlaceholderText(data) || ''
225224
}),
226-
// iOS Safari: Scroll caret into view when keyboard opens
227-
...(isMobile ? [createScrollCaretIntoViewPlugin()] : []),
228225
// iOS Safari: Fix caret positioning when tapping in middle of words
229226
IOSCaretFix
230227
]

packages/webapp/src/components/TipTap/plugins/scrollCaretIntoViewPlugin.ts

Lines changed: 0 additions & 109 deletions
This file was deleted.

packages/webapp/src/components/pages/document/components/BigPencilBtn.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { useCallback } from 'react'
2+
import { Pencil } from '@icons'
3+
import { useCaretPosition, useEnableEditor } from '@hooks/useCaretPosition'
4+
5+
/**
6+
* Floating Action Button for entering edit mode.
7+
* Places caret at optimal viewport position and opens keyboard.
8+
*/
9+
const EditFAB = () => {
10+
const { isKeyboardOpen, getTargetCaretPos } = useCaretPosition()
11+
const { enableAndFocusAt } = useEnableEditor()
12+
13+
const handleClick = useCallback(() => {
14+
const targetPos = getTargetCaretPos()
15+
enableAndFocusAt(targetPos)
16+
}, [getTargetCaretPos, enableAndFocusAt])
17+
18+
if (isKeyboardOpen) return null
19+
20+
return (
21+
<button
22+
onClick={handleClick}
23+
className="edit-fab btn btn-circle border-docsy bg-docsy active fixed right-6 bottom-8 z-20 size-16 text-white">
24+
<Pencil size={28} />
25+
</button>
26+
)
27+
}
28+
29+
export default EditFAB

packages/webapp/src/components/pages/document/components/EditorContent.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useStore } from '@stores'
55
import { twMerge } from 'tailwind-merge'
66
import { useRef, useCallback } from 'react'
77
import useDoubleTap from '@hooks/useDoubleTap'
8+
import { useEnableEditor, useEditorFocusScroll } from '@hooks/useCaretPosition'
89

910
const RenderLoader = ({ className }: { className?: string }) => {
1011
return (
@@ -20,19 +21,20 @@ const EditorContent = ({ className }: { className?: string }) => {
2021
const {
2122
editor: { instance: editor, loading, providerSyncing, applyingFilters }
2223
} = useStore((state) => state.settings)
23-
const { isKeyboardOpen } = useStore((state) => state)
2424
const editorElement = useRef<HTMLDivElement>(null)
25-
26-
// Focus handler
27-
const handleFocus = useCallback(() => {
28-
if (!editor) return
29-
const btnBigBluePencil = document.querySelector('.btn_bigBluePencil') as HTMLButtonElement
30-
31-
if (btnBigBluePencil && !isKeyboardOpen) btnBigBluePencil.click()
32-
}, [editor, isKeyboardOpen])
33-
34-
// Double tap handler
35-
const handleDoubleTap = useDoubleTap(handleFocus)
25+
const { enableAndFocus, isKeyboardOpen } = useEnableEditor()
26+
27+
// Auto-scroll caret into view on any focus (direct tap, etc.)
28+
useEditorFocusScroll()
29+
30+
// Double-tap: enable editor and focus (caret already at tap position via iOS fix)
31+
const handleDoubleTap = useDoubleTap(
32+
useCallback(() => {
33+
if (!isKeyboardOpen) {
34+
enableAndFocus()
35+
}
36+
}, [isKeyboardOpen, enableAndFocus])
37+
)
3638

3739
if (loading || providerSyncing || !editor) {
3840
return <RenderLoader className={className} />

packages/webapp/src/components/pages/document/layouts/MobileLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import MobilePadTitle from '@components/TipTap/pad-title-section/MobilePadTitle'
33
import MobileEditor from '../components/MobileEditor'
44
import { useStore } from '@stores'
55
import TocModal from '@components/pages/document/components/TocModal'
6-
import BigPencilBtn from '@components/pages/document/components/BigPencilBtn'
6+
import EditFAB from '@components/pages/document/components/EditFAB'
77
import { ModalDrawer } from '@components/ui/ModalDrawer'
88
import { useHashRouter } from '@hooks/useHashRouter'
99
import MobileHistory from '@components/pages/history/mobile/MobileHistory'
@@ -41,7 +41,7 @@ const MobileLayout = () => {
4141
</div>
4242
<MobileLeftSidePanel filterModalRef={filterModalRef} />
4343
<MobileEditor />
44-
<BigPencilBtn />
44+
<EditFAB />
4545
<BottomSheet />
4646
<div className="mobileToolbarBottom sticky bottom-0 left-0 z-20 w-full bg-white">
4747
<ToolbarMobile />

0 commit comments

Comments
 (0)