Skip to content

Commit feb8564

Browse files
committed
fix(mobile): implement native scrolling for note editor
1 parent 76931f7 commit feb8564

File tree

2 files changed

+59
-24
lines changed

2 files changed

+59
-24
lines changed

apps/mobile/v1/editor/src/components/Editor.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface EditorProps {
2525
editable?: boolean;
2626
theme?: 'light' | 'dark';
2727
colors?: EditorColors;
28+
scrollEnabled?: boolean;
2829
}
2930

3031
export interface EditorRef {
@@ -82,6 +83,8 @@ const createEditorHTML = (content: string, placeholder: string, isDark: boolean,
8283
8384
html {
8485
background: ${editorColors.background};
86+
-webkit-overflow-scrolling: touch;
87+
overflow-y: auto;
8588
}
8689
8790
body {
@@ -91,10 +94,9 @@ const createEditorHTML = (content: string, placeholder: string, isDark: boolean,
9194
color: ${editorColors.foreground};
9295
font-size: 16px;
9396
line-height: 1.6;
94-
min-height: 100vh;
97+
-webkit-overflow-scrolling: touch;
9598
}
9699
#editor {
97-
min-height: 100vh;
98100
padding-bottom: 40px;
99101
outline: none;
100102
}
@@ -293,13 +295,20 @@ const createEditorHTML = (content: string, placeholder: string, isDark: boolean,
293295
editor.innerHTML = ${JSON.stringify(content || '<p></p>')};
294296
isInitialized = true;
295297
298+
// Send height to React Native
299+
function sendHeight() {
300+
const height = document.documentElement.scrollHeight;
301+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'height', height }));
302+
}
303+
296304
// Send content changes to React Native (debounced)
297305
let timeout;
298306
function notifyChange() {
299307
clearTimeout(timeout);
300308
timeout = setTimeout(() => {
301309
const html = editor.innerHTML;
302310
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'content', html }));
311+
sendHeight();
303312
}, 300);
304313
}
305314
@@ -308,6 +317,7 @@ const createEditorHTML = (content: string, placeholder: string, isDark: boolean,
308317
clearTimeout(timeout);
309318
const html = editor.innerHTML;
310319
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'content', html }));
320+
sendHeight();
311321
});
312322
313323
// Track selection changes to detect active formats
@@ -1857,21 +1867,28 @@ const createEditorHTML = (content: string, placeholder: string, isDark: boolean,
18571867
};
18581868
18591869
// Auto-focus on load
1860-
setTimeout(() => editor.focus(), 100);
1870+
setTimeout(() => {
1871+
editor.focus();
1872+
sendHeight();
1873+
}, 100);
1874+
1875+
// Send height on window resize
1876+
window.addEventListener('resize', sendHeight);
18611877
</script>
18621878
</body>
18631879
</html>
18641880
`;
18651881
};
18661882

18671883
export const Editor = forwardRef<EditorRef, EditorProps>(
1868-
({ value, onChange, onFormatChange, placeholder = 'Start typing...', editable = true, theme = 'light', colors }, ref) => {
1884+
({ value, onChange, onFormatChange, placeholder = 'Start typing...', editable = true, theme = 'light', colors, scrollEnabled = true }, ref) => {
18691885
const webViewRef = useRef<WebView>(null);
18701886
const isDark = theme === 'dark';
18711887
const [initialContent] = useState(value); // Capture initial content only once
18721888
const lastValueRef = useRef(value); // Track last value to detect external changes
18731889
const lastThemeRef = useRef(isDark); // Track theme changes
18741890
const [themeKey, setThemeKey] = useState(isDark ? 'dark' : 'light');
1891+
const [webViewHeight, setWebViewHeight] = useState(300);
18751892

18761893
const execCommand = (command: string, value?: string | number) => {
18771894
webViewRef.current?.injectJavaScript(
@@ -1921,6 +1938,8 @@ export const Editor = forwardRef<EditorRef, EditorProps>(
19211938
onChange(data.html);
19221939
} else if (data.type === 'formats' && onFormatChange) {
19231940
onFormatChange(data.formats);
1941+
} else if (data.type === 'height') {
1942+
setWebViewHeight(data.height);
19241943
}
19251944
} catch (error) {
19261945
console.error('Error parsing WebView message:', error);
@@ -1968,8 +1987,14 @@ export const Editor = forwardRef<EditorRef, EditorProps>(
19681987
allowFileAccessFromFileURLs={true}
19691988
allowUniversalAccessFromFileURLs={true}
19701989
mixedContentMode="always"
1971-
style={[styles.webview, { backgroundColor: colors?.background || (isDark ? '#1a1a1a' : '#fff') }]}
1972-
scrollEnabled={true}
1990+
style={[
1991+
styles.webview,
1992+
{
1993+
backgroundColor: colors?.background || (isDark ? '#1a1a1a' : '#fff'),
1994+
height: scrollEnabled ? undefined : webViewHeight,
1995+
}
1996+
]}
1997+
scrollEnabled={scrollEnabled}
19731998
showsVerticalScrollIndicator={false}
19741999
keyboardDisplayRequiresUserAction={false}
19752000
hideKeyboardAccessoryView={true}

apps/mobile/v1/src/screens/EditNote/index.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
99
import { Editor, type EditorColors,type EditorRef } from '@/editor/src';
1010

1111
import { FileUpload } from '../../components/FileUpload';
12+
import { useKeyboardHeight } from '../../hooks/useKeyboardHeight';
1213
import { logger } from '../../lib/logger';
1314
import { type FileAttachment,type Note, useApiService } from '../../services/api';
1415
import { useTheme } from '../../theme';
@@ -32,7 +33,7 @@ export default function EditNoteScreen() {
3233
const [attachments, setAttachments] = useState<FileAttachment[]>([]);
3334
const [showAttachments, setShowAttachments] = useState(false);
3435
const [createdNoteId, setCreatedNoteId] = useState<string | null>(null);
35-
const [showHeader, setShowHeader] = useState(false);
36+
const [showHeader, setShowHeader] = useState(!isEditing);
3637
const [showToolbar, setShowToolbar] = useState(true);
3738
const [activeFormats, setActiveFormats] = useState({
3839
bold: false,
@@ -48,6 +49,7 @@ export default function EditNoteScreen() {
4849
});
4950

5051
const editorRef = useRef<EditorRef>(null);
52+
const keyboardHeight = useKeyboardHeight();
5153

5254
// Map theme colors to editor colors
5355
const editorColors: EditorColors = useMemo(() => ({
@@ -576,22 +578,31 @@ export default function EditNoteScreen() {
576578

577579
<View style={[styles.divider, { backgroundColor: theme.colors.border }]} />
578580

579-
<View style={[
580-
styles.richEditor,
581-
{
582-
backgroundColor: theme.colors.background,
583-
}
584-
]}>
585-
<Editor
586-
ref={editorRef}
587-
value={content}
588-
onChange={setContent}
589-
onFormatChange={setActiveFormats}
590-
placeholder="Write your note..."
591-
theme={theme.isDark ? 'dark' : 'light'}
592-
colors={editorColors}
593-
/>
594-
</View>
581+
<ScrollView
582+
style={{ flex: 1 }}
583+
contentContainerStyle={{ paddingBottom: keyboardHeight > 0 ? keyboardHeight : 0 }}
584+
showsVerticalScrollIndicator={false}
585+
keyboardShouldPersistTaps="handled"
586+
bounces={true}
587+
>
588+
<View style={[
589+
styles.richEditor,
590+
{
591+
backgroundColor: theme.colors.background,
592+
}
593+
]}>
594+
<Editor
595+
ref={editorRef}
596+
value={content}
597+
onChange={setContent}
598+
onFormatChange={setActiveFormats}
599+
placeholder="Write your note..."
600+
theme={theme.isDark ? 'dark' : 'light'}
601+
colors={editorColors}
602+
scrollEnabled={false}
603+
/>
604+
</View>
605+
</ScrollView>
595606
</SafeAreaView>
596607
);
597608
}
@@ -627,7 +638,6 @@ const styles = StyleSheet.create({
627638
marginHorizontal: 0,
628639
},
629640
richEditor: {
630-
flex: 1,
631641
},
632642
toolbarContainer: {
633643
position: 'absolute',

0 commit comments

Comments
 (0)