Skip to content

Commit 458fd0c

Browse files
committed
fix(mobile): improve note editor UX and theme consistency
- Fix note editor starting position by adding key prop to force remount per note - Add CSS scroll-restoration and overflow-anchor controls to prevent auto-scrolling - Implement smooth fade-in transition to hide scroll positioning on load - Hide placeholder text to prevent flashing during scroll - Extend view note divider to full width for visual consistency with edit mode - Make attachment badge text color theme-aware for proper contrast in light/dark themes
1 parent c5cab13 commit 458fd0c

7 files changed

Lines changed: 96 additions & 17 deletions

File tree

apps/mobile/v1/app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"infoPlist": {
3535
"NSCameraUsageDescription": "This app uses the camera to capture photos for your notes.",
3636
"NSPhotoLibraryUsageDescription": "This app accesses your photo library to attach images to your notes.",
37-
"NSMicrophoneUsageDescription": "This app uses the microphone to record audio notes."
37+
"NSMicrophoneUsageDescription": "This app uses the microphone to record audio notes.",
38+
"ITSAppUsesNonExemptEncryption": false
3839
}
3940
},
4041
"android": {

apps/mobile/v1/src/hooks/useNoteEditor.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useMemo, useRef } from 'react';
1+
import { useCallback, useMemo, useRef, useEffect } from 'react';
22
import { Alert } from 'react-native';
33
import { useEditorBridge, TenTapStartKit, type EditorBridge } from '@10play/tentap-editor';
44
import { useRouter } from 'expo-router';
@@ -29,8 +29,8 @@ export function useNoteEditor(noteId?: string): UseNoteEditorReturn {
2929

3030
const editor = useEditorBridge({
3131
autofocus: false,
32-
avoidIosKeyboard: true,
33-
initialContent: '<p></p>',
32+
avoidIosKeyboard: false,
33+
initialContent: '',
3434
bridgeExtensions: TenTapStartKit,
3535
});
3636

@@ -39,6 +39,13 @@ export function useNoteEditor(noteId?: string): UseNoteEditorReturn {
3939
[theme.colors]
4040
);
4141

42+
// Reset refs when noteId changes
43+
useEffect(() => {
44+
editorReadyRef.current = false;
45+
pendingContentRef.current = null;
46+
contentSetRef.current = false;
47+
}, [noteId]);
48+
4249
const handleEditorLoad = useCallback(() => {
4350
editor.injectCSS(customCSS, 'theme-css');
4451

@@ -59,6 +66,35 @@ export function useNoteEditor(noteId?: string): UseNoteEditorReturn {
5966
if (!contentSetRef.current) {
6067
editor.setContent(pendingContentRef.current!);
6168
contentSetRef.current = true;
69+
70+
// Position content at top before showing
71+
setTimeout(() => {
72+
editor.webviewRef?.current?.injectJavaScript(`
73+
function scrollToTop() {
74+
window.scrollTo(0, 0);
75+
document.documentElement.scrollTop = 0;
76+
document.body.scrollTop = 0;
77+
78+
const prosemirror = document.querySelector('.ProseMirror');
79+
if (prosemirror) {
80+
prosemirror.scrollTop = 0;
81+
}
82+
}
83+
84+
// Scroll immediately
85+
scrollToTop();
86+
87+
// Show content after positioning with smooth fade
88+
requestAnimationFrame(() => {
89+
scrollToTop();
90+
requestAnimationFrame(() => {
91+
document.body.classList.add('ready');
92+
});
93+
});
94+
true;
95+
`);
96+
}, 50);
97+
6298
if (__DEV__) {
6399
console.log('[iOS Fix] Content set successfully');
64100
}
@@ -89,6 +125,35 @@ export function useNoteEditor(noteId?: string): UseNoteEditorReturn {
89125
}
90126
editor.setContent(content);
91127
contentSetRef.current = true;
128+
129+
// Position content at top before showing
130+
setTimeout(() => {
131+
editor.webviewRef?.current?.injectJavaScript(`
132+
function scrollToTop() {
133+
window.scrollTo(0, 0);
134+
document.documentElement.scrollTop = 0;
135+
document.body.scrollTop = 0;
136+
137+
const prosemirror = document.querySelector('.ProseMirror');
138+
if (prosemirror) {
139+
prosemirror.scrollTop = 0;
140+
}
141+
}
142+
143+
// Scroll immediately
144+
scrollToTop();
145+
146+
// Show content after positioning with smooth fade
147+
requestAnimationFrame(() => {
148+
scrollToTop();
149+
requestAnimationFrame(() => {
150+
document.body.classList.add('ready');
151+
});
152+
});
153+
true;
154+
`);
155+
}, 50);
156+
92157
if (__DEV__) {
93158
console.log('[iOS Fix] Content set successfully from loadNote');
94159
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function EditorHeader({
6363
</View>
6464
{attachmentsCount > 0 && (
6565
<View style={[styles.attachmentBadge, { backgroundColor: showAttachments ? "#3b82f6" : theme.colors.mutedForeground }]}>
66-
<Text style={styles.attachmentBadgeText}>
66+
<Text style={[styles.attachmentBadgeText, { color: showAttachments ? '#ffffff' : theme.colors.muted }]}>
6767
{attachmentsCount > 9 ? '9+' : attachmentsCount}
6868
</Text>
6969
</View>
@@ -149,7 +149,6 @@ const styles = StyleSheet.create({
149149
paddingHorizontal: 4,
150150
},
151151
attachmentBadgeText: {
152-
color: '#ffffff',
153152
fontSize: 10,
154153
fontWeight: '600',
155154
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,13 @@ export default function EditNoteScreen() {
223223
Platform.OS === 'android' && keyboardHeight > 0 && { marginBottom: keyboardHeight + 60 }
224224
]}>
225225
<RichText
226+
key={noteId as string || 'new-note'}
226227
editor={editor}
227228
style={{ flex: 1, backgroundColor: theme.colors.background }}
228229
onLoad={handleEditorLoad}
229230
webViewProps={{
230231
bounces: false,
231232
overScrollMode: 'never',
232-
contentInset: Platform.OS === 'ios' ? { bottom: toolbarHeight } : undefined,
233233
injectedJavaScript: Platform.OS === 'android' ? `
234234
(function() {
235235
function scrollToCursor() {

apps/mobile/v1/src/screens/EditNote/styles.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export function generateEditorStyles(themeColors: {
1414
overflow-x: hidden !important;
1515
width: 100vw !important;
1616
background-color: ${themeColors.background} !important;
17+
scroll-padding-top: 0 !important;
18+
overflow-anchor: none !important;
19+
scroll-restoration: manual !important;
1720
}
1821
*:not(input[type="checkbox"]):not(input[type="checkbox"]::after) {
1922
color: ${themeColors.foreground} !important;
@@ -32,22 +35,34 @@ export function generateEditorStyles(themeColors: {
3235
overscroll-behavior: contain !important;
3336
width: 100vw !important;
3437
max-width: 100vw !important;
38+
scroll-behavior: auto !important;
39+
overflow-anchor: none !important;
40+
opacity: 0 !important;
41+
transition: opacity 0.15s ease-in !important;
42+
}
43+
body.ready {
44+
opacity: 1 !important;
3545
}
3646
.ProseMirror {
3747
outline: none;
3848
overflow-x: hidden !important;
49+
overflow-anchor: none !important;
3950
width: calc(100vw - 24px) !important; /* 16px left + 8px right padding */
4051
max-width: calc(100vw - 24px) !important;
4152
line-height: 1.5 !important;
4253
padding: 0 !important;
4354
padding-right: 16px !important; /* Extra padding to keep text away from scrollbar */
44-
padding-bottom: 200px !important; /* Extra space at the end for comfortable editing */
4555
margin: 0 !important;
4656
}
47-
@media (min-width: 600px) {
48-
.ProseMirror {
49-
padding-bottom: 250px !important;
50-
}
57+
/* Hide placeholder text */
58+
.ProseMirror p.is-editor-empty:first-child::before,
59+
.ProseMirror p.is-empty:first-child::before {
60+
display: none !important;
61+
}
62+
.ProseMirror::after {
63+
content: '';
64+
display: block;
65+
height: 80px;
5166
}
5267
.ProseMirror > *:not(ul[data-type="taskList"]):not(ul[data-type="taskList"] *) {
5368
max-width: 100% !important;
@@ -63,7 +78,7 @@ export function generateEditorStyles(themeColors: {
6378
color: ${themeColors.foreground} !important;
6479
}
6580
.ProseMirror > p:first-child {
66-
margin-top: 12px !important;
81+
margin-top: 8px !important;
6782
}
6883
li[data-type="taskItem"] > div > p:first-child {
6984
margin-top: 0 !important;
@@ -74,7 +89,7 @@ export function generateEditorStyles(themeColors: {
7489
color: ${themeColors.foreground} !important;
7590
}
7691
h1:first-child, h2:first-child, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
77-
margin-top: 12px;
92+
margin-top: 8px;
7893
}
7994
h1 { font-size: 2em; }
8095
h2 { font-size: 1.5em; }

apps/mobile/v1/src/screens/ViewNote/ViewHeader.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function ViewHeader({
9090
</View>
9191
{attachmentsCount > 0 && (
9292
<View style={[styles.attachmentBadge, { backgroundColor: showAttachments ? "#3b82f6" : theme.colors.mutedForeground }]}>
93-
<Text style={styles.attachmentBadgeText}>
93+
<Text style={[styles.attachmentBadgeText, { color: showAttachments ? '#ffffff' : theme.colors.card }]}>
9494
{attachmentsCount > 9 ? '9+' : attachmentsCount}
9595
</Text>
9696
</View>
@@ -200,7 +200,6 @@ const styles = StyleSheet.create({
200200
paddingHorizontal: 4,
201201
},
202202
attachmentBadgeText: {
203-
color: '#ffffff',
204203
fontSize: 10,
205204
fontWeight: '600',
206205
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ const styles = StyleSheet.create({
252252
},
253253
divider: {
254254
height: StyleSheet.hairlineWidth,
255-
marginHorizontal: 16,
255+
marginHorizontal: 0,
256256
},
257257
attachmentsContainer: {
258258
borderBottomWidth: StyleSheet.hairlineWidth,

0 commit comments

Comments
 (0)