Skip to content

Commit a41f216

Browse files
committed
fix(mobile): improve checkbox alignment and remove notification badges
- Fix checkbox text alignment in note editor and viewer - Use flexbox with align-items: center for proper vertical alignment - Apply display: contents to labels to remove wrapper interference - Hide empty Tiptap spans that caused misalignment - Remove p tag margins inside task items (key fix) - Standardize to 16x16px checkboxes with 8px text spacing
1 parent 26f3404 commit a41f216

File tree

4 files changed

+192
-45
lines changed

4 files changed

+192
-45
lines changed

apps/mobile/v1/app/folder-notes.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
1010
import { Input } from '@/src/components/ui/Input';
1111
import { BottomSheetModal, BottomSheetView, BottomSheetBackdrop, BottomSheetScrollView, BottomSheetTextInput } from '@gorhom/bottom-sheet';
1212
import { FOLDER_COLORS } from '@/src/constants/ui';
13-
import { useVersionNotification } from '@/src/hooks/useVersionNotification';
1413

1514
function getViewTitle(viewType: string): string {
1615
switch (viewType) {
@@ -27,7 +26,6 @@ export default function FolderNotesScreen() {
2726
const router = useRouter();
2827
const theme = useTheme();
2928
const api = useApiService();
30-
const { hasNewVersion } = useVersionNotification();
3129
const [breadcrumbs, setBreadcrumbs] = useState<string[]>([]);
3230
const [breadcrumbFolders, setBreadcrumbFolders] = useState<Folder[]>([]);
3331
const [allFolders, setAllFolders] = useState<Folder[]>([]);
@@ -327,9 +325,6 @@ export default function FolderNotesScreen() {
327325
onPress={() => setShowSettingsDropdown(!showSettingsDropdown)}
328326
>
329327
<Ionicons name="settings-outline" size={20} color={theme.colors.mutedForeground} />
330-
{hasNewVersion && (
331-
<View style={styles.notificationBadge} />
332-
)}
333328
</TouchableOpacity>
334329

335330
{showSettingsDropdown && (
@@ -764,13 +759,4 @@ const styles = StyleSheet.create({
764759
headerDivider: {
765760
height: StyleSheet.hairlineWidth,
766761
},
767-
notificationBadge: {
768-
position: 'absolute',
769-
top: 4,
770-
right: 4,
771-
width: 8,
772-
height: 8,
773-
borderRadius: 4,
774-
backgroundColor: '#ef4444',
775-
},
776762
});

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

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,62 @@ export default function EditNoteScreen() {
8181
const formats = items.map(item => typeof item === 'string' ? item : item.type);
8282
setActiveFormats(formats);
8383
});
84+
85+
// Inject custom JavaScript to handle Enter key in checkbox lists using command method
86+
setTimeout(() => {
87+
richTextRef.current?.command(`
88+
(function() {
89+
if (window.checkboxEnterHandlerAdded) return;
90+
window.checkboxEnterHandlerAdded = true;
91+
92+
document.addEventListener('keydown', function(e) {
93+
if (e.key === 'Enter' && !e.shiftKey) {
94+
const selection = window.getSelection();
95+
if (!selection.rangeCount) return;
96+
97+
const range = selection.getRangeAt(0);
98+
let node = range.startContainer;
99+
100+
// Find parent list item
101+
let listItem = node;
102+
while (listItem && listItem.nodeName !== 'LI') {
103+
listItem = listItem.parentElement;
104+
if (!listItem || listItem === document.body) break;
105+
}
106+
107+
if (listItem && listItem.nodeName === 'LI') {
108+
// Check if this list item contains a checkbox
109+
const checkbox = listItem.querySelector('input[type="checkbox"]');
110+
if (checkbox) {
111+
e.preventDefault();
112+
113+
// Create new list item with checkbox
114+
const newLi = document.createElement('li');
115+
const newCheckbox = document.createElement('input');
116+
newCheckbox.type = 'checkbox';
117+
newCheckbox.style.cssText = 'width: 16px !important; height: 16px !important; margin: 0 8px 0 0 !important; flex-shrink: 0 !important;';
118+
119+
const textNode = document.createTextNode('\\u200B'); // Zero-width space
120+
121+
newLi.appendChild(newCheckbox);
122+
newLi.appendChild(textNode);
123+
124+
// Insert new list item after current one
125+
listItem.parentNode.insertBefore(newLi, listItem.nextSibling);
126+
127+
// Move cursor to new list item
128+
const newRange = document.createRange();
129+
newRange.setStart(textNode, 1);
130+
newRange.collapse(true);
131+
selection.removeAllRanges();
132+
selection.addRange(newRange);
133+
}
134+
}
135+
}
136+
}, true);
137+
})();
138+
`);
139+
}, 500);
84140
}
85141
}, [editorReady]);
86142

@@ -96,13 +152,80 @@ export default function EditNoteScreen() {
96152
}
97153
};
98154

155+
const transformCheckboxHtml = (html: string): string => {
156+
// Transform plain checkbox lists to Tiptap-compatible format using regex
157+
let transformed = html;
158+
159+
// First, identify and transform UL elements containing checkboxes
160+
// Match UL tags that contain checkbox inputs
161+
transformed = transformed.replace(
162+
/<ul([^>]*)>([\s\S]*?)<\/ul>/gi,
163+
(match, ulAttrs, ulContent) => {
164+
// Check if this UL contains checkboxes
165+
if (ulContent.includes('type="checkbox"') || ulContent.includes("type='checkbox'")) {
166+
// Add data-type="taskList" if not already present
167+
const hasDataType = /data-type/i.test(ulAttrs);
168+
const newUlAttrs = hasDataType ? ulAttrs : `${ulAttrs} data-type="taskList"`;
169+
170+
// Transform each LI that contains a checkbox
171+
const transformedContent = ulContent.replace(
172+
/<li([^>]*)>([\s\S]*?)<\/li>/gi,
173+
(liMatch, liAttrs, liContent) => {
174+
// Check if this LI contains a checkbox
175+
const checkboxMatch = liContent.match(/<input([^>]*type=["']checkbox["'][^>]*)>/i);
176+
if (checkboxMatch) {
177+
// Add data-type="taskItem" to LI
178+
const hasLiDataType = /data-type/i.test(liAttrs);
179+
const newLiAttrs = hasLiDataType ? liAttrs : `${liAttrs} data-type="taskItem"`;
180+
181+
// Extract checkbox and remaining content
182+
const checkbox = checkboxMatch[0];
183+
let remainingContent = liContent.replace(checkbox, '').trim();
184+
185+
// Remove non-breaking spaces that might be at the start
186+
remainingContent = remainingContent.replace(/^(&nbsp;|\u00A0)+/, '');
187+
188+
// Wrap checkbox in label if not already
189+
const wrappedCheckbox = checkbox.includes('<label>')
190+
? checkbox
191+
: `<label>${checkbox}</label>`;
192+
193+
// Wrap remaining content in div if it's not empty and not already wrapped
194+
let wrappedContent = '';
195+
if (remainingContent) {
196+
// Check if content is already wrapped in a div or p
197+
if (!/^<(div|p)[\s>]/i.test(remainingContent)) {
198+
wrappedContent = `<div>${remainingContent}</div>`;
199+
} else {
200+
wrappedContent = remainingContent;
201+
}
202+
}
203+
204+
return `<li${newLiAttrs}>${wrappedCheckbox}${wrappedContent}</li>`;
205+
}
206+
return liMatch;
207+
}
208+
);
209+
210+
return `<ul${newUlAttrs}>${transformedContent}</ul>`;
211+
}
212+
return match;
213+
}
214+
);
215+
216+
return transformed;
217+
};
218+
99219
const handleSave = async (options?: { skipNavigation?: boolean }) => {
100220
const titleToUse = title.trim() || 'Untitled';
101221

102222
setIsSaving(true);
103223

104224
try {
105-
const htmlContent = await richTextRef.current?.getContentHtml() || '';
225+
let htmlContent = await richTextRef.current?.getContentHtml() || '';
226+
227+
// Transform checkbox lists to Tiptap format
228+
htmlContent = transformCheckboxHtml(htmlContent);
106229

107230
let savedNote: Note;
108231

@@ -333,6 +456,56 @@ export default function EditNoteScreen() {
333456
margin: 4px 0;
334457
}
335458
459+
/* Task list checkbox styling - unified for both mobile and web */
460+
ul[data-type="taskList"],
461+
ul:has(> li > input[type="checkbox"]) {
462+
list-style: none !important;
463+
padding-left: 0 !important;
464+
margin: 8px 0 !important;
465+
}
466+
467+
li[data-type="taskItem"],
468+
li:has(> input[type="checkbox"]),
469+
li:has(> label > input[type="checkbox"]) {
470+
display: flex !important;
471+
align-items: center !important;
472+
margin: 4px 0 !important;
473+
list-style: none !important;
474+
}
475+
476+
input[type="checkbox"] {
477+
width: 16px !important;
478+
height: 16px !important;
479+
min-width: 16px !important;
480+
min-height: 16px !important;
481+
margin: 0 8px 0 0 !important;
482+
flex-shrink: 0 !important;
483+
cursor: pointer !important;
484+
}
485+
486+
li[data-type="taskItem"] label,
487+
li label:has(> input[type="checkbox"]) {
488+
display: contents !important;
489+
}
490+
491+
/* Hide the empty span that Tiptap adds */
492+
li[data-type="taskItem"] label > span {
493+
display: none !important;
494+
}
495+
496+
li[data-type="taskItem"] > div,
497+
li[data-type="taskItem"] > p {
498+
flex: 1 !important;
499+
line-height: 1.6 !important;
500+
margin: 0 !important;
501+
}
502+
503+
/* Remove p tag margins inside task items */
504+
li[data-type="taskItem"] p {
505+
margin: 0 !important;
506+
line-height: 1.6 !important;
507+
}
508+
336509
pre {
337510
background-color: ${theme.isDark ? 'rgba(255, 255, 255, 0.05)' : theme.colors.muted} !important;
338511
color: ${theme.colors.foreground} !important;

apps/mobile/v1/src/screens/SettingsScreen.tsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { BottomSheetModal, BottomSheetView, BottomSheetBackdrop, BottomSheetScro
1313
import AsyncStorage from '@react-native-async-storage/async-storage';
1414
import { APP_VERSION } from '../constants/version';
1515
import { UsageBottomSheet } from '../components/settings/UsageBottomSheet';
16-
import { useVersionNotification } from '../hooks/useVersionNotification';
1716

1817
// Type for valid Ionicons names
1918
type IconName = keyof typeof Ionicons.glyphMap;
@@ -36,7 +35,6 @@ export default function SettingsScreen({ onLogout }: Props) {
3635
const theme = useTheme();
3736
const { user } = useUser();
3837
const router = useRouter();
39-
const { hasNewVersion, markVersionAsSeen } = useVersionNotification();
4038

4139
// Bottom sheet refs
4240
const themeModeSheetRef = useRef<BottomSheetModal>(null);
@@ -243,10 +241,7 @@ export default function SettingsScreen({ onLogout }: Props) {
243241
title: "What's New",
244242
subtitle: 'See latest updates and changes',
245243
icon: 'newspaper-outline',
246-
onPress: async () => {
247-
await markVersionAsSeen();
248-
Linking.openURL('https://github.com/typelets/typelets-app/blob/main/CHANGELOG.md');
249-
},
244+
onPress: () => Linking.openURL('https://github.com/typelets/typelets-app/blob/main/CHANGELOG.md'),
250245
},
251246
{
252247
title: 'Support',
@@ -330,9 +325,6 @@ export default function SettingsScreen({ onLogout }: Props) {
330325
size={20}
331326
color={theme.colors.foreground}
332327
/>
333-
{item.title === "What's New" && hasNewVersion && (
334-
<View style={styles.notificationBadge} />
335-
)}
336328
</View>
337329
<View style={styles.settingItemText}>
338330
<Text style={[
@@ -1044,13 +1036,4 @@ const styles = StyleSheet.create({
10441036
marginBottom: 12,
10451037
marginTop: 8,
10461038
},
1047-
notificationBadge: {
1048-
position: 'absolute',
1049-
top: 2,
1050-
right: 2,
1051-
width: 8,
1052-
height: 8,
1053-
borderRadius: 4,
1054-
backgroundColor: '#ef4444',
1055-
},
10561039
});

apps/mobile/v1/src/screens/ViewNote/htmlGenerator.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,37 +68,42 @@ export function generateNoteHtml(
6868
li {
6969
margin: 4px 0;
7070
}
71-
/* Task list styling */
71+
/* Task list styling - unified and simple */
7272
ul[data-type="taskList"] {
7373
list-style: none !important;
7474
padding-left: 0 !important;
7575
margin: 16px 0 !important;
7676
}
7777
li[data-type="taskItem"] {
7878
display: flex !important;
79-
align-items: flex-start !important;
80-
margin: 8px 0 !important;
79+
align-items: center !important;
80+
margin: 4px 0 !important;
8181
}
8282
li[data-type="taskItem"] label {
83-
display: flex !important;
84-
margin-right: 8px !important;
85-
flex-shrink: 0 !important;
86-
line-height: 1.5 !important;
87-
height: 24px !important;
88-
align-items: center !important;
83+
display: contents !important;
84+
}
85+
/* Hide the empty span that Tiptap adds */
86+
li[data-type="taskItem"] label > span {
87+
display: none !important;
8988
}
9089
li[data-type="taskItem"] input[type="checkbox"] {
9190
width: 16px !important;
9291
height: 16px !important;
93-
margin: 0 !important;
92+
min-width: 16px !important;
93+
min-height: 16px !important;
94+
margin: 0 8px 0 0 !important;
9495
flex-shrink: 0 !important;
9596
}
9697
li[data-type="taskItem"] > div,
9798
li[data-type="taskItem"] > p {
9899
flex: 1 !important;
99-
color: ${themeColors.foreground} !important;
100-
line-height: 1.5 !important;
100+
line-height: 1.6 !important;
101+
margin: 0 !important;
102+
}
103+
/* Remove p tag margins inside task items */
104+
li[data-type="taskItem"] p {
101105
margin: 0 !important;
106+
line-height: 1.6 !important;
102107
}
103108
strong {
104109
font-weight: 600;

0 commit comments

Comments
 (0)