@@ -51,23 +51,107 @@ export const BaseListPlugin = createTSlatePlugin<BaseListConfig>({
5151 const document = new DOMParser ( ) . parseFromString ( data , 'text/html' ) ;
5252 const { body } = document ;
5353
54+ // First pass: flatten nested UL/OL that are inside LI elements
55+ // We need to move them to be siblings of their parent LI
56+ const lisWithNestedLists : {
57+ li : Element ;
58+ nestedLists : Element [ ] ;
59+ } [ ] = [ ] ;
60+
5461 traverseHtmlElements ( body , ( element ) => {
5562 if ( element . tagName === 'LI' ) {
63+ const nestedLists : Element [ ] = [ ] ;
64+ // Find nested UL/OL elements
65+ Array . from ( element . children ) . forEach ( ( child ) => {
66+ if ( child . tagName === 'UL' || child . tagName === 'OL' ) {
67+ nestedLists . push ( child ) ;
68+ }
69+ } ) ;
70+
71+ if ( nestedLists . length > 0 ) {
72+ lisWithNestedLists . push ( { li : element , nestedLists } ) ;
73+ }
74+ }
75+ return true ;
76+ } ) ;
77+
78+ // Move nested lists to be after their parent LI
79+ lisWithNestedLists . forEach ( ( { li, nestedLists } ) => {
80+ nestedLists . forEach ( ( nestedList ) => {
81+ // Remove the nested list from inside the LI
82+ nestedList . remove ( ) ;
83+ // Insert it after the LI in the parent container
84+ if ( li . parentNode ) {
85+ li . parentNode . insertBefore ( nestedList , li . nextSibling ) ;
86+ }
87+ } ) ;
88+ } ) ;
89+
90+ // Second pass: process LI elements (now without nested lists inside them)
91+ traverseHtmlElements ( body , ( element ) => {
92+ if ( element . tagName === 'LI' ) {
93+ const htmlElement = element as HTMLElement ;
5694 const { childNodes } = element ;
5795
58- // replace li block children (e.g. p) by their children
96+ // Process li children and flatten block elements
5997 const liChildren : Node [ ] = [ ] ;
98+
6099 childNodes . forEach ( ( child ) => {
61- if ( isHtmlBlockElement ( child as Element ) ) {
62- liChildren . push ( ...child . childNodes ) ;
63- } else {
64- liChildren . push ( child ) ;
100+ if ( child . nodeType === Node . ELEMENT_NODE ) {
101+ const childElement = child as Element ;
102+ if ( isHtmlBlockElement ( childElement ) ) {
103+ // Replace block elements (e.g. p) with their children
104+ liChildren . push ( ...childElement . childNodes ) ;
105+ return ;
106+ }
65107 }
108+ liChildren . push ( child ) ;
66109 } ) ;
67110
68111 element . replaceChildren ( ...liChildren ) ;
69112
70- // TODO: recursive check on ul parents for indent
113+ // Check for aria-level first (Google Docs uses this)
114+ const ariaLevel = element . getAttribute ( 'aria-level' ) ;
115+
116+ if ( ariaLevel ) {
117+ // aria-level takes precedence
118+ htmlElement . dataset . indent = ariaLevel ;
119+ } else {
120+ // Calculate indent level based on nested UL/OL parents
121+ let indent = 0 ;
122+ let parent = element . parentElement ;
123+ while ( parent && parent !== body ) {
124+ if ( parent . tagName === 'UL' || parent . tagName === 'OL' ) {
125+ indent ++ ;
126+ }
127+ parent = parent . parentElement ;
128+ }
129+
130+ // Set indent level as data attribute
131+ if ( indent > 0 ) {
132+ htmlElement . dataset . indent = String ( indent ) ;
133+ }
134+ }
135+
136+ // Set list style type from inline style or parent list type
137+ const listStyleType = htmlElement . style . listStyleType ;
138+ if ( listStyleType ) {
139+ htmlElement . dataset . listStyleType = listStyleType ;
140+ } else {
141+ // Fallback to parent list type
142+ const listParent = element . closest ( 'ul, ol' ) ;
143+ if ( listParent ) {
144+ const parentListStyleType = ( listParent as HTMLElement )
145+ . style . listStyleType ;
146+ if ( parentListStyleType ) {
147+ htmlElement . dataset . listStyleType = parentListStyleType ;
148+ } else if ( listParent . tagName === 'UL' ) {
149+ htmlElement . dataset . listStyleType = 'disc' ;
150+ } else if ( listParent . tagName === 'OL' ) {
151+ htmlElement . dataset . listStyleType = 'decimal' ;
152+ }
153+ }
154+ }
71155
72156 return false ;
73157 }
@@ -95,10 +179,19 @@ export const BaseListPlugin = createTSlatePlugin<BaseListConfig>({
95179 } ,
96180 ] ,
97181 parse : ( { editor, element, getOptions } ) => {
182+ // Get indent from data-indent or aria-level (gdoc)
183+ const dataIndent = element . dataset . indent ;
184+ const ariaLevel = element . getAttribute ( 'aria-level' ) ;
185+ const indent = dataIndent ? Number ( dataIndent ) : Number ( ariaLevel ) ;
186+
187+ // Get list style type from data attribute or use default
188+ const dataListStyleType = element . dataset . listStyleType ;
189+ const listStyleType =
190+ dataListStyleType || getOptions ( ) . getListStyleType ?.( element ) ;
191+
98192 return {
99- // gdoc uses aria-level attribute
100- indent : Number ( element . getAttribute ( 'aria-level' ) ) ,
101- listStyleType : getOptions ( ) . getListStyleType ?.( element ) ,
193+ indent : indent || undefined ,
194+ listStyleType : listStyleType || undefined ,
102195 type : editor . getType ( KEYS . p ) ,
103196 } ;
104197 } ,
0 commit comments