@@ -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+ / < u l ( [ ^ > ] * ) > ( [ \s \S ] * ?) < \/ u l > / 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 = / d a t a - t y p e / 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+ / < l i ( [ ^ > ] * ) > ( [ \s \S ] * ?) < \/ l i > / gi,
173+ ( liMatch , liAttrs , liContent ) => {
174+ // Check if this LI contains a checkbox
175+ const checkboxMatch = liContent . match ( / < i n p u t ( [ ^ > ] * t y p e = [ " ' ] c h e c k b o x [ " ' ] [ ^ > ] * ) > / i) ;
176+ if ( checkboxMatch ) {
177+ // Add data-type="taskItem" to LI
178+ const hasLiDataType = / d a t a - t y p e / 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 ( / ^ ( & n b s p ; | \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 ( ! / ^ < ( d i v | 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;
0 commit comments